From 73e7e3fa46af6b6f6b02fae59389f4f3d7b8967e Mon Sep 17 00:00:00 2001
From: Lone Iversen <108085781+loivsen@users.noreply.github.com>
Date: Tue, 2 May 2023 12:48:03 +0200
Subject: [PATCH] date input logic for types date and time, added story
---
.../libs/models/index.ts | 6 +
.../date-input/date-input.element.ts | 178 +++++++++++++++---
.../date-input/date-input.stories.ts | 46 +++++
.../repositories/config/config.repository.ts | 18 ++
.../src/core/mocks/browser-handlers.ts | 2 +
.../src/core/mocks/domains/config.handlers.ts | 13 ++
.../src/core/mocks/e2e-handlers.ts | 2 +
7 files changed, 241 insertions(+), 24 deletions(-)
create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/repositories/config/config.repository.ts
create mode 100644 src/Umbraco.Web.UI.Client/src/core/mocks/domains/config.handlers.ts
diff --git a/src/Umbraco.Web.UI.Client/libs/models/index.ts b/src/Umbraco.Web.UI.Client/libs/models/index.ts
index d2fccc54c2..23a1b95c89 100644
--- a/src/Umbraco.Web.UI.Client/libs/models/index.ts
+++ b/src/Umbraco.Web.UI.Client/libs/models/index.ts
@@ -14,3 +14,9 @@ export interface UmbSwatchDetails {
label: string;
value: string;
}
+export interface ServertimeOffset {
+ /**
+ * offset in minutes relative to UTC
+ */
+ offset: number;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/date-input/date-input.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/date-input/date-input.element.ts
index d269cbcf71..12fe5f1717 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/date-input/date-input.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/date-input/date-input.element.ts
@@ -1,9 +1,10 @@
-import { css, html } from 'lit';
+import { PropertyValueMap, css, html } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property, state } from 'lit/decorators.js';
import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins';
import { ifDefined } from 'lit/directives/if-defined.js';
import { UUIInputEvent } from '@umbraco-ui/uui';
+import { UmbConfigRepository } from '../../repositories/config/config.repository';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
@customElement('umb-date-input')
@@ -14,8 +15,23 @@ export class UmbDateInputElement extends FormControlMixin(UmbLitElement) {
return undefined;
}
+ /**
+ * Specifies the type of input that will be rendered.
+ * @type {'date'| 'time'| 'datetime-local'}
+ * @attr
+ * @default date
+ */
+ @property()
+ type: 'date' | 'time' | 'datetime-local' = 'date';
+
@property({ type: String })
- type = 'date';
+ displayValue?: string;
+
+ @property({ type: Boolean })
+ offsetTime = false;
+
+ @state()
+ private _offsetValue = 0;
@property({ type: String })
min?: string;
@@ -26,11 +42,7 @@ export class UmbDateInputElement extends FormControlMixin(UmbLitElement) {
@property({ type: Number })
step?: number;
- @property({ type: Boolean })
- offsetTime = false;
-
- @state()
- private _localValue?: string;
+ private _configRepository = new UmbConfigRepository(this);
constructor() {
super();
@@ -38,50 +50,168 @@ export class UmbDateInputElement extends FormControlMixin(UmbLitElement) {
connectedCallback(): void {
super.connectedCallback();
- this.offsetTime
- ? (this._localValue = this.value as string)
- : (this._localValue = this.#getLocal(this.value as string));
+ this.offsetTime ? this.#getOffset() : (this.displayValue = this.#UTCToLocal(this.value as string));
+ }
+ /*
+ connectedCallback(): void {
+ super.connectedCallback();
+ this.offsetTime ? this.#getOffset() : (this.displayValue = this.#getLocal(this.value as string));
+ } */
+
+ async #getOffset() {
+ const data = await this._configRepository.getServertimeOffset();
+ if (!data) return;
+ this._offsetValue = data.offset;
+
+ if (!this.value) return;
+ this.displayValue = this.#valueToServerOffset(this.value as string, true);
}
+ #localToUTC(d: string) {
+ if (this.type === 'time') {
+ return new Date(`${new Date().toJSON().slice(0, 10)} ${d}`).toISOString().slice(11, 16);
+ } else {
+ const date = new Date(d);
+ const isoDate = date.toISOString();
+ return `${isoDate.substring(0, 10)}T${isoDate.substring(11, 19)}Z`;
+ }
+ }
+
+ #UTCToLocal(d: string) {
+ if (this.type === 'time') {
+ const local = new Date(`${new Date().toJSON().slice(0, 10)} ${d}Z`)
+ .toLocaleTimeString(undefined, {
+ hourCycle: 'h23',
+ })
+ .slice(0, 5);
+ return local;
+ } else {
+ const timezoneReset = `${d.replace('Z', '')}Z`;
+ const date = new Date(timezoneReset);
+
+ const dateString = `${date.getFullYear()}-${('0' + (date.getMonth() + 1)).slice(-2)}-${(
+ '0' + date.getDate()
+ ).slice(-2)}T${('0' + date.getHours()).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}:${(
+ '0' + date.getSeconds()
+ ).slice(-2)}`;
+
+ return this.type === 'datetime-local' ? dateString : `${dateString.substring(0, 10)}`;
+ }
+ }
+
+ #dateToString(date: Date) {
+ return `${date.getFullYear()}-${('0' + (date.getMonth() + 1)).slice(-2)}-${('0' + date.getDate()).slice(-2)}T${(
+ '0' + date.getHours()
+ ).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}:${('0' + date.getSeconds()).slice(-2)}`;
+ }
+
+ #valueToServerOffset(d: string, utc = false) {
+ if (this.type === 'time') {
+ return '';
+ } else {
+ const newDate = new Date(d.replace('Z', ''));
+ const dateOffset = new Date(
+ newDate.setTime(newDate.getTime() + (utc ? this._offsetValue * -1 : this._offsetValue) * 60 * 1000)
+ );
+ return this.type === 'datetime-local'
+ ? this.#dateToString(dateOffset)
+ : this.#dateToString(dateOffset).slice(0, 10);
+ }
+ }
+
+ #onChange(e: UUIInputEvent) {
+ e.stopPropagation();
+ const picked = e.target.value as string;
+ this.value = picked ? this.#localToUTC(picked) : '';
+ this.displayValue = picked ? picked : '';
+ this.dispatchEvent(new CustomEvent('change'));
+ }
+
+ /*
+
+ #valueToServerOffset(date: string, utc = false) {
+ const newDate = new Date(date);
+ const dateOffset = new Date(
+ newDate.setTime(newDate.getTime() + (utc ? this._offsetValue * -1 : this._offsetValue) * 60 * 1000)
+ );
+ }
+
+ async #getOffset() {
+ const data = await this._configRepository.getServertimeOffset();
+ if (!data) return;
+ this._offsetValue = data.offset;
+
+ if (!this.value) return;
+ this.displayValue = this.#convertValueToServerOffset((this.value as string).replace('Z', ''), true);
+ }
+
+ /** Converts a Date object to string in the correct format for input field */
+ /*
+ #convertDateToString(date: Date) {
+ if (!date) return;
+ const string = `${date.getFullYear()}-${('0' + (date.getMonth() + 1)).slice(-2)}-${('0' + date.getDate()).slice(
+ -2
+ )}T${('0' + date.getHours()).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}:${('0' + date.getSeconds()).slice(
+ -2
+ )}Z`;
+ return string;
+ }
+ */
+ /*
+ #convertValueToServerOffset(date: string, utc?: boolean) {
+ if (!date) return;
+ const newDate = new Date(date);
+ // + offsetValue (minutes) * seconds * miliseconds
+ const dateOffset = new Date(
+ newDate.setTime(newDate.getTime() + (utc ? this._offsetValue * -1 : this._offsetValue) * 60 * 1000)
+ );
+ console.log(dateOffset);
+ return this.#convertDateToString(dateOffset);
+ }
+ */
+ /*
#getUTC(timeLocal: string) {
+ if (!timeLocal) return;
const date = new Date(timeLocal);
const isoDate = date.toISOString();
return `${isoDate.substring(0, 10)}T${isoDate.substring(11, 19)}Z`;
}
#getLocal(timeUTC: string) {
+ if (!timeUTC) return;
const local = new Date(timeUTC);
- const localString = `${local.getFullYear()}-${('0' + (local.getMonth() + 1)).slice(-2)}-${(
- '0' + local.getDate()
- ).slice(-2)}T${('0' + local.getHours()).slice(-2)}:${('0' + local.getMinutes()).slice(-2)}:${(
- '0' + local.getSeconds()
- ).slice(-2)}Z`;
- return localString;
+ return this.#convertDateToString(local);
}
-
+*/
+ /*
#onDatetimeChange(e: UUIInputEvent) {
e.stopPropagation();
const pickedTime = e.target.value as string;
- this.offsetTime ? (this.value = pickedTime) : (this.value = this.#getUTC(pickedTime));
+ this.displayValue = pickedTime;
+
+ const val = this.offsetTime ? this.#convertValueToServerOffset(pickedTime) : this.#getUTC(pickedTime);
+ this.value = val ?? '';
- this._localValue = pickedTime;
this.dispatchEvent(new CustomEvent('change'));
}
+ */
+ //@change="${this.#onDatetimeChange}"
render() {
return html`
-
+ .value="${this.displayValue?.replace('Z', '')}">
+
+
UTC: ${this.value}
- Local ${this._localValue}
- offset: ${this.offsetTime}`;
+ Display: ${this.displayValue}
+ Offset: ${this._offsetValue}
`;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/date-input/date-input.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/date-input/date-input.stories.ts
index 85458a242a..381f55073c 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/date-input/date-input.stories.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/date-input/date-input.stories.ts
@@ -1,4 +1,5 @@
import { Meta, StoryObj } from '@storybook/web-components';
+import { html } from 'lit';
import './date-input.element';
import type { UmbDateInputElement } from './date-input.element';
@@ -17,3 +18,48 @@ export const Overview: Story = {
offsetTime: true,
},
};
+
+export const Date: Story = {
+ args: {
+ type: 'date',
+ value: '2023-04-01',
+ offsetTime: true,
+ },
+};
+
+export const Time: Story = {
+ args: {
+ type: 'time',
+ value: '10:00',
+ },
+};
+
+export const DatetimelocalOffset: Story = {
+ args: {
+ type: 'datetime-local',
+ value: '2023-04-01T10:00:00',
+ offsetTime: false,
+ displayValue: '',
+ },
+ render: (args) =>
+ html``,
+};
+
+export const Datetimelocal: Story = {
+ args: {
+ type: 'datetime-local',
+ value: '2023-04-01T10:00:00',
+ offsetTime: false,
+ displayValue: '',
+ },
+ render: (args) =>
+ html``,
+};
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/repositories/config/config.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/repositories/config/config.repository.ts
new file mode 100644
index 0000000000..1f7aef941a
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/repositories/config/config.repository.ts
@@ -0,0 +1,18 @@
+import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
+import type { ServertimeOffset } from '@umbraco-cms/backoffice/models';
+import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
+import { umbracoPath } from '@umbraco-cms/backoffice/utils';
+
+export class UmbConfigRepository {
+ #host;
+
+ constructor(host: UmbControllerHostElement) {
+ this.#host = host;
+ }
+
+ async getServertimeOffset() {
+ const resource = fetch(umbracoPath('/config/servertimeoffset')).then((res) => res.json());
+ const { data } = await tryExecuteAndNotify(this.#host, resource);
+ return data;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/browser-handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/browser-handlers.ts
index e899c2aaf6..a985c05391 100644
--- a/src/Umbraco.Web.UI.Client/src/core/mocks/browser-handlers.ts
+++ b/src/Umbraco.Web.UI.Client/src/core/mocks/browser-handlers.ts
@@ -29,6 +29,7 @@ import { handlers as logViewerHandlers } from './domains/log-viewer.handlers';
import { handlers as packageHandlers } from './domains/package.handlers';
import { handlers as rteEmbedHandlers } from './domains/rte-embed.handlers';
import { handlers as stylesheetHandlers } from './domains/stylesheet.handlers';
+import { handlers as configHandlers } from './domains/config.handlers';
const handlers = [
serverHandlers.serverVersionHandler,
@@ -61,6 +62,7 @@ const handlers = [
...packageHandlers,
...rteEmbedHandlers,
...stylesheetHandlers,
+ ...configHandlers,
];
switch (import.meta.env.VITE_UMBRACO_INSTALL_STATUS) {
diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/config.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/config.handlers.ts
new file mode 100644
index 0000000000..800d803b10
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/config.handlers.ts
@@ -0,0 +1,13 @@
+import { rest } from 'msw';
+import { umbracoPath } from '@umbraco-cms/backoffice/utils';
+import type { ServertimeOffset } from '@umbraco-cms/backoffice/models';
+
+export const handlers = [
+ rest.get(umbracoPath('/config/servertimeoffset'), (_req, res, ctx) => {
+ return res(
+ // Respond with a 200 status code
+ ctx.status(200),
+ ctx.json({ offset: -120 })
+ );
+ }),
+];
diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/e2e-handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/e2e-handlers.ts
index e0a5d8efe6..9e02222276 100644
--- a/src/Umbraco.Web.UI.Client/src/core/mocks/e2e-handlers.ts
+++ b/src/Umbraco.Web.UI.Client/src/core/mocks/e2e-handlers.ts
@@ -14,6 +14,7 @@ import { handlers as healthCheckHandlers } from './domains/health-check.handlers
import { handlers as languageHandlers } from './domains/language.handlers';
import { handlers as redirectManagementHandlers } from './domains/redirect-management.handlers';
import { handlers as packageHandlers } from './domains/package.handlers';
+import { handlers as configHandlers } from './domains/config.handlers';
export const handlers = [
serverHandlers.serverRunningHandler,
@@ -33,4 +34,5 @@ export const handlers = [
...languageHandlers,
...redirectManagementHandlers,
...packageHandlers,
+ ...configHandlers,
];