diff --git a/services/static-webserver/client/source/class/osparc/auth/core/Utils.js b/services/static-webserver/client/source/class/osparc/auth/core/Utils.js index da013ab969f..e04140bdc1d 100644 --- a/services/static-webserver/client/source/class/osparc/auth/core/Utils.js +++ b/services/static-webserver/client/source/class/osparc/auth/core/Utils.js @@ -68,6 +68,12 @@ qx.Class.define("osparc.auth.core.Utils", { }; }, + checkEmail: function(emailValue) { + // Same expression used in qx.util.Validate.checkEmail + const reg = /^([A-Za-z0-9_\-.+])+@([A-Za-z0-9_\-.])+\.([A-Za-z]{2,})$/; + return reg.test(emailValue); + }, + /** Finds parameters in the fragment * * Expected fragment format as https://osparc.io#page=reset-password;code=123546 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 1ef6bb8d7cb..ef9783942b2 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -276,6 +276,10 @@ qx.Class.define("osparc.data.Resources", { method: "PUT", url: statics.API + "/projects/{studyId}/groups/{gId}" }, + shareWithEmail: { + method: "POST", + url: statics.API + "/projects/{studyId}:share" + }, addTag: { useCache: false, method: "POST", diff --git a/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js b/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js index 0008c5d7c48..d87c450c2de 100644 --- a/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js +++ b/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js @@ -51,6 +51,15 @@ qx.Class.define("osparc.filter.CollaboratorToggleButton", { this.getChildControl("check"); }, + properties: { + iconSrc: { + check: "String", + nullable: true, + init: "@FontAwesome5Solid/check/14", + event: "changeIconSrc" + }, + }, + members: { _createChildControlImpl: function(id) { let control; @@ -69,7 +78,8 @@ qx.Class.define("osparc.filter.CollaboratorToggleButton", { } break; case "check": - control = new qx.ui.basic.Image("@FontAwesome5Solid/check/14"); + control = new qx.ui.basic.Image(); + this.bind("iconSrc", control, "source"); control.setAnonymous(true); this._add(control); this.bind("value", control, "visibility", { diff --git a/services/static-webserver/client/source/class/osparc/share/AddCollaborators.js b/services/static-webserver/client/source/class/osparc/share/AddCollaborators.js index f59327843c8..8f21a4e1958 100644 --- a/services/static-webserver/client/source/class/osparc/share/AddCollaborators.js +++ b/services/static-webserver/client/source/class/osparc/share/AddCollaborators.js @@ -99,6 +99,19 @@ qx.Class.define("osparc.share.AddCollaborators", { collaboratorsManager.close(); this.fireDataEvent("addCollaborators", e.getData()); }, this); + if (this.__serializedDataCopy["resourceType"] === "study") { + collaboratorsManager.addListener("shareWithEmails", e => { + const { + selectedEmails, + newAccessRights, + message, + } = e.getData(); + collaboratorsManager.close(); + osparc.store.Study.sendShareEmails(this.__serializedDataCopy, selectedEmails, newAccessRights, message) + .then(() => osparc.FlashMessenger.logAs(this.tr("Emails sent"), "INFO")) + .catch(err => osparc.FlashMessenger.logError(err)); + }, this); + } }, this); const organizations = this.getChildControl("my-organizations"); diff --git a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js index 0db26b0dbf4..3ca3ea2918d 100644 --- a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js +++ b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js @@ -33,12 +33,23 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { this.__potentialCollaborators = {}; this.__reloadPotentialCollaborators(); + this.__shareWithEmailEnabled = false; + if (this.__resourceData["resourceType"] === "study") { + osparc.utils.DisabledPlugins.isShareWithEmailEnabled() + .then(isEnabled => { + if (isEnabled) { + this.__shareWithEmailEnabled = true; + } + }); + } + this.center(); this.open(); }, events: { - "addCollaborators": "qx.event.type.Data" + "addCollaborators": "qx.event.type.Data", + "shareWithEmails": "qx.event.type.Data", }, members: { @@ -65,6 +76,10 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { this.add(control); break; } + case "filter-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); + this.add(control); + break; case "text-filter": { control = new osparc.filter.TextFilter("name", "collaboratorsManager"); control.setCompact(true); @@ -72,7 +87,24 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { filterTextField.setPlaceholder(this.tr("Search")); filterTextField.setBackgroundColor("transparent"); this.addListener("appear", () => filterTextField.focus()); - this.add(control); + this.getChildControl("filter-layout").add(control, { + flex: 1 + }); + break; + } + case "send-email-button": { + control = new qx.ui.form.Button(this.tr("Send email")); + control.exclude(); + control.addListener("execute", () => { + const textField = this.getChildControl("text-filter").getChildControl("textfield"); + const email = textField.getValue(); + if (osparc.auth.core.Utils.checkEmail(email)) { + const invitedButton = this.__invitedButton(email); + this.getChildControl("potential-collaborators-list").addAt(invitedButton, 0); + invitedButton.setValue(true); + } + }); + this.getChildControl("filter-layout").add(control); break; } case "potential-collaborators-list": { @@ -143,11 +175,18 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { const textFilter = this.getChildControl("text-filter"); const filterTextField = textFilter.getChildControl("textfield"); filterTextField.addListener("input", e => { - const filterValue = e.getData(); + const inputValue = e.getData(); if (this.__searchDelayer) { clearTimeout(this.__searchDelayer); } - if (filterValue.length > 3) { + const sendEmailButton = this.getChildControl("send-email-button"); + sendEmailButton.exclude(); + if (inputValue.length > 3) { + if (this.__shareWithEmailEnabled) { + if (osparc.auth.core.Utils.checkEmail(inputValue)) { + sendEmailButton.show(); + } + } const waitBeforeSearching = 1000; this.__searchDelayer = setTimeout(() => { this.__searchUsers(); @@ -220,13 +259,10 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { this.__addPotentialCollaborators(); }, - __collaboratorButton: function(collaborator, preSelected = false) { + __collaboratorButton: function(collaborator) { const collaboratorButton = new osparc.filter.CollaboratorToggleButton(collaborator); collaboratorButton.groupId = collaborator.getGroupId(); - collaboratorButton.setValue(preSelected); - if (!preSelected) { - collaboratorButton.subscribeToFilterGroup("collaboratorsManager"); - } + collaboratorButton.subscribeToFilterGroup("collaboratorsManager"); collaboratorButton.addListener("changeValue", e => { const selected = e.getData(); @@ -242,6 +278,34 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { return collaboratorButton; }, + __invitedButton: function(email) { + if (email in this.__selectedCollaborators) { + return this.__selectedCollaborators[email]; + } + + const collaboratorData = { + label: email, + email: email, + description: null, + }; + const collaborator = qx.data.marshal.Json.createModel(collaboratorData); + const collaboratorButton = new osparc.filter.CollaboratorToggleButton(collaborator); + collaboratorButton.setIconSrc("@FontAwesome5Solid/envelope/14"); + + collaboratorButton.addListener("changeValue", e => { + const selected = e.getData(); + if (selected) { + this.__selectedCollaborators[collaborator.getEmail()] = collaborator; + collaboratorButton.unsubscribeToFilterGroup("collaboratorsManager"); + } else if (collaborator.getEmail() in this.__selectedCollaborators) { + delete this.__selectedCollaborators[collaborator.getEmail()]; + collaboratorButton.subscribeToFilterGroup("collaboratorsManager"); + } + this.getChildControl("share-button").setEnabled(Boolean(Object.keys(this.__selectedCollaborators).length)); + }, this); + return collaboratorButton; + }, + __addPotentialCollaborators: function(foundCollaborators = []) { const potentialCollaborators = Object.values(this.__potentialCollaborators).concat(foundCollaborators); const potentialCollaboratorList = this.getChildControl("potential-collaborators-list"); @@ -315,10 +379,47 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { } } if (Object.keys(this.__selectedCollaborators).length) { - this.fireDataEvent("addCollaborators", { - selectedGids: Object.keys(this.__selectedCollaborators), - newAccessRights, - }); + const selectedGIds = Object.keys(this.__selectedCollaborators).filter(key => qx.lang.Type.isNumber(key)); + const selectedEmails = Object.keys(this.__selectedCollaborators).filter(key => osparc.auth.core.Utils.checkEmail(key)); + + const addCollaborators = () => { + if (selectedGIds.length) { + this.fireDataEvent("addCollaborators", { + selectedGids: selectedGIds, + newAccessRights, + }); + } + }; + + const sendEmails = message => { + if (selectedEmails.length) { + this.fireDataEvent("shareWithEmails", { + selectedEmails, + newAccessRights, + message, + }); + } + }; + + if (selectedEmails.length) { + const dialog = new osparc.ui.window.Confirmation(); + dialog.setCaption(this.tr("Add Message")); + dialog.setMessage(this.tr("Add a message to include in the email (optional)")); + dialog.getConfirmButton().setLabel(this.tr("Send")); + const messageEditor = new qx.ui.form.TextArea().set({ + autoSize: true, + minHeight: 70, + maxHeight: 140, + }); + dialog.addWidget(messageEditor); + dialog.open(); + dialog.addListener("close", () => { + addCollaborators(); + sendEmails(messageEditor.getValue()); + }, this); + } else { + addCollaborators(); + } } } } diff --git a/services/static-webserver/client/source/class/osparc/store/Study.js b/services/static-webserver/client/source/class/osparc/store/Study.js index 9f57f5281ac..78a30233761 100644 --- a/services/static-webserver/client/source/class/osparc/store/Study.js +++ b/services/static-webserver/client/source/class/osparc/store/Study.js @@ -111,5 +111,24 @@ qx.Class.define("osparc.store.Study", { }) .catch(err => osparc.FlashMessenger.logError(err)); }, + + sendShareEmails: function(studyData, selectedEmails, newAccessRights, message) { + const promises = selectedEmails.map(selectedEmail => { + const params = { + url: { + "studyId": studyData["uuid"], + }, + data: { + shareeEmail: selectedEmail, + sharerMessage: message, + read: newAccessRights["read"], + write: newAccessRights["write"], + delete: newAccessRights["delete"], + } + }; + return osparc.data.Resources.fetch("studies", "shareWithEmail", params); + }); + return Promise.all(promises); + }, } }); diff --git a/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js b/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js index ccd68623f94..233ba211e05 100644 --- a/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js +++ b/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js @@ -29,6 +29,7 @@ qx.Class.define("osparc.utils.DisabledPlugins", { VERSION_CONTROL: "WEBSERVER_VERSION_CONTROL", META_MODELING: "WEBSERVER_META_MODELING", LICENSES: "WEBSERVER_LICENSES", + SHARE_WITH_EMAIL: "WEBSERVER_SHARE_WITH_EMAIL", isExportDisabled: function() { return this.__isPluginDisabled(this.EXPORT); @@ -52,6 +53,11 @@ qx.Class.define("osparc.utils.DisabledPlugins", { return this.__isPluginDisabled(this.LICENSES); }, + isShareWithEmailEnabled: function() { + // return !this.__isPluginDisabled(this.SHARE_WITH_EMAIL); + return new Promise(resolve => resolve(osparc.utils.Utils.isDevelopmentPlatform())); + }, + isJobsEnabled: function() { if (osparc.utils.Utils.isDevelopmentPlatform() && osparc.product.Utils.isProduct("s4lacad")) { return true;