diff --git a/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js b/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js index d93cc1c6a414..41837171a7ac 100644 --- a/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js +++ b/services/static-webserver/client/source/class/osparc/conversation/AddMessage.js @@ -175,7 +175,7 @@ qx.Class.define("osparc.conversation.AddMessage", { addComment: function() { const conversationId = this.getConversationId(); if (conversationId) { - this.__postMessage(); + return this.__postMessage(); } else { const studyData = this.getStudyData(); let promise = null; @@ -191,10 +191,10 @@ qx.Class.define("osparc.conversation.AddMessage", { } promise = osparc.store.ConversationsSupport.getInstance().postConversation(extraContext); } - promise + return promise .then(data => { this.setConversationId(data["conversationId"]); - this.__postMessage(); + return this.__postMessage(); }); } }, @@ -211,12 +211,14 @@ qx.Class.define("osparc.conversation.AddMessage", { } else { promise = osparc.store.ConversationsSupport.getInstance().postMessage(conversationId, content); } - promise + return promise .then(data => { this.fireDataEvent("messageAdded", data); commentField.getChildControl("text-area").setValue(""); + return data; }); } + return Promise.reject(); }, __editComment: function() { diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js index 165437d17962..4b5c8181db9f 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -1515,7 +1515,7 @@ qx.Class.define("osparc.data.Resources", { method: "GET", url: statics.API + "/conversations/{conversationId}" }, - renameConversation: { + patchConversation: { method: "PATCH", url: statics.API + "/conversations/{conversationId}" }, diff --git a/services/static-webserver/client/source/class/osparc/data/model/Conversation.js b/services/static-webserver/client/source/class/osparc/data/model/Conversation.js index b1e0e568751a..01a322c0c974 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Conversation.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Conversation.js @@ -246,6 +246,13 @@ qx.Class.define("osparc.data.model.Conversation", { }); }, + patchExtraContext: function(extraContext) { + osparc.store.ConversationsSupport.getInstance().patchExtraContext(this.getConversationId(), extraContext) + .then(() => { + this.setExtraContext(extraContext); + }); + }, + addMessage: function(message) { if (message) { const found = this.__messages.find(msg => msg["messageId"] === message["messageId"]); @@ -284,6 +291,27 @@ qx.Class.define("osparc.data.model.Conversation", { return this.getExtraContext()["projectId"]; } return null; - } + }, + + getAppointment: function() { + if (this.getExtraContext() && "appointment" in this.getExtraContext()) { + return this.getExtraContext()["appointment"]; + } + return null; + }, + + setAppointment: function(appointment) { + const extraContext = this.getExtraContext() || {}; + extraContext["appointment"] = appointment ? appointment.toISOString() : null; + // OM: Supporters are not allowed to patch the conversation metadata yet + const backendAllowsPatch = osparc.store.Products.getInstance().amIASupportUser() ? false : true; + if (backendAllowsPatch) { + return osparc.store.ConversationsSupport.getInstance().patchExtraContext(this.getConversationId(), extraContext) + .then(() => { + this.setExtraContext(Object.assign({}, extraContext)); + }); + } + return Promise.resolve(this.setExtraContext(Object.assign({}, extraContext))); + }, }, }); diff --git a/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js b/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js index 05b5cae55214..4aa7cd1361b4 100644 --- a/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js +++ b/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js @@ -124,7 +124,19 @@ qx.Class.define("osparc.store.ConversationsSupport", { name, } }; - return osparc.data.Resources.fetch("conversationsSupport", "renameConversation", params); + return osparc.data.Resources.fetch("conversationsSupport", "patchConversation", params); + }, + + patchExtraContext: function(conversationId, extraContext) { + const params = { + url: { + conversationId, + }, + data: { + extraContext, + } + }; + return osparc.data.Resources.fetch("conversationsSupport", "patchConversation", params); }, fetchLastMessage: function(conversationId) { diff --git a/services/static-webserver/client/source/class/osparc/support/ConversationPage.js b/services/static-webserver/client/source/class/osparc/support/ConversationPage.js index 685f7372ebc5..ed1eb3404598 100644 --- a/services/static-webserver/client/source/class/osparc/support/ConversationPage.js +++ b/services/static-webserver/client/source/class/osparc/support/ConversationPage.js @@ -84,14 +84,8 @@ qx.Class.define("osparc.support.ConversationPage", { }); this.getChildControl("conversation-header-center-layout").addAt(control, 0); break; - case "conversation-extra-content": - control = new qx.ui.basic.Label().set({ - font: "text-12", - textColor: "text-disabled", - rich: true, - allowGrowX: true, - selectable: true, - }); + case "conversation-extra-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.VBox(2)); this.getChildControl("conversation-header-center-layout").addAt(control, 1); break; case "open-project-button": @@ -105,6 +99,19 @@ qx.Class.define("osparc.support.ConversationPage", { control.addListener("execute", () => this.__openProjectDetails()); this.getChildControl("conversation-header-layout").addAt(control, 2); break; + case "set-appointment-button": { + control = new qx.ui.form.Button().set({ + maxWidth: 26, + maxHeight: 24, + padding: [0, 6], + alignX: "center", + alignY: "middle", + icon: "@FontAwesome5Solid/clock/12", + }); + control.addListener("execute", () => this.__openAppointmentDetails()); + this.getChildControl("conversation-header-layout").addAt(control, 3); + break; + } case "conversation-options": { control = new qx.ui.form.MenuButton().set({ maxWidth: 24, @@ -123,7 +130,7 @@ qx.Class.define("osparc.support.ConversationPage", { }); renameButton.addListener("execute", () => this.__renameConversation()); menu.add(renameButton); - this.getChildControl("conversation-header-layout").addAt(control, 3); + this.getChildControl("conversation-header-layout").addAt(control, 4); break; } case "conversation-content": @@ -146,28 +153,65 @@ qx.Class.define("osparc.support.ConversationPage", { title.setValue(this.tr("Ask a Question")); } - const extraContextLabel = this.getChildControl("conversation-extra-content"); + const extraContextLayout = this.getChildControl("conversation-extra-layout"); const amISupporter = osparc.store.Products.getInstance().amIASupportUser(); - if (conversation && amISupporter) { - const extraContext = conversation.getExtraContext(); - if (extraContext && Object.keys(extraContext).length) { - let extraContextText = `Ticket ID: ${conversation.getConversationId()}`; - const contextProjectId = conversation.getContextProjectId(); - if (contextProjectId) { - extraContextText += `
Project ID: ${contextProjectId}`; + if (conversation) { + const createExtraContextLabel = text => { + return new qx.ui.basic.Label(text).set({ + font: "text-12", + textColor: "text-disabled", + rich: true, + allowGrowX: true, + selectable: true, + }); + }; + const updateExtraContext = () => { + extraContextLayout.removeAll(); + const extraContext = conversation.getExtraContext(); + if (extraContext && Object.keys(extraContext).length) { + const ticketIdLabel = createExtraContextLabel(`Ticket ID: ${conversation.getConversationId()}`); + extraContextLayout.add(ticketIdLabel); + const contextProjectId = conversation.getContextProjectId(); + if (contextProjectId && amISupporter) { + const projectIdLabel = createExtraContextLabel(`Project ID: ${contextProjectId}`); + extraContextLayout.add(projectIdLabel); + } + const appointment = conversation.getAppointment(); + if (appointment) { + const appointmentLabel = createExtraContextLabel(); + let appointmentText = "Appointment: "; + if (appointment === "requested") { + // still pending + appointmentText += appointment; + } else { + // already set + appointmentText += osparc.utils.Utils.formatDateAndTime(new Date(appointment)); + appointmentLabel.set({ + cursor: "pointer", + toolTipText: osparc.utils.Utils.formatDateWithCityAndTZ(new Date(appointment)), + }); + } + appointmentLabel.setValue(appointmentText); + extraContextLayout.add(appointmentLabel); + } } - extraContextLabel.setValue(extraContextText); - } - extraContextLabel.show(); - } else { - extraContextLabel.exclude(); + }; + updateExtraContext(); + conversation.addListener("changeExtraContext", () => updateExtraContext(), this); } - const openButton = this.getChildControl("open-project-button"); + const openProjectButton = this.getChildControl("open-project-button"); if (conversation && conversation.getContextProjectId()) { - openButton.show(); + openProjectButton.show(); } else { - openButton.exclude(); + openProjectButton.exclude(); + } + + const setAppointmentButton = this.getChildControl("set-appointment-button"); + if (conversation && conversation.getAppointment() && amISupporter) { + setAppointmentButton.show(); + } else { + setAppointmentButton.exclude(); } const options = this.getChildControl("conversation-options"); @@ -193,6 +237,17 @@ qx.Class.define("osparc.support.ConversationPage", { } }, + __openAppointmentDetails: function() { + const win = new osparc.widget.DateTimeChooser(); + win.addListener("dateChanged", e => { + const newValue = e.getData()["newValue"]; + this.getConversation().setAppointment(newValue) + .catch(err => console.error(err)); + win.close(); + }, this); + win.open(); + }, + __renameConversation: function() { let oldName = this.getConversation().getName(); if (oldName === "null") { @@ -207,5 +262,19 @@ qx.Class.define("osparc.support.ConversationPage", { renamer.center(); renamer.open(); }, + + __getAddMessageField: function() { + return this.getChildControl("conversation-content") && + this.getChildControl("conversation-content").getChildControl("add-message"); + }, + + postMessage: function(message) { + const addMessage = this.__getAddMessageField(); + if (addMessage && addMessage.getChildControl("comment-field")) { + addMessage.getChildControl("comment-field").setText(message); + return addMessage.addComment(); + } + return Promise.reject(); + }, } }); diff --git a/services/static-webserver/client/source/class/osparc/support/SupportCenter.js b/services/static-webserver/client/source/class/osparc/support/SupportCenter.js index 925b5e9cb327..80ae521f7c27 100644 --- a/services/static-webserver/client/source/class/osparc/support/SupportCenter.js +++ b/services/static-webserver/client/source/class/osparc/support/SupportCenter.js @@ -38,10 +38,12 @@ qx.Class.define("osparc.support.SupportCenter", { this.getChildControl("conversations-intro-text"); this.getChildControl("conversations-list"); this.getChildControl("ask-a-question-button"); + this.getChildControl("book-a-call-button"); }, statics: { WINDOW_WIDTH: 430, + REQUEST_CALL_MESSAGE: "Dear Support,\nI would like to make an appointment for a support call.", getMaxHeight: function() { // height: max 80% of screen, or 600px @@ -112,15 +114,29 @@ qx.Class.define("osparc.support.SupportCenter", { }); break; } + case "buttons-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({ + alignX: "center", + })); + this.getChildControl("conversations-layout").add(control); + break; case "ask-a-question-button": control = new osparc.ui.form.FetchButton(this.tr("Ask a Question")).set({ appearance: "strong-button", allowGrowX: false, center: true, - alignX: "center", }); control.addListener("execute", () => this.openConversation(null), this); - this.getChildControl("conversations-layout").add(control); + this.getChildControl("buttons-layout").add(control); + break; + case "book-a-call-button": + control = new osparc.ui.form.FetchButton(this.tr("Book a Call")).set({ + appearance: "strong-button", + allowGrowX: false, + center: true, + }); + control.addListener("execute", () => this.createConversationBookCall(null), this); + this.getChildControl("buttons-layout").add(control); break; case "conversation-page": control = new osparc.support.ConversationPage(); @@ -152,5 +168,30 @@ qx.Class.define("osparc.support.SupportCenter", { this.__showConversation(); } }, + + createConversationBookCall: function() { + const conversationPage = this.getChildControl("conversation-page"); + conversationPage.setConversation(null); + this.__showConversation(); + conversationPage.postMessage(osparc.support.SupportCenter.REQUEST_CALL_MESSAGE) + .then(data => { + const conversationId = data["conversationId"]; + osparc.store.ConversationsSupport.getInstance().getConversation(conversationId) + .then(conversation => { + // update conversation name and patch extra_context + conversation.renameConversation("Book a call"); + conversation.patchExtraContext({ + ...conversation.getExtraContext(), + "appointment": "requested" + }); + // This should be an automatic response in the chat + const msg = this.tr("Your request has been sent.
Our support team will get back to you."); + osparc.FlashMessenger.logAs(msg, "INFO"); + }); + }) + .catch(err => { + console.error("Error sending request call message", err); + }); + }, } }); diff --git a/services/static-webserver/client/source/class/osparc/ui/form/DateTimeField.js b/services/static-webserver/client/source/class/osparc/ui/form/DateTimeField.js new file mode 100644 index 000000000000..300a6d212b9e --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/ui/form/DateTimeField.js @@ -0,0 +1,134 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.ui.form.DateTimeField", { + extend: qx.ui.core.Widget, + include: [qx.ui.form.MForm], + implement: [qx.ui.form.IForm, qx.ui.form.IStringForm], + + construct: function() { + this.base(arguments); + + this._setLayout(new qx.ui.layout.HBox(5)); + + this.set({ + maxHeight: 26 + }); + + // Date selector + this.__dateField = new qx.ui.form.DateField(); + const dateFormat = new qx.util.format.DateFormat("dd/MM/yyyy"); + this.__dateField.setDateFormat(dateFormat); + this._add(this.__dateField); + + // Hour selector + this.__hourSpinner = new qx.ui.form.Spinner(0, 12, 23); + this._add(this.__hourSpinner); + + // Minute selector + this.__minuteSpinner = new qx.ui.form.Spinner(0, 0, 59); + this._add(this.__minuteSpinner); + + const now = new Date(); + this.setValue(now); + + // Sync changes back to value + this.__dateField.addListener("changeValue", this.__updateValue, this); + this.__hourSpinner.addListener("changeValue", this.__updateValue, this); + this.__minuteSpinner.addListener("changeValue", this.__updateValue, this); + }, + + properties: { + // The combined Date value + value: { + check: "Date", + nullable: true, + event: "changeValue", + apply: "_applyValue" + } + }, + + members: { + __dateField: null, + __hourSpinner: null, + __minuteSpinner: null, + + _applyValue: function(value, old) { + if (value) { + this.__dateField.setValue(value); + this.__hourSpinner.setValue(value.getHours()); + this.__minuteSpinner.setValue(value.getMinutes()); + } else { + this.__dateField.resetValue(); + this.__hourSpinner.resetValue(); + this.__minuteSpinner.resetValue(); + } + }, + + __updateValue: function() { + const date = this.__dateField.getValue(); + const now = new Date(); + + if (date) { + // Prevent past dates + if (date < now.setHours(0,0,0,0)) { + this.__dateField.setValue(new Date()); + return; + } + + const newDate = new Date(date.getTime()); + newDate.setHours(this.__hourSpinner.getValue()); + newDate.setMinutes(this.__minuteSpinner.getValue()); + + // If today, prevent past time + const isToday = + date.getFullYear() === now.getFullYear() && + date.getMonth() === now.getMonth() && + date.getDate() === now.getDate(); + + if (isToday && newDate < now) { + this.__hourSpinner.setValue(now.getHours()); + this.__minuteSpinner.setValue(now.getMinutes()); + this.setValue(now); + } else { + this.setValue(newDate); + } + } else { + this.resetValue(); + } + }, + + // Interface methods (IStringForm) + setValueAsString: function(str) { + const d = new Date(str); + if (!isNaN(d.getTime())) { + this.setValue(d); + } + }, + + getValueAsString: function() { + const v = this.getValue(); + return v ? v.toISOString() : ""; + } + }, + + destruct: function() { + this.__dateField = null; + this.__hourSpinner = null; + this.__minuteSpinner = null; + } +}); diff --git a/services/static-webserver/client/source/class/osparc/ui/form/IntlTelInput.js b/services/static-webserver/client/source/class/osparc/ui/form/IntlTelInput.js index 621c0583561f..d4519ff9e1be 100644 --- a/services/static-webserver/client/source/class/osparc/ui/form/IntlTelInput.js +++ b/services/static-webserver/client/source/class/osparc/ui/form/IntlTelInput.js @@ -5,7 +5,7 @@ https://osparc.io Copyright: - 2022 IT'IS Foundation, https://itis.swiss + 2025 IT'IS Foundation, https://itis.swiss License: MIT: https://opensource.org/licenses/MIT diff --git a/services/static-webserver/client/source/class/osparc/ui/message/FlashMessageOEC.js b/services/static-webserver/client/source/class/osparc/ui/message/FlashMessageOEC.js index a2227e9274d0..30d59bb878ff 100644 --- a/services/static-webserver/client/source/class/osparc/ui/message/FlashMessageOEC.js +++ b/services/static-webserver/client/source/class/osparc/ui/message/FlashMessageOEC.js @@ -117,14 +117,8 @@ qx.Class.define("osparc.ui.message.FlashMessageOEC", { supportCenter.openConversation(null); const textToAddMessageField = msg => { - if ( - supportCenter.getChildControl("conversation-page") && - supportCenter.getChildControl("conversation-page").getChildControl("conversation-content") && - supportCenter.getChildControl("conversation-page").getChildControl("conversation-content").getChildControl("add-message") && - supportCenter.getChildControl("conversation-page").getChildControl("conversation-content").getChildControl("add-message").getChildControl("comment-field") - ) { - supportCenter.getChildControl("conversation-page").getChildControl("conversation-content").getChildControl("add-message").getChildControl("comment-field").setText(msg); - supportCenter.getChildControl("conversation-page").getChildControl("conversation-content").getChildControl("add-message").addComment(); + if (supportCenter.getChildControl("conversation-page")) { + supportCenter.getChildControl("conversation-page").postMessage(msg); } } @@ -149,6 +143,9 @@ qx.Class.define("osparc.ui.message.FlashMessageOEC", { const friendlyContext = this.__getSupportFriendlyContext(); const text = "Dear Support Team,\n" + extraContext + "\n" + friendlyContext; textToAddMessageField(text); + // This should be an automatic response in the chat + const msg = this.tr("Thanks, your report has been sent.
Our support team will get back to you."); + osparc.FlashMessenger.logAs(msg, "INFO"); } else { supportCenter.close(); } diff --git a/services/static-webserver/client/source/class/osparc/utils/Utils.js b/services/static-webserver/client/source/class/osparc/utils/Utils.js index 905260ac17c3..471c076c542a 100644 --- a/services/static-webserver/client/source/class/osparc/utils/Utils.js +++ b/services/static-webserver/client/source/class/osparc/utils/Utils.js @@ -577,9 +577,9 @@ qx.Class.define("osparc.utils.Utils", { }, /** - * @param value {Date Object} Date Object + * @param date {Date Object} Date Object */ - formatDate: function(value) { + formatDate: function(date) { // create a date format like "Oct. 19, 11:31 AM" if it's this year const dateFormat = new qx.util.format.DateFormat( qx.locale.Date.getDateFormat("medium") @@ -592,20 +592,20 @@ qx.Class.define("osparc.utils.Utils", { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); - if (today.toDateString() === value.toDateString()) { + if (today.toDateString() === date.toDateString()) { dateStr = qx.locale.Manager.tr("Today"); - } else if (yesterday.toDateString() === value.toDateString()) { + } else if (yesterday.toDateString() === date.toDateString()) { dateStr = qx.locale.Manager.tr("Yesterday"); - } else if (tomorrow.toDateString() === value.toDateString()) { + } else if (tomorrow.toDateString() === date.toDateString()) { dateStr = qx.locale.Manager.tr("Tomorrow"); } else { const currentYear = today.getFullYear(); - if (value.getFullYear() === currentYear) { + if (date.getFullYear() === currentYear) { // Remove the year if it's the current year const shortDateFormat = new qx.util.format.DateFormat("MMM d"); - dateStr = shortDateFormat.format(value); + dateStr = shortDateFormat.format(date); } else { - dateStr = dateFormat.format(value); + dateStr = dateFormat.format(date); } } return dateStr; @@ -618,21 +618,54 @@ qx.Class.define("osparc.utils.Utils", { }, /** - * @param value {Date Object} Date Object + * @param date {Date Object} Date Object */ - formatTime: function(value, long = false) { + formatTime: function(date, long = false) { const timeFormat = new qx.util.format.DateFormat( qx.locale.Date.getTimeFormat(long ? "long" : "short") ); - const timeStr = timeFormat.format(value); + const timeStr = timeFormat.format(date); return timeStr; }, /** - * @param value {Date Object} Date Object + * @param date {Date Object} Date Object */ - formatDateAndTime: function(value) { - return osparc.utils.Utils.formatDate(value) + " " + osparc.utils.Utils.formatTime(value); + formatDateAndTime: function(date) { + return osparc.utils.Utils.formatDate(date) + " " + osparc.utils.Utils.formatTime(date); + }, + + /** + * @param {Date} date - The date to format. + * @returns {String} - The formatted date string with city name and timezone. Sep 4, 1986, 17:00 Zurich (GMT+02:00) + */ + formatDateWithCityAndTZ: function(date) { + // Short date/time formatter + const options = { + year: "numeric", // 1986 + month: "short", // Sep + day: "numeric", // 4 + hour: "numeric", // 9 + minute: "2-digit", + hour12: false, // 24h format + }; + + const dtf = new Intl.DateTimeFormat("en-US", options); + const formatted = dtf.format(date); + + // Timezone city + const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + const city = tz.split("/").pop().replace("_", " "); + + // UTC offset (minutes → +HH:MM) + const offsetMinutes = -date.getTimezoneOffset(); // JS returns opposite sign + const sign = offsetMinutes >= 0 ? "+" : "-"; + const absMinutes = Math.abs(offsetMinutes); + const hours = String(Math.floor(absMinutes / 60)).padStart(2, "0"); + const minutes = String(absMinutes % 60).padStart(2, "0"); + const offsetStr = `GMT${sign}${hours}:${minutes}`; + + return `${formatted} ${city} (${offsetStr})`; }, formatMsToHHMMSS: function(ms) { diff --git a/services/static-webserver/client/source/class/osparc/widget/DateTimeChooser.js b/services/static-webserver/client/source/class/osparc/widget/DateTimeChooser.js new file mode 100644 index 000000000000..ac5b86607572 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/widget/DateTimeChooser.js @@ -0,0 +1,111 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + + +qx.Class.define("osparc.widget.DateTimeChooser", { + extend: osparc.ui.window.Window, + + construct: function(winTitle, value) { + this.base(arguments, winTitle || this.tr("Choose a Date and Time")); + + const width = 260; + const height = 26; + this.set({ + layout: new qx.ui.layout.VBox(10), + autoDestroy: true, + modal: true, + width, + height, + showMaximize: false, + showMinimize: false, + showClose: true, + resizable: false, + clickAwayClose: false, + }); + + const dateTimeField = this.getChildControl("date-time-field"); + if (value) { + dateTimeField.setValue(value); + } + this.getChildControl("cancel-button"); + this.getChildControl("save-button"); + + this.center(); + + this.__attachEventHandlers(); + }, + + events: { + "dateChanged": "qx.event.type.Data", + }, + + members: { + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "date-time-field": + control = new osparc.ui.form.DateTimeField(); + this.add(control); + break; + case "buttons-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5).set({ + alignX: "right" + })); + this.add(control); + break; + case "cancel-button": + control = new qx.ui.form.Button(this.tr("Cancel")).set({ + appearance: "form-button-text", + }); + control.addListener("execute", () => this.close(), this); + this.getChildControl("buttons-layout").add(control); + break; + case "save-button": { + control = new qx.ui.form.Button(this.tr("Save")).set({ + appearance: "form-button", + }); + control.addListener("execute", e => { + const dateTimeField = this.getChildControl("date-time-field"); + const data = { + newValue: dateTimeField.getValue() + }; + this.fireDataEvent("dateChanged", data); + }, this); + this.getChildControl("buttons-layout").add(control); + break; + } + } + return control || this.base(arguments, id); + }, + + __attachEventHandlers: function() { + let command = new qx.ui.command.Command("Enter"); + command.addListener("execute", () => { + this.getChildControl("save-button").execute(); + command.dispose(); + command = null; + }); + + let commandEsc = new qx.ui.command.Command("Esc"); + commandEsc.addListener("execute", () => { + this.close(); + commandEsc.dispose(); + commandEsc = null; + }); + } + } +});