From cb74ff72e21e4489470b6288cd5954c4ce98ac6f Mon Sep 17 00:00:00 2001 From: Odei Maiz <33152403+odeimaiz@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:02:17 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20[Frontend]=20Enh:=20Tag=20manage?= =?UTF-8?q?ment=20(#6720)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/source/class/osparc/NewRelease.js | 20 +- .../source/class/osparc/NewUITracker.js | 33 ++-- .../source/class/osparc/dashboard/CardBase.js | 2 +- .../class/osparc/dashboard/Dashboard.js | 3 - .../class/osparc/dashboard/GridButtonItem.js | 2 +- .../class/osparc/dashboard/ListButtonItem.js | 2 +- .../dashboard/ResourceContainerManager.js | 10 +- .../class/osparc/dashboard/ResourceFilter.js | 13 +- .../class/osparc/dashboard/SearchBarFilter.js | 17 +- .../source/class/osparc/data/model/Tag.js | 86 +++++++++ .../source/class/osparc/desktop/MainPage.js | 2 +- .../class/osparc/desktop/MainPageDesktop.js | 2 +- .../desktop/preferences/pages/TagsPage.js | 11 +- .../class/osparc/filter/UserTagsFilter.js | 6 +- .../source/class/osparc/form/tag/TagItem.js | 181 ++++++++++-------- .../class/osparc/form/tag/TagManager.js | 15 +- .../class/osparc/form/tag/TagToggleButton.js | 8 +- .../source/class/osparc/info/StudyUtils.js | 4 +- .../osparc/notification/NotificationUI.js | 12 +- .../notification/NotificationsContainer.js | 7 +- .../source/class/osparc/store/Folders.js | 3 +- .../source/class/osparc/store/Services.js | 6 +- .../client/source/class/osparc/store/Tags.js | 132 +++++++++++++ .../source/class/osparc/ui/basic/Tag.js | 16 +- 24 files changed, 427 insertions(+), 166 deletions(-) create mode 100644 services/static-webserver/client/source/class/osparc/data/model/Tag.js create mode 100644 services/static-webserver/client/source/class/osparc/store/Tags.js diff --git a/services/static-webserver/client/source/class/osparc/NewRelease.js b/services/static-webserver/client/source/class/osparc/NewRelease.js index af6c23f34eb..bac9d1efb25 100644 --- a/services/static-webserver/client/source/class/osparc/NewRelease.js +++ b/services/static-webserver/client/source/class/osparc/NewRelease.js @@ -44,13 +44,19 @@ qx.Class.define("osparc.NewRelease", { /** * Compare the latest version provided by the backend with the one loaded in the browser (might be an old cached one) */ - isMyFrontendOld: async function() { - const lastUICommit = await osparc.store.AppSummary.getLatestUIFromBE(); - const thisUICommit = osparc.utils.LibVersions.getVcsRefUI(); - if (lastUICommit && thisUICommit) { - return lastUICommit !== thisUICommit; - } - return false; + isMyFrontendOld: function() { + return new Promise((resolve, reject) => { + osparc.store.AppSummary.getLatestUIFromBE() + .then(lastUICommit => { + const thisUICommit = osparc.utils.LibVersions.getVcsRefUI(); + if (lastUICommit && thisUICommit) { + resolve(lastUICommit !== thisUICommit) + } else { + reject(); + } + }) + .catch(() => reject()); + }); } }, diff --git a/services/static-webserver/client/source/class/osparc/NewUITracker.js b/services/static-webserver/client/source/class/osparc/NewUITracker.js index 04a19536128..c85fb3f9390 100644 --- a/services/static-webserver/client/source/class/osparc/NewUITracker.js +++ b/services/static-webserver/client/source/class/osparc/NewUITracker.js @@ -27,21 +27,24 @@ qx.Class.define("osparc.NewUITracker", { __checkInterval: null, startTracker: function() { - const checkNewUI = async () => { - const newReleaseAvailable = await osparc.NewRelease.isMyFrontendOld(); - if (newReleaseAvailable) { - let msg = ""; - msg += qx.locale.Manager.tr("A new version of the application is now available."); - msg += "
"; - msg += qx.locale.Manager.tr("Click the Reload button to get the latest features."); - // permanent message - const flashMessage = osparc.FlashMessenger.getInstance().logAs(msg, "INFO", 0).set({ - maxWidth: 500 - }); - const reloadButton = osparc.utils.Utils.reloadNoCacheButton(); - flashMessage.addWidget(reloadButton); - this.stopTracker(); - } + const checkNewUI = () => { + osparc.NewRelease.isMyFrontendOld() + .then(newReleaseAvailable => { + if (newReleaseAvailable) { + let msg = ""; + msg += qx.locale.Manager.tr("A new version of the application is now available."); + msg += "
"; + msg += qx.locale.Manager.tr("Click the Reload button to get the latest features."); + // permanent message + const flashMessage = osparc.FlashMessenger.getInstance().logAs(msg, "INFO", 0).set({ + maxWidth: 500 + }); + const reloadButton = osparc.utils.Utils.reloadNoCacheButton(); + flashMessage.addWidget(reloadButton); + this.stopTracker(); + } + }) + .catch(() => setTimeout(() => checkNewUI(), 5*1000)); }; checkNewUI(); this.__checkInterval = setInterval(checkNewUI, this.self().CHECK_INTERVAL); 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 8d59dee3728..1b7a8fe6e82 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js @@ -926,7 +926,7 @@ qx.Class.define("osparc.dashboard.CardBase", { }, _filterTags: function(tags) { - const checks = this.getTags().map(tag => tag.id); + const checks = this.getTags().map(tag => tag.getTagId()); return this.self().filterTags(checks, tags); }, diff --git a/services/static-webserver/client/source/class/osparc/dashboard/Dashboard.js b/services/static-webserver/client/source/class/osparc/dashboard/Dashboard.js index cc714440242..4a1420ade43 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/Dashboard.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/Dashboard.js @@ -181,9 +181,6 @@ qx.Class.define("osparc.dashboard.Dashboard", { const store = osparc.store.Store.getInstance(); preResourcePromises.push(store.getAllGroupsAndMembers()); preResourcePromises.push(osparc.store.Services.getServicesLatest(false)); - if (permissions.canDo("study.tag")) { - preResourcePromises.push(osparc.data.Resources.get("tags")); - } Promise.all(preResourcePromises) .then(() => { [ diff --git a/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js index 148a6b114bb..828a0c74ba7 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js @@ -262,7 +262,7 @@ qx.Class.define("osparc.dashboard.GridButtonItem", { tagsContainer.setVisibility(tags.length ? "visible" : "excluded"); tagsContainer.removeAll(); tags.forEach(tag => { - const tagUI = new osparc.ui.basic.Tag(tag.name, tag.color, "searchBarFilter"); + const tagUI = new osparc.ui.basic.Tag(tag, "searchBarFilter"); tagUI.set({ font: "text-12", toolTipText: this.tr("Click to filter by this Tag") diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ListButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/ListButtonItem.js index e89e03a0943..71f59b970df 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ListButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ListButtonItem.js @@ -237,7 +237,7 @@ qx.Class.define("osparc.dashboard.ListButtonItem", { const tagsContainer = this.getChildControl("tags"); tagsContainer.removeAll(); tags.forEach(tag => { - const tagUI = new osparc.ui.basic.Tag(tag.name, tag.color, "searchBarFilter"); + const tagUI = new osparc.ui.basic.Tag(tag, "searchBarFilter"); tagUI.set({ alignY: "middle", font: "text-12", diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js index b28b5d89a04..fa99ba050dd 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js @@ -208,7 +208,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { }, __createCard: function(resourceData) { - const tags = resourceData.tags ? osparc.store.Store.getInstance().getTags().filter(tag => resourceData.tags.includes(tag.id)) : []; + const tags = resourceData.tags ? osparc.store.Tags.getInstance().getTags().filter(tag => resourceData.tags.includes(tag.getTagId())) : []; const card = this.getMode() === "grid" ? new osparc.dashboard.GridButtonItem() : new osparc.dashboard.ListButtonItem(); card.set({ appearance: resourceData.type ? `pb-${resourceData.type}` : `pb-${resourceData.resourceType}`, @@ -434,7 +434,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { }, __groupByTags: function(cards, resourceData) { - const tags = resourceData.tags ? osparc.store.Store.getInstance().getTags().filter(tag => resourceData.tags.includes(tag.id)) : []; + const tags = resourceData.tags ? osparc.store.Tags.getInstance().getTags().filter(tag => resourceData.tags.includes(tag.getTagId())) : []; if (tags.length === 0) { let noGroupContainer = this.__getGroupContainer("no-group"); const card = this.__createCard(resourceData); @@ -443,9 +443,11 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { cards.push(card); } else { tags.forEach(tag => { - let groupContainer = this.__getGroupContainer(tag.id); + let groupContainer = this.__getGroupContainer(tag.getTagId()); if (groupContainer === null) { - groupContainer = this.__createGroupContainer(tag.id, tag.name, tag.color); + groupContainer = this.__createGroupContainer(tag.getTagId(), tag.getName(), tag.getColor()); + tag.bind("name", groupContainer, "headerLabel"); + tag.bind("color", groupContainer, "headerColor"); groupContainer.setHeaderIcon("@FontAwesome5Solid/tag/24"); this.__groupedContainers.add(groupContainer); this.__groupedContainers.getChildren().sort((a, b) => a.getHeaderLabel().localeCompare(b.getHeaderLabel())); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js index 142cdab7d3f..0c452e3e33a 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js @@ -158,16 +158,15 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { const maxTags = 5; this.__tagButtons = []; layout.removeAll(); - osparc.store.Store.getInstance().getTags().forEach((tag, idx) => { - const button = new qx.ui.form.ToggleButton(tag.name, "@FontAwesome5Solid/tag/18"); + osparc.store.Tags.getInstance().getTags().forEach((tag, idx) => { + const button = new qx.ui.form.ToggleButton(null, "@FontAwesome5Solid/tag/18"); + button.id = tag.getTagId(); + tag.bind("name", button, "label"); + tag.bind("color", button.getChildControl("icon"), "textColor"); osparc.utils.Utils.setIdToWidget(button, this.__resourceType + "-tagFilterItem"); - button.id = tag.id; button.set({ appearance: "filter-toggle-button", - value: selectedTagIds.includes(tag.id) - }); - button.getChildControl("icon").set({ - textColor: tag.color + value: selectedTagIds.includes(tag.getTagId()) }); layout.add(button); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilter.js b/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilter.js index b836a93ef44..5b376a6b404 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilter.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilter.js @@ -208,14 +208,14 @@ qx.Class.define("osparc.dashboard.SearchBarFilter", { }, __addTags: function(menuButton) { - const tags = osparc.store.Store.getInstance().getTags(); + const tags = osparc.store.Tags.getInstance().getTags(); menuButton.setVisibility(tags.length ? "visible" : "excluded"); if (tags.length) { const tagsMenu = new qx.ui.menu.Menu(); osparc.utils.Utils.setIdToWidget(tagsMenu, "searchBarFilter-tags-menu"); tags.forEach(tag => { - const tagButton = new qx.ui.menu.Button(tag.name, "@FontAwesome5Solid/tag/12"); - tagButton.getChildControl("icon").setTextColor(tag.color); + const tagButton = new qx.ui.menu.Button(tag.getName(), "@FontAwesome5Solid/tag/12"); + tagButton.getChildControl("icon").setTextColor(tag.getColor()); tagsMenu.add(tagButton); tagButton.addListener("execute", () => this.addTagActiveFilter(tag), this); }); @@ -271,16 +271,17 @@ qx.Class.define("osparc.dashboard.SearchBarFilter", { }, addTagActiveFilter: function(tag) { - this.__addChip("tag", tag.id, tag.name); + this.__addChip("tag", tag.getTagId(), tag.getName()); }, setTagsActiveFilter: function(tagIds) { - const tags = osparc.store.Store.getInstance().getTags(); + const tags = osparc.store.Tags.getInstance().getTags(); tags.forEach(tag => { - if (tagIds.includes(tag.id)) { - this.__addChip("tag", tag.id, tag.name); + const tagId = tag.getTagId(); + if (tagIds.includes(tagId)) { + this.__addChip("tag", tagId, tag.getName()); } else { - this.__removeChip("tag", tag.id, tag.name); + this.__removeChip("tag", tagId, tag.getName()); } }); }, diff --git a/services/static-webserver/client/source/class/osparc/data/model/Tag.js b/services/static-webserver/client/source/class/osparc/data/model/Tag.js new file mode 100644 index 00000000000..fc7e00a5fcc --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/data/model/Tag.js @@ -0,0 +1,86 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +/** + * Class that stores Tag data. + */ + +qx.Class.define("osparc.data.model.Tag", { + extend: qx.core.Object, + + /** + * @param tagData {Object} Object containing the serialized Tag Data + */ + construct: function(tagData) { + this.base(arguments); + + this.set({ + tagId: tagData.id, + name: tagData.name, + description: tagData.description, + color: tagData.color, + accessRights: tagData.accessRights, + }); + }, + + properties: { + tagId: { + check: "Number", + nullable: true, + init: null, + event: "changeTagId" + }, + + name: { + check: "String", + nullable: false, + init: null, + event: "changeName" + }, + + description: { + check: "String", + nullable: true, + init: null, + event: "changeDescription" + }, + + color: { + check: "Color", + event: "changeColor", + init: "#303030" + }, + + accessRights: { + check: "Object", + nullable: false, + init: null, + event: "changeAccessRights" + }, + }, + + members: { + serialize: function() { + const jsonObject = {}; + const propertyKeys = this.self().getProperties(); + propertyKeys.forEach(key => { + jsonObject[key] = this.get(key); + }); + return jsonObject; + } + } +}); 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 d2b72acfdcc..0ccb9bbe8b9 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/MainPage.js +++ b/services/static-webserver/client/source/class/osparc/desktop/MainPage.js @@ -66,7 +66,7 @@ qx.Class.define("osparc.desktop.MainPage", { preloadPromises.push(store.reloadWallets()); } preloadPromises.push(store.getAllClassifiers(true)); - preloadPromises.push(store.getTags()); + preloadPromises.push(osparc.store.Tags.getInstance().fetchTags()); Promise.all(preloadPromises) .then(() => { const mainStack = this.__createMainStack(); diff --git a/services/static-webserver/client/source/class/osparc/desktop/MainPageDesktop.js b/services/static-webserver/client/source/class/osparc/desktop/MainPageDesktop.js index 93f5f50c74d..40c99616a40 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/MainPageDesktop.js +++ b/services/static-webserver/client/source/class/osparc/desktop/MainPageDesktop.js @@ -61,7 +61,7 @@ qx.Class.define("osparc.desktop.MainPageDesktop", { preloadPromises.push(store.reloadWallets()); } preloadPromises.push(store.getAllClassifiers(true)); - preloadPromises.push(store.getTags()); + preloadPromises.push(osparc.store.Tags.getInstance().fetchTags()); Promise.all(preloadPromises) .then(() => { const desktopCenter = new osparc.desktop.credits.DesktopCenter(); diff --git a/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/TagsPage.js b/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/TagsPage.js index 7265c65cebd..add2f2f3040 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/TagsPage.js +++ b/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/TagsPage.js @@ -48,13 +48,10 @@ qx.Class.define("osparc.desktop.preferences.pages.TagsPage", { icon: "@FontAwesome5Solid/plus/14" }); osparc.utils.Utils.setIdToWidget(this.__addTagButton, "addTagBtn"); - osparc.data.Resources.get("tags") - .then(tags => { - this.__tagItems = tags.map(tag => new osparc.form.tag.TagItem().set({...tag})); - this.__renderLayout(); - this.__attachEventHandlers(); - }) - .catch(err => console.error(err)); + const tags = osparc.store.Tags.getInstance().getTags(); + this.__tagItems = tags.map(tag => new osparc.form.tag.TagItem().set({tag})); + this.__renderLayout(); + this.__attachEventHandlers(); }, __renderLayout: function() { diff --git a/services/static-webserver/client/source/class/osparc/filter/UserTagsFilter.js b/services/static-webserver/client/source/class/osparc/filter/UserTagsFilter.js index c0a74265e01..caf5914e5d3 100644 --- a/services/static-webserver/client/source/class/osparc/filter/UserTagsFilter.js +++ b/services/static-webserver/client/source/class/osparc/filter/UserTagsFilter.js @@ -18,11 +18,11 @@ qx.Class.define("osparc.filter.UserTagsFilter", { }, members: { __buildMenu: function() { - osparc.store.Store.getInstance().getTags() + osparc.store.Tags.getInstance().getTags() .forEach(tag => { - const menuButton = this._addOption(tag.name); + const menuButton = this._addOption(tag.getName()); menuButton.setIcon("@FontAwesome5Solid/square/12"); - menuButton.getChildControl("icon").setTextColor(tag.color); + menuButton.getChildControl("icon").setTextColor(tag.getColor()); }); }, __attachEventListeners: function(filterId, filterGroupId) { diff --git a/services/static-webserver/client/source/class/osparc/form/tag/TagItem.js b/services/static-webserver/client/source/class/osparc/form/tag/TagItem.js index 7e79bb54bf3..77282a5db7f 100644 --- a/services/static-webserver/client/source/class/osparc/form/tag/TagItem.js +++ b/services/static-webserver/client/source/class/osparc/form/tag/TagItem.js @@ -26,37 +26,51 @@ qx.Class.define("osparc.form.tag.TagItem", { }, properties: { + tag: { + check: "osparc.data.model.Tag", + nullable: false, + init: null, + event: "changeTag", + apply: "__applyTag", + }, + id: { check: "Integer" }, + name: { check: "String", event: "changeName", init: "" }, + description: { check: "String", nullable: true, event: "changeDescription", init: "" }, + color: { check: "Color", event: "changeColor", init: "#303030" }, + accessRights: { check: "Object", nullable: false, + event: "changeAccessRights", apply: "__renderLayout", - event: "changeAccessRights" }, + mode: { check: "String", init: "display", nullable: false, apply: "_applyMode" }, + appearance: { init: "tagitem", refine: true @@ -78,57 +92,7 @@ qx.Class.define("osparc.form.tag.TagItem", { __colorButton: null, __loadingIcon: null, __validationManager: null, - /** - * Renders this tag item from scratch. - */ - __renderLayout: function() { - this._removeAll(); - if (this.getMode() === this.self().modes.EDIT) { - this.__renderEditMode(); - } else if (this.getMode() === this.self().modes.DISPLAY) { - this.__renderDisplayMode(); - } - }, - __renderEditMode: function() { - const nameContainer = new qx.ui.container.Composite(new qx.ui.layout.VBox()).set({ - width: 90 - }); - nameContainer.add(new qx.ui.basic.Label(this.tr("Name")).set({ - buddy: this.getChildControl("nameinput") - })); - nameContainer.add(this.getChildControl("nameinput").set({ - value: this.getName() - })); - this._add(nameContainer); - const descInputContainer = new qx.ui.container.Composite(new qx.ui.layout.VBox()); - descInputContainer.add(new qx.ui.basic.Label(this.tr("Description")).set({ - buddy: this.getChildControl("descriptioninput") - })); - descInputContainer.add(this.getChildControl("descriptioninput").set({ - value: this.getDescription() - })); - this._add(descInputContainer, { - flex: 1 - }); - this._add(this.__colorPicker()); - this._add(this.__tagItemEditButtons()); - }, - __renderDisplayMode: function() { - const tagContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox()).set({ - width: 100 - }); - tagContainer.add(this.getChildControl("tag")); - this._add(tagContainer); - const descriptionContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox()); - descriptionContainer.add(this.getChildControl("description"), { - width: "100%" - }); - this._add(descriptionContainer, { - flex: 1 - }); - this._add(this.__tagItemButtons()); - this.resetBackgroundColor(); - }, + _createChildControlImpl: function(id) { let control; switch (id) { @@ -151,7 +115,7 @@ qx.Class.define("osparc.form.tag.TagItem", { } control = this.__description; break; - case "nameinput": + case "name-input": // Tag name input in edit mode if (this.__nameInput === null) { this.__nameInput = new qx.ui.form.TextField().set({ @@ -162,7 +126,7 @@ qx.Class.define("osparc.form.tag.TagItem", { } control = this.__nameInput; break; - case "descriptioninput": + case "description-input": // Tag description input in edit mode if (this.__descriptionInput === null) { this.__descriptionInput = new qx.ui.form.TextArea().set({ @@ -172,7 +136,7 @@ qx.Class.define("osparc.form.tag.TagItem", { } control = this.__descriptionInput; break; - case "colorinput": + case "color-input": // Color input in edit mode if (this.__colorInput === null) { this.__colorInput = new qx.ui.form.TextField().set({ @@ -180,20 +144,20 @@ qx.Class.define("osparc.form.tag.TagItem", { width: 60, required: true }); - this.__colorInput.bind("value", this.getChildControl("colorbutton"), "backgroundColor"); - this.__colorInput.bind("value", this.getChildControl("colorbutton"), "textColor", { + this.__colorInput.bind("value", this.getChildControl("color-button"), "backgroundColor"); + this.__colorInput.bind("value", this.getChildControl("color-button"), "textColor", { converter: value => osparc.utils.Utils.getContrastedBinaryColor(value) }); this.__validationManager.add(this.__colorInput, osparc.utils.Validators.hexColor); } control = this.__colorInput; break; - case "colorbutton": + case "color-button": // Random color generator button in edit mode if (this.__colorButton === null) { this.__colorButton = new qx.ui.form.Button(null, "@FontAwesome5Solid/sync-alt/12"); this.__colorButton.addListener("execute", () => { - this.getChildControl("colorinput").setValue(osparc.utils.Utils.getRandomColor()); + this.getChildControl("color-input").setValue(osparc.utils.Utils.getRandomColor()); }, this); } control = this.__colorButton; @@ -201,6 +165,69 @@ qx.Class.define("osparc.form.tag.TagItem", { } return control || this.base(arguments, id); }, + + __applyTag: function(tag) { + tag.bind("tagId", this, "id"); + tag.bind("name", this, "name"); + tag.bind("description", this, "description"); + tag.bind("color", this, "color"); + tag.bind("accessRights", this, "accessRights"); + }, + + /** + * Renders this tag item from scratch. + */ + __renderLayout: function() { + this._removeAll(); + if (this.getMode() === this.self().modes.EDIT) { + this.__renderEditMode(); + } else if (this.getMode() === this.self().modes.DISPLAY) { + this.__renderDisplayMode(); + } + }, + + __renderEditMode: function() { + const nameContainer = new qx.ui.container.Composite(new qx.ui.layout.VBox()).set({ + width: 90 + }); + nameContainer.add(new qx.ui.basic.Label(this.tr("Name")).set({ + buddy: this.getChildControl("name-input") + })); + nameContainer.add(this.getChildControl("name-input").set({ + value: this.getName() + })); + this._add(nameContainer); + const descInputContainer = new qx.ui.container.Composite(new qx.ui.layout.VBox()); + descInputContainer.add(new qx.ui.basic.Label(this.tr("Description")).set({ + buddy: this.getChildControl("description-input") + })); + descInputContainer.add(this.getChildControl("description-input").set({ + value: this.getDescription() + })); + this._add(descInputContainer, { + flex: 1 + }); + this._add(this.__colorPicker()); + this._add(this.__tagItemEditButtons()); + }, + + __renderDisplayMode: function() { + const tagContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox()).set({ + width: 100 + }); + tagContainer.add(this.getChildControl("tag")); + this._add(tagContainer); + const descriptionContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox()); + descriptionContainer.add(this.getChildControl("description"), { + width: "100%" + }); + this._add(descriptionContainer, { + flex: 1 + }); + this._add(this.__tagItemButtons()); + this.resetBackgroundColor(); + }, + /** * Generates and returns the buttons for deleting and editing an existing label (display mode) */ @@ -224,12 +251,7 @@ qx.Class.define("osparc.form.tag.TagItem", { editButton.addListener("execute", () => this.setMode(this.self().modes.EDIT), this); deleteButton.addListener("execute", () => { deleteButton.setFetching(true); - const params = { - url: { - tagId: this.getId() - } - }; - osparc.data.Resources.fetch("tags", "delete", params) + osparc.store.Tags.getInstance().deleteTag(this.getId()) .then(() => this.fireEvent("deleteTag")) .catch(console.error) .finally(() => deleteButton.setFetching(false)); @@ -256,21 +278,15 @@ qx.Class.define("osparc.form.tag.TagItem", { saveButton.addListener("execute", () => { if (this.__validationManager.validate()) { const data = this.__serializeData(); - const params = { - data - }; saveButton.setFetching(true); let fetch; if (this.isPropertyInitialized("id")) { - params.url = { - tagId: this.getId() - }; - fetch = osparc.data.Resources.fetch("tags", "put", params); + fetch = osparc.store.Tags.getInstance().putTag(this.getId(), data); } else { - fetch = osparc.data.Resources.fetch("tags", "post", params); + fetch = osparc.store.Tags.getInstance().postTag(data); } fetch - .then(tag => this.set(tag)) + .then(tag => this.setTag(tag)) .catch(console.error) .finally(() => { this.fireEvent("tagSaved"); @@ -295,24 +311,27 @@ qx.Class.define("osparc.form.tag.TagItem", { __colorPicker: function() { const container = new qx.ui.container.Composite(new qx.ui.layout.VBox()); container.add(new qx.ui.basic.Label(this.tr("Color")).set({ - buddy: this.getChildControl("colorinput") + buddy: this.getChildControl("color-input") })); const innerContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox()); - const refreshButton = this.getChildControl("colorbutton"); - const colorInput = this.getChildControl("colorinput"); + const refreshButton = this.getChildControl("color-button"); + const colorInput = this.getChildControl("color-input"); innerContainer.add(refreshButton); innerContainer.add(colorInput); container.add(innerContainer); return container; }, /** - * Creates an object containing the udpated tag info + * Creates an object containing the updated tag info */ __serializeData: function() { + const name = this.getChildControl("name-input").getValue(); + const description = this.getChildControl("description-input").getValue(); + const color = this.getChildControl("color-input").getValue(); return { - name: this.getChildControl("nameinput").getValue().trim(), - description: this.getChildControl("descriptioninput").getValue().trim(), - color: this.getChildControl("colorinput").getValue() + name: name.trim(), + description: description ? description.trim() : "", + color: color }; }, _applyMode: function() { diff --git a/services/static-webserver/client/source/class/osparc/form/tag/TagManager.js b/services/static-webserver/client/source/class/osparc/form/tag/TagManager.js index ae3ef918adb..6f704c1f222 100644 --- a/services/static-webserver/client/source/class/osparc/form/tag/TagManager.js +++ b/services/static-webserver/client/source/class/osparc/form/tag/TagManager.js @@ -88,8 +88,8 @@ qx.Class.define("osparc.form.tag.TagManager", { 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.__repopulateTags(); }); this._add(addTagButton); @@ -119,25 +119,26 @@ qx.Class.define("osparc.form.tag.TagManager", { __repopulateTags: function() { this.__tagsContainer.removeAll(); - const tags = osparc.store.Store.getInstance().getTags(); + const tags = osparc.store.Tags.getInstance().getTags(); tags.forEach(tag => this.__tagsContainer.add(this.__tagButton(tag))); }, __tagButton: function(tag) { - const tagButton = new osparc.form.tag.TagToggleButton(tag, this.__selectedTags.includes(tag.id)); + const tagId = tag.getTagId(); + const tagButton = new osparc.form.tag.TagToggleButton(tag, this.__selectedTags.includes(tagId)); tagButton.addListener("changeValue", evt => { const selected = evt.getData(); if (this.isLiveUpdate()) { tagButton.setFetching(true); if (selected) { - this.__saveAddTag(tag.id, tagButton); + this.__saveAddTag(tagId, tagButton); } else { - this.__saveRemoveTag(tag.id, tagButton); + this.__saveRemoveTag(tagId, tagButton); } } else if (selected) { - this.__selectedTags.push(tag.id); + this.__selectedTags.push(tagId); } else { - this.__selectedTags.remove(tag.id); + this.__selectedTags.remove(tagId); } }, this); tagButton.subscribeToFilterGroup("studyBrowserTagManager"); diff --git a/services/static-webserver/client/source/class/osparc/form/tag/TagToggleButton.js b/services/static-webserver/client/source/class/osparc/form/tag/TagToggleButton.js index 3075d738cf3..35feee0c3bc 100644 --- a/services/static-webserver/client/source/class/osparc/form/tag/TagToggleButton.js +++ b/services/static-webserver/client/source/class/osparc/form/tag/TagToggleButton.js @@ -23,11 +23,11 @@ qx.Class.define("osparc.form.tag.TagToggleButton", { appearance: "tagbutton" }); this.setIcon("@FontAwesome5Solid/square/14"); - this.getChildControl("icon").setTextColor(tag.color); - if (tag.description) { - this.setLabel(tag.name + " : " + tag.description); + this.getChildControl("icon").setTextColor(tag.getColor()); + if (tag.getDescription()) { + this.setLabel(tag.getName() + " : " + tag.getDescription()); } else { - this.setLabel(tag.name); + this.setLabel(tag.getName()); } this.getChildControl("check"); diff --git a/services/static-webserver/client/source/class/osparc/info/StudyUtils.js b/services/static-webserver/client/source/class/osparc/info/StudyUtils.js index 95ea7f20b7f..f1d2c3449e5 100644 --- a/services/static-webserver/client/source/class/osparc/info/StudyUtils.js +++ b/services/static-webserver/client/source/class/osparc/info/StudyUtils.js @@ -211,12 +211,12 @@ qx.Class.define("osparc.info.StudyUtils", { tagsContainer.removeAll(); const noTagsLabel = new qx.ui.basic.Label(qx.locale.Manager.tr("Add tags")); tagsContainer.add(noTagsLabel); - osparc.store.Store.getInstance().getTags().filter(tag => model.getTags().includes(tag.id)) + osparc.store.Tags.getInstance().getTags().filter(tag => model.getTags().includes(tag.getTagId())) .forEach(selectedTag => { if (tagsContainer.indexOf(noTagsLabel) > -1) { tagsContainer.remove(noTagsLabel); } - tagsContainer.add(new osparc.ui.basic.Tag(selectedTag.name, selectedTag.color)); + tagsContainer.add(new osparc.ui.basic.Tag(selectedTag)); }); }; study.addListener("changeTags", () => addTags(study), this); diff --git a/services/static-webserver/client/source/class/osparc/notification/NotificationUI.js b/services/static-webserver/client/source/class/osparc/notification/NotificationUI.js index da49db7f0a4..67194c84418 100644 --- a/services/static-webserver/client/source/class/osparc/notification/NotificationUI.js +++ b/services/static-webserver/client/source/class/osparc/notification/NotificationUI.js @@ -22,6 +22,7 @@ qx.Class.define("osparc.notification.NotificationUI", { this.base(arguments); this.set({ + margin: 4, maxWidth: this.self().MAX_WIDTH, padding: this.self().PADDING, cursor: "pointer" @@ -216,9 +217,14 @@ qx.Class.define("osparc.notification.NotificationUI", { } }); - notification.bind("read", this, "backgroundColor", { - converter: read => read ? "background-main-3" : "background-main-4" - }); + const highlight = mouseOn => { + this.set({ + backgroundColor: mouseOn ? "strong-main" : "transparent" + }) + }; + this.addListener("mouseover", () => highlight(true)); + this.addListener("mouseout", () => highlight(false)); + highlight(false); }, __notificationTapped: function() { diff --git a/services/static-webserver/client/source/class/osparc/notification/NotificationsContainer.js b/services/static-webserver/client/source/class/osparc/notification/NotificationsContainer.js index 34757474f64..c59a8a94a4c 100644 --- a/services/static-webserver/client/source/class/osparc/notification/NotificationsContainer.js +++ b/services/static-webserver/client/source/class/osparc/notification/NotificationsContainer.js @@ -27,9 +27,14 @@ qx.Class.define("osparc.notification.NotificationsContainer", { zIndex: osparc.utils.Utils.FLOATING_Z_INDEX, maxWidth: osparc.notification.NotificationUI.MAX_WIDTH, maxHeight: 250, - backgroundColor: "background-main-3", + backgroundColor: "background-main", decorator: "rounded", }); + let color = qx.theme.manager.Color.getInstance().resolve("text"); + color = qx.util.ColorUtil.stringToRgb(color); + color.push(0.3); // add transparency + color = qx.util.ColorUtil.rgbToRgbString(color); + osparc.utils.Utils.addBorder(this, 1, color); osparc.utils.Utils.setIdToWidget(this, "notificationsContainer"); const root = qx.core.Init.getApplication().getRoot(); diff --git a/services/static-webserver/client/source/class/osparc/store/Folders.js b/services/static-webserver/client/source/class/osparc/store/Folders.js index 7deb66618bb..d6e83d8fb23 100644 --- a/services/static-webserver/client/source/class/osparc/store/Folders.js +++ b/services/static-webserver/client/source/class/osparc/store/Folders.js @@ -172,6 +172,7 @@ qx.Class.define("osparc.store.Folders", { __addToCache: function(folderData) { let folder = this.foldersCached.find(f => f.getFolderId() === folderData["folderId"] && f.getWorkspaceId() === folderData["workspaceId"]); if (folder) { + const props = Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.Folder)); // put Object.keys(folderData).forEach(key => { if (key === "createdAt") { @@ -180,7 +181,7 @@ qx.Class.define("osparc.store.Folders", { folder.set("lastModified", new Date(folderData["modifiedAt"])); } else if (key === "trashedAt") { folder.set("trashedAt", new Date(folderData["trashedAt"])); - } else { + } else if (props.includes(key)) { folder.set(key, folderData[key]); } }); diff --git a/services/static-webserver/client/source/class/osparc/store/Services.js b/services/static-webserver/client/source/class/osparc/store/Services.js index f6851b3aa43..c2abeed32ec 100644 --- a/services/static-webserver/client/source/class/osparc/store/Services.js +++ b/services/static-webserver/client/source/class/osparc/store/Services.js @@ -44,7 +44,11 @@ qx.Class.define("osparc.store.Services", { resolve(servicesObj); }) - .catch(err => console.error("getServices failed", err)); + .catch(err => { + const msg = err.message || qx.locale.Manager.tr("Unable to fetch Services"); + osparc.FlashMessenger.getInstance().logAs(msg, "ERROR"); + console.error(err); + }); }); }, diff --git a/services/static-webserver/client/source/class/osparc/store/Tags.js b/services/static-webserver/client/source/class/osparc/store/Tags.js new file mode 100644 index 00000000000..4ffd9f5cd4f --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/store/Tags.js @@ -0,0 +1,132 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.store.Tags", { + extend: qx.core.Object, + type: "singleton", + + construct: function() { + this.base(arguments); + + this.tagsCached = []; + }, + + events: { + "tagAdded": "qx.event.type.Data", + "tagRemoved": "qx.event.type.Data", + }, + + members: { + tagsCached: null, + + fetchTags: function() { + if (osparc.auth.Data.getInstance().isGuest()) { + return new Promise(resolve => { + resolve([]); + }); + } + + return osparc.data.Resources.get("tags") + .then(tagsData => { + const tags = []; + tagsData.forEach(tagData => { + const tag = this.__addToCache(tagData); + tags.push(tag); + }); + return tags; + }); + }, + + getTags: function() { + return this.tagsCached; + }, + + postTag: function(newTagData) { + const params = { + data: newTagData + }; + return osparc.data.Resources.getInstance().fetch("tags", "post", params) + .then(tagData => { + const tag = this.__addToCache(tagData); + this.fireDataEvent("tagAdded", tag); + return tag; + }); + }, + + deleteTag: function(tagId) { + const params = { + url: { + tagId + } + }; + return osparc.data.Resources.getInstance().fetch("tags", "delete", params) + .then(() => { + const tag = this.getTag(tagId); + if (tag) { + this.__deleteFromCache(tagId); + this.fireDataEvent("tagRemoved", tag); + } + }) + .catch(console.error); + }, + + putTag: function(tagId, updateData) { + const params = { + url: { + tagId + }, + data: updateData + }; + return osparc.data.Resources.getInstance().fetch("tags", "put", params) + .then(tagData => { + return this.__addToCache(tagData); + }) + .catch(console.error); + }, + + getTag: function(tagId = null) { + return this.tagsCached.find(f => f.getTagId() === tagId); + }, + + __addToCache: function(tagData) { + let tag = this.tagsCached.find(f => f.getTagId() === tagData["id"]); + if (tag) { + const props = Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.Tag)); + // put + Object.keys(tagData).forEach(key => { + if (props.includes(key)) { + tag.set(key, tagData[key]); + } + }); + } else { + // get and post + tag = new osparc.data.model.Tag(tagData); + this.tagsCached.unshift(tag); + } + return tag; + }, + + __deleteFromCache: function(tagId) { + const idx = this.tagsCached.findIndex(f => f.getTagId() === tagId); + if (idx > -1) { + this.tagsCached.splice(idx, 1); + return true; + } + return false; + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/ui/basic/Tag.js b/services/static-webserver/client/source/class/osparc/ui/basic/Tag.js index 4b23fc0efde..64930674e25 100644 --- a/services/static-webserver/client/source/class/osparc/ui/basic/Tag.js +++ b/services/static-webserver/client/source/class/osparc/ui/basic/Tag.js @@ -13,17 +13,19 @@ qx.Class.define("osparc.ui.basic.Tag", { extend: qx.ui.basic.Label, /** * Constructor for the Tag element. - * @param {String} value Short text to be shown on the tag - * @param {String} color Color for the background, must be in hex3 or hex6 form + * @param {osparc.data.model.Tag} tag Short text to be shown on the tag * @param {String} [filterGroupId] If present, clicking on the tab will dispatch a bus message with the * id ``GroupIdTagsTrigger`` to be subscribed by a filter. */ - construct: function(value, color, filterGroupId) { - this.base(arguments, value); - this.setFont("text-11"); - if (color) { - this.setColor(color); + construct: function(tag, filterGroupId) { + this.base(arguments); + + if (tag) { + tag.bind("name", this, "value"); + tag.bind("color", this, "color"); } + this.setFont("text-11"); + if (filterGroupId) { this.setCursor("pointer"); this.addListener("tap", e => {