diff --git a/services/static-webserver/client/source/class/osparc/MaintenanceTracker.js b/services/static-webserver/client/source/class/osparc/MaintenanceTracker.js index 91e0186006a..ad4177b8d96 100644 --- a/services/static-webserver/client/source/class/osparc/MaintenanceTracker.js +++ b/services/static-webserver/client/source/class/osparc/MaintenanceTracker.js @@ -41,7 +41,7 @@ qx.Class.define("osparc.MaintenanceTracker", { statics: { CHECK_INTERVAL: 15*60*1000, // Check every 15' - CLOSABLE_WARN_IN_ADVANCE: 12*60*60*1000, // Show Ribbon Closable Message 12h in advance + CLOSABLE_WARN_IN_ADVANCE: 24*60*60*1000, // Show Ribbon Closable Message 24h in advance PERMANENT_WARN_IN_ADVANCE: 30*60*1000 // Show Ribbon Permament Message 30' in advance }, diff --git a/services/static-webserver/client/source/class/osparc/component/form/tag/TagItem.js b/services/static-webserver/client/source/class/osparc/component/form/tag/TagItem.js index b5d323a29ea..34df12efffc 100644 --- a/services/static-webserver/client/source/class/osparc/component/form/tag/TagItem.js +++ b/services/static-webserver/client/source/class/osparc/component/form/tag/TagItem.js @@ -10,18 +10,21 @@ */ qx.Class.define("osparc.component.form.tag.TagItem", { extend: qx.ui.core.Widget, + construct: function() { this.base(arguments); this._setLayout(new qx.ui.layout.HBox(5)); this.__validationManager = new qx.ui.form.validation.Manager(); this.__renderLayout(); }, + statics: { modes: { DISPLAY: "display", EDIT: "edit" } }, + properties: { id: { check: "Integer" @@ -59,10 +62,13 @@ qx.Class.define("osparc.component.form.tag.TagItem", { refine: true } }, + events: { + "tagSaved": "qx.event.type.Event", "cancelNewTag": "qx.event.type.Event", "deleteTag": "qx.event.type.Event" }, + members: { __tag: null, __description: null, @@ -236,10 +242,14 @@ qx.Class.define("osparc.component.form.tag.TagItem", { __tagItemEditButtons: function() { const buttonContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox()); const saveButton = new osparc.ui.form.FetchButton(null, "@FontAwesome5Solid/check/12").set({ - appearance: "link-button" + appearance: "link-button", + paddingTop: 15, // avoid buddy text + alignY: "middle" }); const cancelButton = new qx.ui.form.Button(null, "@FontAwesome5Solid/times/12").set({ - appearance: "link-button" + appearance: "link-button", + paddingTop: 15, // avoid buddy text + alignY: "middle" }); buttonContainer.add(saveButton); buttonContainer.add(cancelButton); @@ -263,6 +273,7 @@ qx.Class.define("osparc.component.form.tag.TagItem", { .then(tag => this.set(tag)) .catch(console.error) .finally(() => { + this.fireEvent("tagSaved"); this.setMode(this.self().modes.DISPLAY); saveButton.setFetching(false); }); diff --git a/services/static-webserver/client/source/class/osparc/component/form/tag/TagManager.js b/services/static-webserver/client/source/class/osparc/component/form/tag/TagManager.js index 939fdc16999..7c85b6f4e47 100644 --- a/services/static-webserver/client/source/class/osparc/component/form/tag/TagManager.js +++ b/services/static-webserver/client/source/class/osparc/component/form/tag/TagManager.js @@ -10,30 +10,18 @@ * Tag manager server to manage one resource's related tags. */ qx.Class.define("osparc.component.form.tag.TagManager", { - extend: osparc.ui.window.SingletonWindow, - construct: function(studyData, attachment, resourceName, resourceId) { - this.base(arguments, "tagManager", this.tr("Apply Tags")); - this.set({ - layout: new qx.ui.layout.VBox(), - allowMinimize: false, - allowMaximize: false, - showMinimize: false, - showMaximize: false, - autoDestroy: true, - movable: false, - resizable: false, - modal: true, - width: 262, - clickAwayClose: true - }); - this.__attachment = attachment; - this.__resourceName = resourceName; - this.__resourceId = resourceId; - this.__studyData = studyData; - this.__selectedTags = new qx.data.Array(studyData["tags"]); + extend: qx.ui.core.Widget, + + construct: function(studyData) { + this.base(arguments); + + this._setLayout(new qx.ui.layout.VBox()); + + this.__selectedTags = new qx.data.Array(); this.__renderLayout(); this.__attachEventHandlers(); - this.open(); + + this.setStudydata(studyData); }, events: { @@ -45,79 +33,94 @@ qx.Class.define("osparc.component.form.tag.TagManager", { liveUpdate: { check: "Boolean", event: "changeLiveUpdate", - init: true + init: false + } + }, + + statics: { + popUpInWindow: function(tagManager, title) { + if (!title) { + title = qx.locale.Manager.tr("Apply Tags"); + } + return osparc.ui.window.Window.popUpInWindow(tagManager, title, 280, null).set({ + allowMinimize: false, + allowMaximize: false, + showMinimize: false, + showMaximize: false, + clickAwayClose: true, + movable: true, + resizable: true, + showClose: true + }); } }, members: { __studyData: null, - __attachment: null, - __resourceName: null, __resourceId: null, __selectedTags: null, + __tagsContainer: null, + __addTagButton: null, __renderLayout: function() { const filter = new osparc.component.filter.TextFilter("name", "studyBrowserTagManager").set({ allowStretchX: true, margin: [0, 10, 5, 10] }); - this.add(filter); + this._add(filter); - const buttonContainer = new qx.ui.container.Composite(new qx.ui.layout.VBox()); - this.add(buttonContainer, { + const tagsContainer = this.__tagsContainer = new qx.ui.container.Composite(new qx.ui.layout.VBox()); + this._add(tagsContainer, { flex: 1 }); - osparc.store.Store.getInstance().getTags().forEach(tag => buttonContainer.add(this.__tagButton(tag))); - if (buttonContainer.getChildren().length === 0) { - buttonContainer.add(new qx.ui.basic.Label().set({ - value: this.tr("Add your first tag in Preferences/Tags"), - font: "title-16", - textColor: "service-window-hint", - rich: true, - padding: 10, - textAlign: "center" - })); - } + + const addTagButton = this.__addTagButton = new qx.ui.form.Button().set({ + appearance: "strong-button", + label: this.tr("New Tag"), + icon: "@FontAwesome5Solid/plus/14", + alignX: "center", + allowGrowX: false + }); + addTagButton.addListener("execute", () => { + const newItem = new osparc.component.form.tag.TagItem().set({ + mode: osparc.component.form.tag.TagItem.modes.EDIT + }); + newItem.addListener("tagSaved", () => this.__repopulateTags(), this); + newItem.addListener("cancelNewTag", e => tagsContainer.remove(e.getTarget()), this); + newItem.addListener("deleteTag", e => tagsContainer.remove(e.getTarget()), this); + this.__repopulateTags(); + tagsContainer.add(newItem); + }); + this._add(addTagButton); const buttons = new qx.ui.container.Composite(new qx.ui.layout.HBox().set({ alignX: "right" })); const saveButton = new osparc.ui.form.FetchButton(this.tr("Save")); + saveButton.set({ + appearance: "strong-button" + }); osparc.utils.Utils.setIdToWidget(saveButton, "saveTagsBtn"); - saveButton.addListener("execute", e => { - this.__save(saveButton); - }, this); + saveButton.addListener("execute", () => this.__save(saveButton), this); buttons.add(saveButton); this.bind("liveUpdate", buttons, "visibility", { converter: value => value ? "excluded" : "visible" }); - this.add(buttons); + this._add(buttons); }, - /** - * If the attachment (element close to which the TagManager is being rendered) is already on the DOM, - * this function calculates where the TagManager should render, taking into account the window edges. - */ - __updatePosition: function() { - if (this.__attachment && this.__attachment.getContentElement().getDomElement()) { - const location = qx.bom.element.Location.get(this.__attachment.getContentElement().getDomElement()); - const freeDistances = osparc.utils.Utils.getFreeDistanceToWindowEdges(this.__attachment); - let position = { - top: location.bottom, - left: location.right - }; - if (this.getWidth() > freeDistances.right) { - position.left = location.left - this.getWidth(); - if (this.getHeight() > freeDistances.bottom) { - position.top = location.top - (this.getHeight() || this.getSizeHint().height); - } - } else if (this.getHeight() > freeDistances.bottom) { - position.top = location.top - this.getHeight(); - } - this.moveTo(position.left, position.top); - } else { - this.center(); - } + setStudydata: function(studyData) { + this.__studyData = studyData; + this.__resourceId = studyData["uuid"]; + this.__selectedTags.removeAll(); + this.__selectedTags.append(studyData["tags"]); + this.__repopulateTags(); + }, + + __repopulateTags: function() { + this.__tagsContainer.removeAll(); + const tags = osparc.store.Store.getInstance().getTags(); + tags.forEach(tag => this.__tagsContainer.add(this.__tagButton(tag))); }, __tagButton: function(tag) { @@ -208,9 +211,6 @@ qx.Class.define("osparc.component.form.tag.TagManager", { }, __attachEventHandlers: function() { - this.addListener("appear", () => { - this.__updatePosition(); - }); this.__selectedTags.addListener("change", evt => { this.fireDataEvent("changeSelected", { ...evt.getData(), diff --git a/services/static-webserver/client/source/class/osparc/component/task/TasksButton.js b/services/static-webserver/client/source/class/osparc/component/task/TasksButton.js index fc5cc894740..71f7ba111ec 100644 --- a/services/static-webserver/client/source/class/osparc/component/task/TasksButton.js +++ b/services/static-webserver/client/source/class/osparc/component/task/TasksButton.js @@ -55,11 +55,15 @@ qx.Class.define("osparc.component.task.TasksButton", { } case "number": control = new qx.ui.basic.Label().set({ + backgroundColor: "background-main-1", font: "text-12" }); + control.getContentElement().setStyles({ + "border-radius": "4px" + }); this._add(control, { - bottom: 3, - right: 0 + bottom: 8, + right: 4 }); break; } diff --git a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js index 0f147d3a6ea..36a90b41337 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js @@ -628,6 +628,11 @@ qx.Class.define("osparc.dashboard.CardBase", { moreOpts.openAccessRights(); }, + openTags: function() { + const moreOpts = this.__openMoreOptions(); + moreOpts.openTags(); + }, + __openQualityEditor: function() { const moreOpts = this.__openMoreOptions(); moreOpts.openQuality(); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js index 0c002d32537..9786a1030ad 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js @@ -348,6 +348,18 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { const shareButton = new qx.ui.menu.Button(this.tr("Share...")); shareButton.addListener("tap", () => card.openAccessRights(), this); return shareButton; + }, + + _getTagsMenuButton: function(card) { + const resourceData = card.getResourceData(); + const isCurrentUserOwner = osparc.data.model.Study.canIWrite(resourceData["accessRights"]); + if (!isCurrentUserOwner) { + return null; + } + + const tagsButton = new qx.ui.menu.Button(this.tr("Tags...")); + tagsButton.addListener("tap", () => card.openTags(), this); + return tagsButton; } } }); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceMoreOptions.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceMoreOptions.js index a0b92a80d98..2d077584299 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceMoreOptions.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceMoreOptions.js @@ -62,6 +62,7 @@ qx.Class.define("osparc.dashboard.ResourceMoreOptions", { __serviceVersionLayout: null, __serviceVersionSelector: null, __permissionsPage: null, + __tagsPage: null, __classifiersPage: null, __qualityPage: null, __servicesUpdatePage: null, @@ -76,6 +77,10 @@ qx.Class.define("osparc.dashboard.ResourceMoreOptions", { this.__openPage(this.__permissionsPage); }, + openTags: function() { + this.__openPage(this.__tagsPage); + }, + openClassifiers: function() { this.__openPage(this.__classifiersPage); }, @@ -150,6 +155,7 @@ qx.Class.define("osparc.dashboard.ResourceMoreOptions", { this.__getPermissionsPage, this.__getClassifiersPage, this.__getQualityPage, + this.__getTagsPage, this.__getServicesUpdatePage, this.__getServicesBootOptionsPage, this.__getSaveAsTemplatePage @@ -219,6 +225,7 @@ qx.Class.define("osparc.dashboard.ResourceMoreOptions", { infoCard.addListener("openAccessRights", () => this.openAccessRights()); infoCard.addListener("openClassifiers", () => this.openClassifiers()); infoCard.addListener("openQuality", () => this.openQuality()); + infoCard.addListener("openTags", () => this.openTags()); infoCard.addListener("updateStudy", e => { const updatedData = e.getData(); if (osparc.utils.Resources.isStudy(resourceData)) { @@ -373,6 +380,28 @@ qx.Class.define("osparc.dashboard.ResourceMoreOptions", { return null; }, + __getTagsPage: function() { + const id = "Tags"; + const resourceData = this.__resourceData; + if (osparc.utils.Resources.isTemplate(resourceData) && !osparc.data.model.Study.canIWrite(resourceData["accessRights"])) { + return null; + } + if (osparc.utils.Resources.isService(resourceData) && !osparc.utils.Services.canIWrite(resourceData["accessRights"])) { + return null; + } + + const title = this.tr("Tags"); + const icon = "@FontAwesome5Solid/tags"; + const tagManager = new osparc.component.form.tag.TagManager(resourceData); + tagManager.addListener("updateTags", e => { + const updatedData = e.getData(); + tagManager.setStudydata(updatedData); + this.fireDataEvent("updateStudy", updatedData); + }, this); + const page = this.__tagsPage = this.__createPage(title, tagManager, icon, id); + return page; + }, + __getServicesUpdatePage: function() { const id = "ServicesUpdate"; const resourceData = this.__resourceData; diff --git a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js index feaaf95237a..645e39d4a31 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -739,6 +739,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { if (shareButton) { menu.add(shareButton); } + + const tagsButton = this._getTagsMenuButton(card); + if (tagsButton) { + menu.add(tagsButton); + } } const duplicateStudyButton = this.__getDuplicateMenuButton(studyData); @@ -875,7 +880,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { "studyId": studyData["uuid"] } }; - const fetchPromise = osparc.data.Resources.fetch("studies", "duplicate", params); + const fetchPromise = osparc.data.Resources.fetch("studies", "duplicate", params, null, {"pollTask": true}); const interval = 1000; const pollTasks = osparc.data.PollTasks.getInstance(); pollTasks.createPollingTask(fetchPromise, interval) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/TemplateBrowser.js b/services/static-webserver/client/source/class/osparc/dashboard/TemplateBrowser.js index 31d8f7fa9f7..cc3dbafd852 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/TemplateBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/TemplateBrowser.js @@ -272,6 +272,11 @@ qx.Class.define("osparc.dashboard.TemplateBrowser", { menu.add(shareButton); } + const tagsButton = this._getTagsMenuButton(card); + if (tagsButton) { + menu.add(tagsButton); + } + const moreInfoButton = this._getMoreOptionsMenuButton(studyData); if (moreInfoButton) { menu.add(moreInfoButton); 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 52a48844968..6be3eb7aacc 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -818,6 +818,8 @@ qx.Class.define("osparc.data.Resources", { } if (endpoint.includes("delete")) { this.__removeCached(resource, deleteId); + } else if (useCache && endpointDef.method === "POST" && options.pollTask !== true) { + this.__addCached(resource, data); } else if (useCache && endpointDef.method === "GET") { if (endpoint.includes("getPage")) { this.__addCached(resource, data); @@ -970,7 +972,7 @@ qx.Class.define("osparc.data.Resources", { }, /** - * Stores the cached version of a resource, or a collection of them. + * Add the given data to the cached version of a resource, or a collection of them. * @param {String} resource Name of the resource as defined in the static property 'resources'. * @param {*} data Resource or collection of resources to be addded to the cache. */ diff --git a/services/static-webserver/client/source/class/osparc/desktop/MainPage.js b/services/static-webserver/client/source/class/osparc/desktop/MainPage.js index ece8399f54c..c049d255912 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/MainPage.js +++ b/services/static-webserver/client/source/class/osparc/desktop/MainPage.js @@ -242,7 +242,7 @@ qx.Class.define("osparc.desktop.MainPage", { }, data: data["studyData"] }; - const fetchPromise = osparc.data.Resources.fetch("studies", "postToTemplate", params); + const fetchPromise = osparc.data.Resources.fetch("studies", "postToTemplate", params, null, {"pollTask": true}); const pollTasks = osparc.data.PollTasks.getInstance(); const interval = 1000; pollTasks.createPollingTask(fetchPromise, interval) diff --git a/services/static-webserver/client/source/class/osparc/info/CardLarge.js b/services/static-webserver/client/source/class/osparc/info/CardLarge.js index a476124de75..c87f6739554 100644 --- a/services/static-webserver/client/source/class/osparc/info/CardLarge.js +++ b/services/static-webserver/client/source/class/osparc/info/CardLarge.js @@ -33,7 +33,8 @@ qx.Class.define("osparc.info.CardLarge", { events: { "openAccessRights": "qx.event.type.Event", "openClassifiers": "qx.event.type.Event", - "openQuality": "qx.event.type.Event" + "openQuality": "qx.event.type.Event", + "openTags": "qx.event.type.Event" }, properties: { diff --git a/services/static-webserver/client/source/class/osparc/info/MergedLarge.js b/services/static-webserver/client/source/class/osparc/info/MergedLarge.js index deb5bcc4e8e..3e5c43c3105 100644 --- a/services/static-webserver/client/source/class/osparc/info/MergedLarge.js +++ b/services/static-webserver/client/source/class/osparc/info/MergedLarge.js @@ -352,11 +352,10 @@ qx.Class.define("osparc.info.MergedLarge", { }, __openTagsEditor: function() { - const tagManager = new osparc.component.form.tag.TagManager(this.getStudy().serialize(), null, "study", this.getStudy().getUuid()).set({ - liveUpdate: false - }); + const tagManager = new osparc.component.form.tag.TagManager(this.getStudy().serialize()); + const win = osparc.component.form.tag.TagManager.popUpInWindow(tagManager); tagManager.addListener("updateTags", e => { - tagManager.close(); + win.close(); const updatedData = e.getData(); this.getStudy().setTags(updatedData["tags"]); this.fireDataEvent("updateStudy", updatedData); diff --git a/services/static-webserver/client/source/class/osparc/info/StudyLarge.js b/services/static-webserver/client/source/class/osparc/info/StudyLarge.js index 03045234fe2..1e183e5cd33 100644 --- a/services/static-webserver/client/source/class/osparc/info/StudyLarge.js +++ b/services/static-webserver/client/source/class/osparc/info/StudyLarge.js @@ -102,10 +102,13 @@ qx.Class.define("osparc.info.StudyLarge", { if (this.getStudy().getTags().length || this.__canIWrite()) { const tags = this.__createTags(); - const editInTitle = this.__createViewWithEdit(tags.getChildren()[0], this.__openTagsEditor); + const editInTitle = this.__createViewWithEdit(tags.getChildren()[0], null); tags.addAt(editInTitle, 0); if (this.__canIWrite()) { - osparc.utils.Utils.setIdToWidget(editInTitle.getChildren()[1], "editStudyEditTagsBtn"); + const editButton = editInTitle.getChildren()[1]; + editButton.setIcon("@FontAwesome5Solid/eye/12"); + editButton.addListener("execute", () => this.fireEvent("openTags"), this); + osparc.utils.Utils.setIdToWidget(editButton, "editStudyEditTagsBtn"); } this._add(tags); } @@ -125,7 +128,9 @@ qx.Class.define("osparc.info.StudyLarge", { layout.add(view); if (this.__canIWrite()) { const editBtn = osparc.utils.Utils.getEditButton(); - editBtn.addListener("execute", () => cb.call(this), this); + if (cb) { + editBtn.addListener("execute", () => cb.call(this), this); + } layout.add(editBtn); } @@ -307,11 +312,10 @@ qx.Class.define("osparc.info.StudyLarge", { }, __openTagsEditor: function() { - const tagManager = new osparc.component.form.tag.TagManager(this.getStudy().serialize(), null, "study", this.getStudy().getUuid()).set({ - liveUpdate: false - }); + const tagManager = new osparc.component.form.tag.TagManager(this.getStudy().serialize()); + const win = osparc.component.form.tag.TagManager.popUpInWindow(tagManager); tagManager.addListener("updateTags", e => { - tagManager.close(); + win.close(); const updatedData = e.getData(); this.getStudy().setTags(updatedData["tags"]); this.fireDataEvent("updateStudy", updatedData); diff --git a/services/static-webserver/client/source/class/osparc/store/Store.js b/services/static-webserver/client/source/class/osparc/store/Store.js index 2c78f7637a0..6e7f75e6d65 100644 --- a/services/static-webserver/client/source/class/osparc/store/Store.js +++ b/services/static-webserver/client/source/class/osparc/store/Store.js @@ -198,7 +198,7 @@ qx.Class.define("osparc.store.Store", { return; } const stored = this.get(resource); - if (Array.isArray(stored) && Array.isArray(data)) { + if (Array.isArray(stored)) { this.set(resource, stored.concat(data)); } else { this.set(resource, data); diff --git a/services/static-webserver/client/source/class/osparc/utils/Study.js b/services/static-webserver/client/source/class/osparc/utils/Study.js index 588425b5e15..8f646a9e972 100644 --- a/services/static-webserver/client/source/class/osparc/utils/Study.js +++ b/services/static-webserver/client/source/class/osparc/utils/Study.js @@ -188,7 +188,7 @@ qx.Class.define("osparc.utils.Study", { createStudyAndPoll: function(params) { return new Promise((resolve, reject) => { - const fetchPromise = osparc.data.Resources.fetch("studies", "postNewStudy", params); + const fetchPromise = osparc.data.Resources.fetch("studies", "postNewStudy", params, null, {"pollTask": true}); const pollTasks = osparc.data.PollTasks.getInstance(); const interval = 1000; pollTasks.createPollingTask(fetchPromise, interval) @@ -227,7 +227,7 @@ qx.Class.define("osparc.utils.Study", { }, data: minStudyData }; - const fetchPromise = osparc.data.Resources.fetch("studies", "postNewStudyFromTemplate", params); + const fetchPromise = osparc.data.Resources.fetch("studies", "postNewStudyFromTemplate", params, null, {"pollTask": true}); const pollTasks = osparc.data.PollTasks.getInstance(); const interval = 1000; pollTasks.createPollingTask(fetchPromise, interval)