From a766a7b0edf9426cb262d2f7596b4cb02446ba29 Mon Sep 17 00:00:00 2001 From: Laura Neto <12862535+lauraneto@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:10:51 +0200 Subject: [PATCH 01/86] Started the implementation of the new date time property editor --- src/Umbraco.Core/Constants-DataTypes.cs | 10 + src/Umbraco.Core/Constants-PropertyEditors.cs | 5 + .../DateTimeWithTimeZoneConfiguration.cs | 10 + ...DateTimeWithTimeZoneConfigurationEditor.cs | 22 ++ .../DateTimeWithTimeZonePropertyEditor.cs | 28 +++ .../Migrations/Install/DatabaseDataCreator.cs | 16 ++ .../DateTimeWithTimeZoneValueConverter.cs | 52 ++++ src/Umbraco.Web.UI.Client/package-lock.json | 29 +++ src/Umbraco.Web.UI.Client/package.json | 1 + .../src/external/luxon/index.ts | 1 + .../src/external/luxon/package.json | 14 ++ .../src/external/luxon/vite.config.ts | 18 ++ .../src/packages/core/models/index.ts | 5 + .../Umbraco.DateTimeWithTimeZone.ts | 10 + .../date-with-time-zone-picker/manifests.ts | 35 +++ ...r-ui-date-with-time-zone-picker.element.ts | 228 ++++++++++++++++++ .../packages/property-editors/manifests.ts | 4 + .../time-zone-picker/manifests.ts | 13 + ...erty-editor-ui-time-zone-picker.element.ts | 75 ++++++ src/Umbraco.Web.UI.Client/tsconfig.json | 1 + 20 files changed, 577 insertions(+) create mode 100644 src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZoneConfiguration.cs create mode 100644 src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZoneConfigurationEditor.cs create mode 100644 src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZonePropertyEditor.cs create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/DateTimeWithTimeZoneValueConverter.cs create mode 100644 src/Umbraco.Web.UI.Client/src/external/luxon/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/external/luxon/package.json create mode 100644 src/Umbraco.Web.UI.Client/src/external/luxon/vite.config.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/Umbraco.DateTimeWithTimeZone.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/property-editor-ui-date-with-time-zone-picker.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/time-zone-picker/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/time-zone-picker/property-editor-ui-time-zone-picker.element.ts diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index 5db4d71efc49..f013fb064fdd 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -225,6 +225,11 @@ public static class Guids /// public const string LabelDecimal = "8f1ef1e1-9de4-40d3-a072-6673f631ca64"; + /// + /// Guid for Date Picker with timezone + /// + public const string DatePickerWithTimezone = "e7ccd055-60e2-4c63-88e7-212249eaf581"; + /// /// Guid for Content Picker /// @@ -399,6 +404,11 @@ public static class Guids /// Guid for Label decimal /// public static readonly Guid LabelDecimalGuid = new(LabelDecimal); + + /// + /// Guid for Date Picker with timezone + /// + public static readonly Guid DatePickerWithTimezoneGuid = new(DatePickerWithTimezone); } } } diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 0d0b5d97675a..a5a49311798e 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -70,6 +70,11 @@ public static class Aliases /// public const string DateTime = "Umbraco.DateTime"; + /// + /// UTC DateTime. + /// + public const string DateTimeWithTimeZone = "Umbraco.DateTimeWithTimeZone"; + /// /// DropDown List. /// diff --git a/src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZoneConfiguration.cs b/src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZoneConfiguration.cs new file mode 100644 index 000000000000..ac0421b0f610 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZoneConfiguration.cs @@ -0,0 +1,10 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Cms.Core.PropertyEditors; + +public class DateTimeWithTimeZoneConfiguration +{ + [ConfigurationField("timeZones")] + public List TimeZones { get; set; } = []; +} diff --git a/src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZoneConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZoneConfigurationEditor.cs new file mode 100644 index 000000000000..0440563cc413 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZoneConfigurationEditor.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.IO; + +namespace Umbraco.Cms.Core.PropertyEditors; + +public class DateTimeWithTimeZoneConfigurationEditor : ConfigurationEditor +{ + public DateTimeWithTimeZoneConfigurationEditor(IIOHelper ioHelper) + : base(ioHelper) + { + } + + /// + public override IDictionary ToConfigurationEditor(IDictionary configuration) + { + return configuration; + } + + public override IDictionary ToValueEditor(IDictionary configuration) => base.ToValueEditor(configuration); +} diff --git a/src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZonePropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZonePropertyEditor.cs new file mode 100644 index 000000000000..fcda1dc270c2 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DateTimeWithTimeZonePropertyEditor.cs @@ -0,0 +1,28 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.IO; + +namespace Umbraco.Cms.Core.PropertyEditors; + +[DataEditor( + Constants.PropertyEditors.Aliases.DateTimeWithTimeZone, + ValueType = ValueTypes.Json, + ValueEditorIsReusable = true)] +public class DateTimeWithTimeZonePropertyEditor : DataEditor +{ + private readonly IIOHelper _ioHelper; + + public DateTimeWithTimeZonePropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IIOHelper ioHelper) + : base(dataValueEditorFactory) + { + _ioHelper = ioHelper; + SupportsReadOnly = true; + } + + /// + protected override IConfigurationEditor CreateConfigurationEditor() => + new DateTimeWithTimeZoneConfigurationEditor(_ioHelper); +} diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index 4d01ca95d9eb..daa4a032600a 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -2290,6 +2290,22 @@ string GridCollectionView(string collectionViewType) => "\", \"multiple\": true}", }); } + + /*if (_database.Exists(1055)) + { + _database.Insert( + Constants.DatabaseSchema.Tables.DataType, + "pk", + false, + new DataTypeDto + { + NodeId = 1055, + EditorAlias = Constants.PropertyEditors.Aliases.DateTimeWithTimezone, + EditorUiAlias = "Umb.PropertyEditorUi.DatePicker", + DbType = "Ntext", + Configuration = "{\"timezones\": [\"UTC\"], \"format\": \"YYYY-MM-DD HH:mm:ss\"}", + }); + }*/ } private void CreateRelationTypeData() diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/DateTimeWithTimeZoneValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/DateTimeWithTimeZoneValueConverter.cs new file mode 100644 index 000000000000..dd1a967a6ac3 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/DateTimeWithTimeZoneValueConverter.cs @@ -0,0 +1,52 @@ +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +/// +/// The date time with timezone property value converter. +/// +[DefaultPropertyValueConverter(typeof(JsonValueConverter))] +public class DateTimeWithTimeZoneValueConverter : PropertyValueConverterBase +{ + private readonly IJsonSerializer _jsonSerializer; + + public DateTimeWithTimeZoneValueConverter(IJsonSerializer jsonSerializer) + => _jsonSerializer = jsonSerializer; + + /// + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.DateTimeWithTimeZone); + + /// + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(DateTimeOffset); + + /// + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + /// + public override object ConvertSourceToIntermediate( + IPublishedElement owner, + IPublishedPropertyType propertyType, + object? source, + bool preview) + { + var sourceStr = source?.ToString(); + if (sourceStr is not null && _jsonSerializer.TryDeserialize(sourceStr, out DateTimeWithTimeZone? dateTimeWithTimezone)) + { + return dateTimeWithTimezone.Date; + } + + return DateTimeOffset.MinValue; + } + + internal class DateTimeWithTimeZone + { + public required DateTimeOffset Date { get; set; } + + public required string TimeZone { get; set; } + } +} diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 4bb032625a2c..e323e78d8b80 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -2980,6 +2980,13 @@ "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "license": "MIT" }, + "node_modules/@types/luxon": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz", + "integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/markdown-it": { "version": "14.1.2", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", @@ -3855,6 +3862,10 @@ "resolved": "src/packages/extension-insights", "link": true }, + "node_modules/@umbraco-backoffice/external-luxon": { + "resolved": "src/external/luxon", + "link": true + }, "node_modules/@umbraco-backoffice/external-tiptap": { "resolved": "src/external/tiptap", "link": true @@ -10744,6 +10755,15 @@ "dev": true, "license": "MIT" }, + "node_modules/luxon": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz", + "integrity": "sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/madge": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/madge/-/madge-8.0.0.tgz", @@ -17253,6 +17273,15 @@ "lit": "^3.3.0" } }, + "src/external/luxon": { + "name": "@umbraco-backoffice/external-luxon", + "dependencies": { + "luxon": "^3.7.1" + }, + "devDependencies": { + "@types/luxon": "^3.6.2" + } + }, "src/external/marked": { "name": "@umbraco-backoffice/marked", "dependencies": { diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index e52c53f05864..7b13c5c3835b 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -122,6 +122,7 @@ "./external/dompurify": "./dist-cms/external/dompurify/index.js", "./external/heximal-expressions": "./dist-cms/external/heximal-expressions/index.js", "./external/lit": "./dist-cms/external/lit/index.js", + "./external/luxon": "./dist-cms/external/luxon/index.js", "./external/marked": "./dist-cms/external/marked/index.js", "./external/monaco-editor": "./dist-cms/external/monaco-editor/index.js", "./external/openid": "./dist-cms/external/openid/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/external/luxon/index.ts b/src/Umbraco.Web.UI.Client/src/external/luxon/index.ts new file mode 100644 index 000000000000..fea859b5fb77 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/luxon/index.ts @@ -0,0 +1 @@ +export * from 'luxon'; diff --git a/src/Umbraco.Web.UI.Client/src/external/luxon/package.json b/src/Umbraco.Web.UI.Client/src/external/luxon/package.json new file mode 100644 index 000000000000..4ed68c00fbcb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/luxon/package.json @@ -0,0 +1,14 @@ +{ + "name": "@umbraco-backoffice/external-luxon", + "private": true, + "type": "module", + "scripts": { + "build": "vite build" + }, + "dependencies": { + "luxon": "^3.7.1" + }, + "devDependencies": { + "@types/luxon": "^3.6.2" + } +} diff --git a/src/Umbraco.Web.UI.Client/src/external/luxon/vite.config.ts b/src/Umbraco.Web.UI.Client/src/external/luxon/vite.config.ts new file mode 100644 index 000000000000..a5c3bd73dde9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/luxon/vite.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite'; +import { rmSync } from 'fs'; +import { getDefaultConfig } from '../../vite-config-base'; + +const dist = '../../../dist-cms/external/luxon'; + +// delete the unbundled dist folder +rmSync(dist, { recursive: true, force: true }); + +export default defineConfig({ + ...getDefaultConfig({ + dist, + base: '/umbraco/backoffice/external/luxon', + entry: { + index: './index.ts', + }, + }), +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/models/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/models/index.ts index 804ef8093476..3a799c96bdd0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/models/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/models/index.ts @@ -33,3 +33,8 @@ export interface UmbUniqueItemModel { name: string; icon?: string; } + +export interface UmbDateTimeWithTimeZone { + date: string | undefined; + timeZone: string | undefined; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/Umbraco.DateTimeWithTimeZone.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/Umbraco.DateTimeWithTimeZone.ts new file mode 100644 index 000000000000..a5d3457e8755 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/Umbraco.DateTimeWithTimeZone.ts @@ -0,0 +1,10 @@ +import type { ManifestPropertyEditorSchema } from '@umbraco-cms/backoffice/property-editor'; + +export const manifest: ManifestPropertyEditorSchema = { + type: 'propertyEditorSchema', + name: 'Date/Time with Time Zone', + alias: 'Umbraco.DateTimeWithTimeZone', + meta: { + defaultPropertyEditorUiAlias: 'Umb.PropertyEditorUi.DateWithTimeZonePicker', + }, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/manifests.ts new file mode 100644 index 000000000000..f20341e6246e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/manifests.ts @@ -0,0 +1,35 @@ +import { manifest as schemaManifest } from './Umbraco.DateTimeWithTimeZone.js'; + +export const manifests: Array = [ + { + type: 'propertyEditorUi', + alias: 'Umb.PropertyEditorUi.DateWithTimeZonePicker', + name: 'Date With Timezone Picker Property Editor UI', + element: () => import('./property-editor-ui-date-with-time-zone-picker.element.js'), + meta: { + label: 'Date With Time Zone Picker', + propertyEditorSchemaAlias: 'Umbraco.DateTimeWithTimeZone', + icon: 'icon-time', + group: 'pickers', + supportsReadOnly: true, + settings: { + properties: [ + { + alias: 'timeZones', + label: 'Time Zones', + description: 'Select the time zones that the user should be able to select from.', + propertyEditorUiAlias: 'Umb.PropertyEditorUi.TimeZonePicker', + config: [], + }, + ], + defaultData: [ + { + alias: 'timeZones', + value: ['UTC'], + }, + ], + }, + }, + }, + schemaManifest, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/property-editor-ui-date-with-time-zone-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/property-editor-ui-date-with-time-zone-picker.element.ts new file mode 100644 index 000000000000..8491a98fbcc3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/date-with-time-zone-picker/property-editor-ui-date-with-time-zone-picker.element.ts @@ -0,0 +1,228 @@ +import type { + UmbPropertyEditorConfigCollection, + UmbPropertyEditorUiElement, +} from '@umbraco-cms/backoffice/property-editor'; +import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbInputDateElement } from '@umbraco-cms/backoffice/components'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; +import type { UmbDateTimeWithTimeZone } from '@umbraco-cms/backoffice/models'; +import { DateTime } from '@umbraco-cms/backoffice/external/luxon'; + +/** + * @element umb-property-editor-ui-date-with-time-zone-picker + */ +@customElement('umb-property-editor-ui-date-with-time-zone-picker') +export class UmbPropertyEditorUIDateWithTimeZonePickerElement + extends UmbLitElement + implements UmbPropertyEditorUiElement +{ + @state() + private _value: UmbDateTimeWithTimeZone | undefined; + + @property({ type: Object }) + public set value(value: UmbDateTimeWithTimeZone | undefined) { + this._value = value; + } + + public get value(): UmbDateTimeWithTimeZone | undefined { + return this._value; + } + + /** + * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + readonly = false; + + @state() + private _currentDate?: DateTime; + + @state() + private _datePickerValue: string = ''; + + @state() + private _selectedTimeZone?: string = ''; + + @state() + private _timeZoneOffset: string = ''; + + @state() + private _timeZoneOptions: Array