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 6600b55d42a..781bce108d7 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilter.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilter.js @@ -26,9 +26,10 @@ qx.Class.define("osparc.dashboard.SearchBarFilter", { this._setLayout(new qx.ui.layout.HBox(5)); this.set({ - backgroundColor: "input_background", + backgroundColor: this.self().BG_COLOR, paddingLeft: 6, height: this.self().HEIGHT, + maxHeight: this.self().HEIGHT, decorator: "rounded", }); @@ -41,8 +42,17 @@ qx.Class.define("osparc.dashboard.SearchBarFilter", { this.__currentFilter = null; }, + properties: { + showFilterMenu: { + check: "Boolean", + init: true, + event: "changeShowFilterMenu", + } + }, + statics: { HEIGHT: 36, + BG_COLOR: "input_background", getSharedWithOptions: function(resourceType) { if (resourceType === "template") { @@ -70,7 +80,19 @@ qx.Class.define("osparc.dashboard.SearchBarFilter", { label: qx.locale.Manager.tr("Public") + " " + resourceAlias, icon: "@FontAwesome5Solid/globe/20" }]; - } + }, + + createChip: function(chipType, chipId, chipLabel) { + const chipButton = new qx.ui.form.Button().set({ + label: osparc.utils.Utils.capitalize(chipType) + " = '" + chipLabel + "'", + icon: "@MaterialIcons/close/12", + toolTipText: chipLabel, + appearance: "chip-button" + }); + chipButton.type = chipType; + chipButton.id = chipId; + return chipButton; + }, }, events: { @@ -100,7 +122,7 @@ qx.Class.define("osparc.dashboard.SearchBarFilter", { break; case "text-field": control = new qx.ui.form.TextField().set({ - backgroundColor: "input_background", + backgroundColor: this.self().BG_COLOR, font: "text-16", placeholder: this.tr("search"), alignY: "bottom", @@ -175,7 +197,7 @@ qx.Class.define("osparc.dashboard.SearchBarFilter", { this.__hideFilterMenu(); } }, this); - textField.addListener("changeValue", () => this.__filter(), this); + textField.addListener("focusout", () => this.__filter(), this); const resetButton = this.getChildControl("reset-button"); resetButton.addListener("execute", () => this.__resetFilters(), this); @@ -184,7 +206,7 @@ qx.Class.define("osparc.dashboard.SearchBarFilter", { }, getTextFilterValue: function() { - return this.getChildControl("text-field").getValue(); + return this.getChildControl("text-field").getValue() ? this.getChildControl("text-field").getValue().trim() : null; }, __showFilterMenu: function() { @@ -203,7 +225,9 @@ qx.Class.define("osparc.dashboard.SearchBarFilter", { left: left }); - this.__filtersMenu.show(); + if (this.getShowFilterMenu()) { + this.__filtersMenu.show(); + } }, __hideFilterMenu: function() { @@ -304,7 +328,6 @@ qx.Class.define("osparc.dashboard.SearchBarFilter", { }); }, - setSharedWithActiveFilter: function(optionId, optionLabel) { this.__removeChips("shared-with"); if (optionId === "show-all") { @@ -323,30 +346,34 @@ qx.Class.define("osparc.dashboard.SearchBarFilter", { } }, + // this widget pops up a larger widget with all filters visible + // and lets users search between projects, templates, public projects and, eventually, files + popUpSearchBarFilter: function() { + const initFilterData = this.getFilterData(); + const searchBarFilterExtended = new osparc.dashboard.SearchBarFilterExtended(this.__resourceType, initFilterData); + const bounds = osparc.utils.Utils.getBounds(this); + searchBarFilterExtended.setLayoutProperties({ + left: bounds.left, + top: bounds.top, + }); + searchBarFilterExtended.set({ + width: bounds.width, + }); + return searchBarFilterExtended; + }, + __addChip: function(type, id, label) { const activeFilter = this.getChildControl("active-filters"); const chipFound = activeFilter.getChildren().find(chip => chip.type === type && chip.id === id); if (chipFound) { return; } - const chip = this.__createChip(type, id, label); + const chip = this.self().createChip(type, id, label); + chip.addListener("execute", () => this.__removeChip(type, id), this); activeFilter.add(chip); this.__filter(); }, - __createChip: function(chipType, chipId, chipLabel) { - const chipButton = new qx.ui.form.Button().set({ - label: osparc.utils.Utils.capitalize(chipType) + " = '" + chipLabel + "'", - icon: "@MaterialIcons/close/12", - toolTipText: chipLabel, - appearance: "chip-button" - }); - chipButton.type = chipType; - chipButton.id = chipId; - chipButton.addListener("execute", () => this.__removeChip(chipType, chipId), this); - return chipButton; - }, - __removeChip: function(type, id) { const activeFilter = this.getChildControl("active-filters"); const chipFound = activeFilter.getChildren().find(chip => chip.type === type && chip.id === id); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilterExtended.js b/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilterExtended.js new file mode 100644 index 00000000000..b1af7fefa84 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilterExtended.js @@ -0,0 +1,304 @@ +/* ************************************************************************ + + 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.dashboard.SearchBarFilterExtended", { + extend: qx.ui.core.Widget, + + construct: function(resourceType, initFilterData = {}) { + this.base(arguments, "searchBarFilter-"+resourceType, "searchBarFilter"); + + this._setLayout(new qx.ui.layout.VBox(10)); + + this.set({ + backgroundColor: osparc.dashboard.SearchBarFilter.BG_COLOR, + padding: 8, + decorator: "rounded", + }); + osparc.utils.Utils.addBorder(this, 1, qx.theme.manager.Color.getInstance().resolve("product-color")); + + this.__resourceType = resourceType; + this.__initFilterData = initFilterData; + + this.__buildLayout(); + + this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS); + + qx.core.Init.getApplication().getRoot().add(this); + + this.__attachHideHandlers(); + }, + + events: { + "filterChanged": "qx.event.type.Data", + }, + + properties: { + currentContext: { + check: [ + "searchProjects", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS, + "searchTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES, + "searchPublicTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES, + ], + init: null, + nullable: false, + event: "changeCurrentContext", + apply: "__applyCurrentContext", + }, + }, + + statics: { + createToolbarRadioButton: function(label, icon, toolTipText = null, pos = null) { + const rButton = new qx.ui.toolbar.RadioButton().set({ + label, + icon, + toolTipText, + padding: 8, + gap: 8, + margin: 0, + }); + rButton.getContentElement().setStyles({ + "border-radius": "0px" + }); + if (pos === "left") { + osparc.utils.Utils.addBorderLeftRadius(rButton); + } else if (pos === "right") { + osparc.utils.Utils.addBorderRightRadius(rButton); + } + return rButton; + }, + }, + + members: { + __resourceType: null, + + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "search-bar-filter": { + control = new osparc.dashboard.SearchBarFilter(this.__resourceType); + const textField = control.getChildControl("text-field"); + textField.addListener("appear", () => { + textField.focus(); + textField.activate(); + }); + this._add(control); + break; + } + case "context-buttons": + control = new qx.ui.toolbar.ToolBar().set({ + spacing: 0, + padding: 0, + backgroundColor: osparc.dashboard.SearchBarFilter.BG_COLOR, + }); + this._add(control); + break; + case "my-projects-button": + control = this.self().createToolbarRadioButton( + this.tr("My Projects"), + "@FontAwesome5Solid/file/14", + null, + "left", + ); + this.getChildControl("context-buttons").add(control); + break; + case "templates-button": + control = this.self().createToolbarRadioButton( + this.tr("Templates"), + "@FontAwesome5Solid/copy/14", + ); + this.getChildControl("context-buttons").add(control); + break; + case "public-projects-button": + control = this.self().createToolbarRadioButton( + this.tr("Public Projects"), + "@FontAwesome5Solid/globe/14", + null, + "right", + ); + this.getChildControl("context-buttons").add(control); + break; + case "filter-buttons": + control = new qx.ui.toolbar.ToolBar().set({ + backgroundColor: osparc.dashboard.SearchBarFilter.BG_COLOR, + }); + this._add(control); + break; + case "shared-with-button": + control = new qx.ui.toolbar.MenuButton(this.tr("Shared with"), "@FontAwesome5Solid/share-alt/12"); + this.__addSharedWithMenu(control); + this.getChildControl("filter-buttons").add(control); + break; + case "tags-button": + control = new qx.ui.toolbar.MenuButton(this.tr("Tags"), "@FontAwesome5Solid/tags/12"); + this.__addTagsMenu(control); + this.getChildControl("filter-buttons").add(control); + break; + } + return control || this.base(arguments, id); + }, + + __buildLayout: function() { + this.getChildControl("search-bar-filter").set({ + showFilterMenu: false, + }); + + const resetButton = this.getChildControl("search-bar-filter").getChildControl("reset-button"); + resetButton.set({ + paddingRight: 2, // 10-8 + opacity: 0.7, + backgroundColor: "transparent", + }); + osparc.utils.Utils.hideBorder(resetButton); + + const radioGroup = new qx.ui.form.RadioGroup(); + const myProjectsButton = this.getChildControl("my-projects-button"); + const templatesButton = this.getChildControl("templates-button"); + const publicProjectsButton = this.getChildControl("public-projects-button"); + radioGroup.add(myProjectsButton, templatesButton, publicProjectsButton); + myProjectsButton.addListener("changeValue", e => e.getData() ? this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS) : null, this); + templatesButton.addListener("changeValue", e => e.getData() ? this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES) : null, this); + publicProjectsButton.addListener("changeValue", e => e.getData() ? this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES) : null, this); + + // Set initial state based on the provided initFilterData + const activeFilters = this.getChildControl("search-bar-filter").getChildControl("active-filters"); + const textField = this.getChildControl("search-bar-filter").getChildControl("text-field"); + if ("sharedWith" in this.__initFilterData && this.__initFilterData["sharedWith"]) { + const sharedWithOptions = osparc.dashboard.SearchBarFilter.getSharedWithOptions(this.__resourceType); + const optionsFound = sharedWithOptions.find(option => option.id === this.__initFilterData["sharedWith"]); + if (optionsFound) { + const chip = osparc.dashboard.SearchBarFilter.createChip("sharedWith", optionsFound.id, optionsFound.label); + activeFilters.add(chip); + } + } + if ("tags" in this.__initFilterData && this.__initFilterData["tags"]) { + const tags = osparc.store.Tags.getInstance().getTags(); + this.__initFilterData["tags"].forEach(tagId => { + const tagFound = tags.find(tag => tag.getTagId() === tagId); + if (tagFound) { + const chip = osparc.dashboard.SearchBarFilter.createChip("tag", tagId, tagFound.getName()); + activeFilters.add(chip); + } + }); + } + if ("text" in this.__initFilterData && this.__initFilterData["text"]) { + textField.setValue(this.__initFilterData["text"]); + } + + // Add listeners + textField.addListener("keypress", e => { + if (e.getKeyIdentifier() === "Enter") { + this.__filter("text", textField.getValue()); + } + }, this); + textField.addListener("unfocus", () => { + this.__filter("text", textField.getValue()); + }, this); + + resetButton.addListener("tap", () => { + this.exclude(); + }); + }, + + __applyCurrentContext: function(value, old) { + if (value === old) { + return; + } + switch (value) { + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: + this.getChildControl("my-projects-button").setValue(true); + this.getChildControl("search-bar-filter").getChildControl("text-field").setPlaceholder(this.tr("Search in My projects")); + this.getChildControl("shared-with-button").setVisibility("visible"); + this.getChildControl("tags-button").setVisibility("visible"); + break; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES: + this.getChildControl("templates-button").setValue(true); + this.getChildControl("search-bar-filter").getChildControl("text-field").setPlaceholder(this.tr("Search in Templates")); + this.getChildControl("shared-with-button").setVisibility("excluded"); + this.getChildControl("tags-button").setVisibility("visible"); + break; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: + this.getChildControl("public-projects-button").setValue(true); + this.getChildControl("search-bar-filter").getChildControl("text-field").setPlaceholder(this.tr("Search in Public projects")); + this.getChildControl("shared-with-button").setVisibility("excluded"); + this.getChildControl("tags-button").setVisibility("visible"); + break; + } + }, + + __filter: function(filterType, filterData) { + this.fireDataEvent("filterChanged", { + searchContext: this.getCurrentContext(), + filterType, + filterData, + }); + this.exclude(); + }, + + __addSharedWithMenu: function(menuButton) { + const menu = this.__sharedWithMenu = new qx.ui.menu.Menu(); + + const sharedWithRadioGroup = new qx.ui.form.RadioGroup(); + const options = osparc.dashboard.SearchBarFilter.getSharedWithOptions(this.__resourceType); + options.forEach((option, idx) => { + const button = new qx.ui.menu.RadioButton(option.label); + menu.add(button); + button.addListener("execute", () => this.__filter("sharedWith", option)); + sharedWithRadioGroup.add(button); + // preselect show-all + if (idx === 0) { + sharedWithRadioGroup.setSelection([button]); + } + }); + menuButton.setMenu(menu); + }, + + __addTagsMenu: function(menuButton) { + const tags = osparc.store.Tags.getInstance().getTags(); + menuButton.setVisibility(tags.length ? "visible" : "excluded"); + if (tags.length) { + const menu = this.__tagsMenu = new qx.ui.menu.Menu(); + osparc.utils.Utils.setIdToWidget(menu, "searchBarFilter-tags-menu"); + tags.forEach(tag => { + const tagButton = new qx.ui.menu.Button(tag.getName(), "@FontAwesome5Solid/tag/12"); + tagButton.getChildControl("icon").setTextColor(tag.getColor()); + menu.add(tagButton); + tagButton.addListener("execute", () => this.__filter("tag", tag)); + }); + menuButton.setMenu(menu); + } + }, + + __attachHideHandlers: function() { + const tapListener = e => { + const excludeElements = [ + this, + this.__sharedWithMenu, + this.__tagsMenu, + ]; + for (let i = 0; i < excludeElements.length; i++) { + if (excludeElements[i] && osparc.utils.Utils.isMouseOnElement(excludeElements[i], e)) { + return; + } + } + + this.exclude(); + document.removeEventListener("mousedown", tapListener); + }; + document.addEventListener("mousedown", tapListener); + }, + } +}); 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 3135e465195..800fe89f135 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -64,15 +64,15 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { properties: { currentContext: { check: [ - "studiesAndFolders", - "workspaces", - "templates", - "publicTemplates", - "functions", - "trash", - "searchProjects", - "searchTemplates", - "searchPublicTemplates", + "studiesAndFolders", // osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS, + "workspaces", // osparc.dashboard.StudyBrowser.CONTEXT.WORKSPACES, + "templates", // osparc.dashboard.StudyBrowser.CONTEXT.TEMPLATES, + "publicTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.PUBLIC_TEMPLATES, + "functions", // osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS, + "trash", // osparc.dashboard.StudyBrowser.CONTEXT.TRASH, + "searchProjects", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS, + "searchTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES, + "searchPublicTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES, ], nullable: false, init: "studiesAndFolders", @@ -113,6 +113,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __foldersList: null, __loadingFolders: null, __loadingWorkspaces: null, + __lastUrlParams: null, // overridden initResources: function() { @@ -298,6 +299,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return; } + this.__lastUrlParams = osparc.utils.Utils.deepCloneObject(resp["params"]["url"]); + if (this.__lastUrlParams["text"]) { + this.__lastUrlParams["text"] = decodeURIComponent(this.__lastUrlParams["text"]); + } + switch (this.getCurrentContext()) { case osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS: case osparc.dashboard.StudyBrowser.CONTEXT.TRASH: @@ -778,12 +784,23 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { delete reqParams["limit"]; delete reqParams["offset"]; delete reqParams["filters"]; + if (reqParams["text"]) { + // decodeURIComponent the text to compare it with the currentParams + reqParams["text"] = decodeURIComponent(reqParams["text"]); + } const cParams = this.__getRequestParams(); const currentParams = {}; Object.entries(cParams).forEach(([snakeKey, value]) => { const key = osparc.utils.Utils.snakeToCamel(snakeKey); - currentParams[key] = value === "null" ? null : value; + if (value === "null") { + currentParams[key] = null; + } else if (key === "text") { + // decodeURIComponent the text to compare it with the reqParams + currentParams[key] = decodeURIComponent(value); + } else { + currentParams[key] = value; + } }); // check the entries in currentParams are the same as the reqParams @@ -881,7 +898,6 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const filterData = this._searchBarFilter.getFilterData(); if (filterData.text) { requestParams.text = filterData.text ? encodeURIComponent(filterData.text) : ""; // name, description and uuid - requestParams["tagIds"] = filterData.tags.length ? filterData.tags.join(",") : ""; } } @@ -1059,6 +1075,14 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { // LAYOUT // _createLayout: function() { this._createSearchBar(); + this._searchBarFilter.set({ + showFilterMenu: false, + }); + const searchBarTextField = this._searchBarFilter.getChildControl("text-field"); + searchBarTextField.set({ + cursor: "pointer", + }); + searchBarTextField.addListener("tap", () => this.__extendSearchBar()); const header = this.__header = new osparc.dashboard.StudyBrowserHeader(); this.__header.addListener("trashEmptied", () => this.reloadResources(), this); @@ -1140,6 +1164,58 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return this._resourcesContainer; }, + __extendSearchBar: function() { + const searchBarFilterExtended = this._searchBarFilter.popUpSearchBarFilter(); + let curatedContext = null; + switch (this.getCurrentContext()) { + case osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS: + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: + case osparc.dashboard.StudyBrowser.CONTEXT.TRASH: + curatedContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS; + break; + case osparc.dashboard.StudyBrowser.CONTEXT.TEMPLATES: + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES: + curatedContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES; + break; + case osparc.dashboard.StudyBrowser.CONTEXT.PUBLIC_TEMPLATES: + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: + curatedContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES; + break; + default: + curatedContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS; + break; + } + searchBarFilterExtended.set({ + currentContext: curatedContext, + }); + searchBarFilterExtended.addListener("filterChanged", e => { + const data = e.getData(); + // first update the filters + const filterType = data["filterType"]; + const filterData = data["filterData"]; + switch (filterType) { + case "text": + this._searchBarFilter.getChildControl("text-field").setValue(filterData); + break; + case "sharedWith": + this._searchBarFilter.setSharedWithActiveFilter(filterData.id, filterData.label); + break; + case "tag": + this._searchBarFilter.addTagActiveFilter(filterData); + break; + } + // then update the search context this will trigger the search + const searchContext = data["searchContext"]; + switch (searchContext) { + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES: + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: + this._changeContext(searchContext); + break; + } + }); + }, + __connectContexts: function() { const header = this.__header; header.addListener("locationChanged", () => { @@ -1167,40 +1243,50 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this._searchBarFilter.addListener("filterChanged", e => { const filterData = e.getData(); - if (filterData.text) { - let searchContext = null; - switch (this.getCurrentContext()) { - case osparc.dashboard.StudyBrowser.CONTEXT.TEMPLATES: - case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES: - searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES; - break; - case osparc.dashboard.StudyBrowser.CONTEXT.PUBLIC_TEMPLATES: - case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: - searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES; - break; - case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: - // functions are not searchable yet - searchContext = null; - break; - default: + let searchContext = null; + let backToContext = null; + switch (this.getCurrentContext()) { + case osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS: + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: + case osparc.dashboard.StudyBrowser.CONTEXT.TRASH: + if (filterData.text) { searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS; - break; - } - if (searchContext) { - this._changeContext(searchContext); - } - } else { - let backToContext = osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS; - switch (this.getCurrentContext()) { - case osparc.dashboard.StudyBrowser.CONTEXT.TEMPLATES: - case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES: + } else { + backToContext = osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS; + } + break; + case osparc.dashboard.StudyBrowser.CONTEXT.TEMPLATES: + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES: + if (filterData.text) { + searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES; + } else { backToContext = osparc.dashboard.StudyBrowser.CONTEXT.TEMPLATES; - break; - case osparc.dashboard.StudyBrowser.CONTEXT.PUBLIC_TEMPLATES: - case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: + } + break; + case osparc.dashboard.StudyBrowser.CONTEXT.PUBLIC_TEMPLATES: + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: + if (filterData.text) { + searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES; + } else { backToContext = osparc.dashboard.StudyBrowser.CONTEXT.PUBLIC_TEMPLATES; - break; - } + } + break; + case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: + // functions are not searchable yet + searchContext = null; + backToContext = osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS; + break; + default: + if (filterData.text) { + searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS; + } else { + backToContext = osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS; + } + break; + } + if (searchContext) { + this._changeContext(searchContext); + } else if (backToContext) { const workspaceId = this.getCurrentWorkspaceId(); const folderId = this.getCurrentFolderId(); this._changeContext(backToContext, workspaceId, folderId); @@ -1219,6 +1305,16 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return; } + if ( + context.includes("search") && + this.__lastUrlParams && + "text" in this.__lastUrlParams && + this.__lastUrlParams["text"] === this._searchBarFilter.getTextFilterValue() + ) { + // text search didn't change + return; + } + osparc.store.Store.getInstance().setStudyBrowserContext(context); this.set({ currentContext: context, diff --git a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js index d91e62ab485..db4e9c176c4 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js @@ -324,7 +324,7 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { } case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: this.__setIcon("@FontAwesome5Solid/search/24"); - title.setValue(this.tr("Projects results")); + title.setValue(this.tr("My Projects results")); break; case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES: this.__setIcon("@FontAwesome5Solid/search/24"); 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 dffd67aaac2..b965b58d24b 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -142,17 +142,13 @@ qx.Class.define("osparc.data.Resources", { getPageSearch: { useCache: false, method: "GET", - url: statics.API + "/projects:search?offset={offset}&limit={limit}&text={text}&tag_ids={tagIds}&order_by={orderBy}&type=user" + url: statics.API + "/projects:search?offset={offset}&limit={limit}&text={text}&order_by={orderBy}&type=user" }, getPageTrashed: { useCache: false, method: "GET", url: statics.API + "/projects:search?filters={%22trashed%22:%22true%22}&offset={offset}&limit={limit}&order_by={orderBy}&type=user" }, - open: { - method: "POST", - url: statics.API + "/projects/{studyId}:open" - }, getWallet: { useCache: false, method: "GET", @@ -166,14 +162,6 @@ qx.Class.define("osparc.data.Resources", { method: "POST", url: statics.API + "/projects/{studyId}/wallet/{walletId}:pay-debt" }, - openDisableAutoStart: { - method: "POST", - url: statics.API + "/projects/{studyId}:open?disable_service_auto_start={disableServiceAutoStart}" - }, - close: { - method: "POST", - url: statics.API + "/projects/{studyId}:close" - }, duplicate: { method: "POST", // url: statics.API + "/projects/{studyId}:duplicate" @@ -197,14 +185,6 @@ qx.Class.define("osparc.data.Resources", { method: "PATCH", url: statics.API + "/projects/{studyId}" }, - trash: { - method: "POST", - url: statics.API + "/projects/{studyId}:trash" - }, - untrash: { - method: "POST", - url: statics.API + "/projects/{studyId}:untrash" - }, delete: { method: "DELETE", url: statics.API + "/projects/{studyId}" @@ -252,11 +232,6 @@ qx.Class.define("osparc.data.Resources", { method: "PUT", url: statics.API + "/projects/{studyId}/nodes/{nodeId}/pricing-plan/{pricingPlanId}/pricing-unit/{pricingUnitId}" }, - checkShareePermissions: { - useCache: false, - method: "GET", - url: statics.API + "/projects/{studyId}/nodes/-/services:access?for_gid={gid}" - }, postAccessRights: { useCache: false, method: "POST", @@ -272,10 +247,6 @@ 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", @@ -303,6 +274,35 @@ qx.Class.define("osparc.data.Resources", { method: "PATCH", url: statics.API + "/projects/{studyId}/metadata" }, + open: { + method: "POST", + url: statics.API + "/projects/{studyId}:open" + }, + openDisableAutoStart: { + method: "POST", + url: statics.API + "/projects/{studyId}:open?disable_service_auto_start={disableServiceAutoStart}" + }, + close: { + method: "POST", + url: statics.API + "/projects/{studyId}:close" + }, + shareWithEmail: { + method: "POST", + url: statics.API + "/projects/{studyId}:share" + }, + checkShareePermissions: { + useCache: false, + method: "GET", + url: statics.API + "/projects/{studyId}/nodes/-/services:access?for_gid={gid}" + }, + trash: { + method: "POST", + url: statics.API + "/projects/{studyId}:trash" + }, + untrash: { + method: "POST", + url: statics.API + "/projects/{studyId}:untrash" + }, } }, "conversations": { diff --git a/services/static-webserver/client/source/class/osparc/data/model/StudyUI.js b/services/static-webserver/client/source/class/osparc/data/model/StudyUI.js index 812dc4a1e62..04733b429ff 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/StudyUI.js +++ b/services/static-webserver/client/source/class/osparc/data/model/StudyUI.js @@ -34,7 +34,6 @@ qx.Class.define("osparc.data.model.StudyUI", { currentNodeId: studyDataUI && studyDataUI.currentNodeId ? studyDataUI.currentNodeId : this.initCurrentNodeId(), mode: studyDataUI && studyDataUI.mode ? studyDataUI.mode : this.initMode(), annotations: {}, - templateType: studyDataUI && studyDataUI.templateType ? studyDataUI.templateType : null, }); if ("annotations" in studyDataUI) { @@ -84,13 +83,6 @@ qx.Class.define("osparc.data.model.StudyUI", { init: {}, nullable: true }, - - templateType: { - check: [null, "hypertool", "tutorial", "template"], - init: null, - nullable: true, - event: "changeTemplateType", - }, }, statics: { diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsIndicatorButton.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsIndicatorButton.js index 25ae3083b79..4397c9a57dc 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsIndicatorButton.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsIndicatorButton.js @@ -64,16 +64,7 @@ qx.Class.define("osparc.desktop.credits.CreditsIndicatorButton", { }, __positionCreditsContainer: function() { - const bounds = this.getBounds(); - const cel = this.getContentElement(); - if (cel) { - const domEle = cel.getDomElement(); - if (domEle) { - const rect = domEle.getBoundingClientRect(); - bounds.left = parseInt(rect.x); - bounds.top = parseInt(rect.y); - } - } + const bounds = osparc.utils.Utils.getBounds(this); const bottom = bounds.top + bounds.height; const right = bounds.left + bounds.width; this.__creditsContainer.setPosition(right, bottom); diff --git a/services/static-webserver/client/source/class/osparc/notification/NotificationsButton.js b/services/static-webserver/client/source/class/osparc/notification/NotificationsButton.js index 3c1cfd12152..c92b6133592 100644 --- a/services/static-webserver/client/source/class/osparc/notification/NotificationsButton.js +++ b/services/static-webserver/client/source/class/osparc/notification/NotificationsButton.js @@ -129,16 +129,7 @@ qx.Class.define("osparc.notification.NotificationsButton", { }, __positionNotificationsContainer: function() { - const bounds = this.getBounds(); - const cel = this.getContentElement(); - if (cel) { - const domEle = cel.getDomElement(); - if (domEle) { - const rect = domEle.getBoundingClientRect(); - bounds.left = parseInt(rect.x); - bounds.top = parseInt(rect.y); - } - } + const bounds = osparc.utils.Utils.getBounds(this); const bottom = bounds.top + bounds.height; const right = bounds.left + bounds.width; this.__notificationsContainer.setPosition(right, bottom); 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 d0c326539a6..145febf1754 100644 --- a/services/static-webserver/client/source/class/osparc/store/Store.js +++ b/services/static-webserver/client/source/class/osparc/store/Store.js @@ -77,15 +77,15 @@ qx.Class.define("osparc.store.Store", { }, studyBrowserContext: { check: [ - "studiesAndFolders", - "workspaces", - "templates", - "publicTemplates", - "functions", - "trash", - "searchProjects", - "searchTemplates", - "searchPublicTemplates", + "studiesAndFolders", // osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS, + "workspaces", // osparc.dashboard.StudyBrowser.CONTEXT.WORKSPACES, + "templates", // osparc.dashboard.StudyBrowser.CONTEXT.TEMPLATES, + "publicTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.PUBLIC_TEMPLATES, + "functions", // osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS, + "trash", // osparc.dashboard.StudyBrowser.CONTEXT.TRASH, + "searchProjects", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS, + "searchTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES, + "searchPublicTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES, ], init: "studiesAndFolders", nullable: false, diff --git a/services/static-webserver/client/source/class/osparc/task/TasksButton.js b/services/static-webserver/client/source/class/osparc/task/TasksButton.js index b43c7b2bb49..7219fbaa5db 100644 --- a/services/static-webserver/client/source/class/osparc/task/TasksButton.js +++ b/services/static-webserver/client/source/class/osparc/task/TasksButton.js @@ -95,16 +95,7 @@ qx.Class.define("osparc.task.TasksButton", { document.removeEventListener("mousedown", tapListener); }; - const bounds = this.getBounds(); - const cel = this.getContentElement(); - if (cel) { - const domeEle = cel.getDomElement(); - if (domeEle) { - const rect = domeEle.getBoundingClientRect(); - bounds.left = parseInt(rect.x); - bounds.top = parseInt(rect.y); - } - } + const bounds = osparc.utils.Utils.getBounds(this); const tasks = osparc.task.TasksContainer.getInstance(); tasks.setTasksContainerPosition( bounds.left + bounds.width - osparc.task.TaskUI.MAX_WIDTH, diff --git a/services/static-webserver/client/source/class/osparc/theme/Appearance.js b/services/static-webserver/client/source/class/osparc/theme/Appearance.js index 906313beff3..fbce40af34d 100644 --- a/services/static-webserver/client/source/class/osparc/theme/Appearance.js +++ b/services/static-webserver/client/source/class/osparc/theme/Appearance.js @@ -240,6 +240,20 @@ qx.Theme.define("osparc.theme.Appearance", { } }, + /* + --------------------------------------------------------------------------- + TABLE + --------------------------------------------------------------------------- + */ + + "table-header-cell": { + style: function() { + return { + font: "text-13", // override the default theme's bold font + } + } + }, + /* --------------------------------------------------------------------------- WINDOW-SMALL-CAP CHOOSER @@ -854,14 +868,12 @@ qx.Theme.define("osparc.theme.Appearance", { backgroundColor = "default-button-focus-background"; } if (states.selected || states.checked) { - textColor = "default-button-disabled"; + textColor = "white"; cursor = "default"; decorator = "form-button-checked"; - backgroundColor = "default-button-disabled-background"; + backgroundColor = "product-color"; } - decorator; - return { textColor: textColor, cursor: cursor, 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 9968467efff..846c1d6e98a 100644 --- a/services/static-webserver/client/source/class/osparc/utils/Utils.js +++ b/services/static-webserver/client/source/class/osparc/utils/Utils.js @@ -91,6 +91,22 @@ qx.Class.define("osparc.utils.Utils", { FLOATING_Z_INDEX: 1000001 + 1, + getBounds: function(widget) { + const bounds = widget.getBounds(); + const cel = widget.getContentElement(); + if (cel) { + const domeEle = cel.getDomElement(); + if (domeEle) { + const rect = domeEle.getBoundingClientRect(); + bounds.left = parseInt(rect.x); + bounds.top = parseInt(rect.y); + bounds.width = parseInt(rect.width); + bounds.height = parseInt(rect.height); + } + } + return bounds; + }, + replaceIconWithThumbnail: function(widget, thumbnailUrl, size = 24) { if (thumbnailUrl) { const thumbnail = new osparc.ui.basic.Thumbnail(thumbnailUrl, size, size).set({ @@ -450,7 +466,7 @@ qx.Class.define("osparc.utils.Utils", { isMouseOnElement: function(element, event, offset = 0) { const domElement = element.getContentElement().getDomElement(); - const boundRect = domElement.getBoundingClientRect(); + const boundRect = domElement && domElement.getBoundingClientRect(); if (boundRect && event.x > boundRect.x - offset && event.y > boundRect.y - offset && @@ -698,10 +714,6 @@ qx.Class.define("osparc.utils.Utils", { widget.getContentElement().setStyle("background-color", "transparent"); }, - removeBorder: function(widget) { - widget.getContentElement().setStyle("border", "0px solid"); - }, - hideBorder: function(widget) { widget.getContentElement().setStyle("border", "1px solid transparent"); },