diff --git a/services/static-webserver/client/source/class/osparc/dashboard/CardContainer.js b/services/static-webserver/client/source/class/osparc/dashboard/CardContainer.js index 281b16bfb4bc..adeccdabf516 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/CardContainer.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/CardContainer.js @@ -27,7 +27,8 @@ qx.Class.define("osparc.dashboard.CardContainer", { return ( widget instanceof osparc.dashboard.CardBase || widget instanceof osparc.dashboard.FolderButtonBase || - widget instanceof osparc.dashboard.WorkspaceButtonBase + widget instanceof osparc.dashboard.WorkspaceButtonBase || + widget instanceof osparc.dashboard.FileButtonItem ); }, }, diff --git a/services/static-webserver/client/source/class/osparc/dashboard/FileButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/FileButtonItem.js new file mode 100644 index 000000000000..4b117fa7b6e7 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/dashboard/FileButtonItem.js @@ -0,0 +1,136 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * Widget used for displaying a File in the Study Browser + * + */ + +qx.Class.define("osparc.dashboard.FileButtonItem", { + extend: osparc.dashboard.ListButtonBase, + + /** + * @param file {osparc.data.model.File} The file to display + */ + construct: function(file) { + this.base(arguments); + + this.set({ + cursor: "default", + }); + + this.setPriority(osparc.dashboard.CardBase.CARD_PRIORITY.ITEM); + + this.set({ + file: file + }); + }, + + events: { + "openLocation": "qx.event.type.Data", + }, + + properties: { + file: { + check: "osparc.data.model.File", + nullable: false, + init: null, + apply: "__applyFile", + }, + }, + + members: { + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "date-by": + control = new osparc.ui.basic.DateAndBy(); + this._add(control, { + row: 0, + column: osparc.dashboard.ListButtonBase.POS.LAST_CHANGE + }); + break; + case "menu-button": { + control = new qx.ui.form.MenuButton().set({ + appearance: "form-button-outlined", + padding: [0, 8], + maxWidth: osparc.dashboard.ListButtonItem.MENU_BTN_DIMENSIONS, + maxHeight: osparc.dashboard.ListButtonItem.MENU_BTN_DIMENSIONS, + alignX: "center", + alignY: "middle", + icon: "@FontAwesome5Solid/ellipsis-v/14", + focusable: false + }); + // make it circular + control.getContentElement().setStyles({ + "border-radius": `${osparc.dashboard.ListButtonItem.MENU_BTN_DIMENSIONS / 2}px` + }); + this._add(control, { + row: 0, + column: osparc.dashboard.ListButtonBase.POS.OPTIONS + }); + break; + } + } + return control || this.base(arguments, id); + }, + + __applyFile: function(file) { + const id = file.getPath(); + this.set({ + cardKey: "file-" + id, + }); + osparc.utils.Utils.setIdToWidget(this, "fileItem_" + id); + + this.setIcon(file.getIsDirectory() ? "@FontAwesome5Solid/folder/" : "@FontAwesome5Solid/file/"); + this.getChildControl("icon").getChildControl("image").set({ + paddingTop: 5, + }); + + this.getChildControl("title").set({ + value: file.getName(), + toolTipText: file.getName(), + }); + + this.getChildControl("owner").set({ + value: "Project Id: " + osparc.utils.Utils.uuidToShort(file.getProjectId()), + }); + + this.getChildControl("date-by").set({ + date: file.getModifiedAt(), + toolTipText: this.tr("Last modified"), + }); + + const menuButton = this.getChildControl("menu-button"); + menuButton.setVisibility("visible"); + + const menu = new qx.ui.menu.Menu().set({ + appearance: "menu-wider", + position: "bottom-right", + }); + + const openLocationButton = new qx.ui.menu.Button(this.tr("Open location"), "@FontAwesome5Solid/folder/12"); + openLocationButton.addListener("execute", () => this.fireDataEvent("openLocation", { + projectId: file.getProjectId(), + path: file.getPath() + }), this); + menu.add(openLocationButton); + + menuButton.setMenu(menu); + }, + } +}); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ListButtonBase.js b/services/static-webserver/client/source/class/osparc/dashboard/ListButtonBase.js index 79d650e40885..7d65bb9c9a9a 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ListButtonBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ListButtonBase.js @@ -42,7 +42,7 @@ qx.Class.define("osparc.dashboard.ListButtonBase", { }, statics: { - ITEM_HEIGHT: 35, + ITEM_HEIGHT: 40, SPACING: 5, POS: { THUMBNAIL: 0, diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ListButtonLoadMore.js b/services/static-webserver/client/source/class/osparc/dashboard/ListButtonLoadMore.js index 1f0fad3e4a6b..c7be9976f644 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ListButtonLoadMore.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ListButtonLoadMore.js @@ -39,12 +39,12 @@ qx.Class.define("osparc.dashboard.ListButtonLoadMore", { members: { _applyFetching: function(value) { this.setIcon(osparc.dashboard.CardBase.LOADING_ICON); + const image = this.getChildControl("icon").getChildControl("image"); + image.setPadding(5); // add padding to make the rotation smoother if (value) { - this.getChildControl("icon").getChildControl("image").getContentElement() - .addClass("rotate"); + image.getContentElement().addClass("rotate"); } else { - this.getChildControl("icon").getChildControl("image").getContentElement() - .removeClass("rotate"); + image.getContentElement().removeClass("rotate"); } this.setEnabled(!value); }, 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 a8b5967cfeea..e2b2550ad7fb 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js @@ -357,6 +357,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { resourcesContainer.addListener("trashWorkspaceRequested", e => this._trashWorkspaceRequested(e.getData())); resourcesContainer.addListener("untrashWorkspaceRequested", e => this._untrashWorkspaceRequested(e.getData())); resourcesContainer.addListener("deleteWorkspaceRequested", e => this._deleteWorkspaceRequested(e.getData())); + resourcesContainer.addListener("openLocation", e => this._openLocation(e.getData())); this._addToLayout(resourcesContainer); }, @@ -928,7 +929,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { throw new Error("Abstract method called!"); }, - _workspaceSelected: function(workspaceId) { + _openLocation: function(data) { throw new Error("Abstract method called!"); }, 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 8ff84ed5bce2..e9f2aa58f7aa 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js @@ -29,6 +29,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { this.__workspacesList = []; this.__foldersList = []; + this.__filesList = []; this.__resourcesList = []; this.__groupedContainersList = []; this.__resourceType = resourceType || "study"; @@ -41,14 +42,16 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { const foldersContainer = this.__foldersContainer = new osparc.dashboard.CardContainer(); this.__foldersContainer.exclude(); this._add(foldersContainer); + + const filesContainer = this.__filesContainer = new osparc.dashboard.CardContainer(); + filesContainer.getLayout().set({ + spacingY: osparc.dashboard.ListButtonBase.SPACING, + }); + this.__filesContainer.exclude(); + this._add(filesContainer); } - const noResourcesFound = this.__noResourcesFound = new qx.ui.basic.Label("No resources found").set({ - visibility: "excluded", - font: "text-14" - }); - noResourcesFound.exclude(); - this._add(noResourcesFound); + this.getChildControl("no-resources-found"); const nonGroupedContainer = this.__nonGroupedContainer = this.__createFlatList(); this._add(nonGroupedContainer); @@ -98,6 +101,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { "changeContext": "qx.event.type.Data", "studyToFolderRequested": "qx.event.type.Data", "folderToFolderRequested": "qx.event.type.Data", + "openLocation": "qx.event.type.Data", }, statics: { @@ -128,22 +132,52 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { spacingY: spacing }); }, + + fitToContainer: function(card, container) { + const __fitToContainer = () => { + const bounds = container.getBounds() || container.getSizeHint(); + card.setWidth(bounds.width); + }; + [ + "appear", + "resize", + ].forEach(ev => { + container.addListener(ev, () => __fitToContainer()); + }); + __fitToContainer(); + }, }, members: { __foldersList: null, __workspacesList: null, + __filesList: null, __resourcesList: null, __groupedContainersList: null, __foldersContainer: null, __workspacesContainer: null, + __filesContainer: null, __nonGroupedContainer: null, __groupedContainers: null, __resourceType: null, - __noResourcesFound: null, __noResourcesFoundTimer: null, - __evaluateNoResourcesFoundLabel: function() { + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "no-resources-found": + control = new qx.ui.basic.Label().set({ + value: this.tr("No Resources found"), + visibility: "excluded", + font: "text-14", + }); + this._add(control); + break; + } + return control || this.base(arguments, id); + }, + + __getNotFoundText: function() { let text = null; switch (this.__resourceType) { case "study": { @@ -175,26 +209,36 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { case "service": text = this.tr("No Apps found"); break; - default: - text = this.tr("No Resources found"); - break; } + return text; + }, - this.__noResourcesFound.exclude(); + __evaluateNoResourcesFoundLabel: function() { + const noResourcesFound = this.getChildControl("no-resources-found"); + noResourcesFound.exclude(); if (this.__noResourcesFoundTimer) { clearTimeout(this.__noResourcesFoundTimer); } - if (text && this.__resourcesList.length === 0) { + + if (this.__resourcesList.length === 0) { // delay it a bit to avoid the initial flickering this.__noResourcesFoundTimer = setTimeout(() => { - this.__noResourcesFound.set({ - value: text, - visibility: "visible", - }); + this.showNoResourcesFound(); }, 2000); } }, + showNoResourcesFound: function() { + const text = this.__getNotFoundText(); + if (text) { + const noResourcesFound = this.getChildControl("no-resources-found"); + noResourcesFound.set({ + value: text, + }); + noResourcesFound.show(); + } + }, + addNonResourceCard: function(card) { if (osparc.dashboard.CardContainer.isValidCard(card)) { let groupContainer = null; @@ -320,17 +364,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { container.add(card); if (this.getMode() === "list") { - const fitToContainer = () => { - const bounds = container.getBounds() || container.getSizeHint(); - card.setWidth(bounds.width); - }; - [ - "appear", - "resize", - ].forEach(ev => { - container.addListener(ev, () => fitToContainer()); - }); - fitToContainer(); + this.self().fitToContainer(card, container); } }, @@ -526,6 +560,39 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { }, // /FOLDERS + // FILES + setFilesToList: function(filesList) { + this.__filesList = filesList; + }, + + reloadFiles: function() { + if (this.__filesContainer) { + this.__filesContainer.removeAll(); + this.__filesContainer.exclude(); + } + let fileCards = []; + this.__filesList.forEach(fileData => fileCards.push(this.__fileToCard(fileData))); + return fileCards; + }, + + __fileToCard: function(fileData) { + const card = this.__createFileCard(fileData); + this.__filesContainer.add(card); + this.__filesContainer.show(); + this.self().fitToContainer(card, this.__filesContainer); + return card; + }, + + __createFileCard: function(fileData) { + const file = new osparc.data.model.File(fileData); + const card = new osparc.dashboard.FileButtonItem(file); + [ + "openLocation", + ].forEach(eName => card.addListener(eName, e => this.fireDataEvent(eName, e.getData()))); + return card; + }, + // /FILES + __moveNoGroupToLast: function() { const idx = this.__groupedContainers.getChildren().findIndex(grpContainer => grpContainer === this.__getGroupContainer("no-group")); if (idx > -1) { diff --git a/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilterExtended.js b/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilterExtended.js index 5f127fc8fd46..8d99efe602a4 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilterExtended.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilterExtended.js @@ -53,7 +53,8 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { "searchProjects", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS, "searchTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES, "searchPublicTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES, - "searchFunctions" // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS + "searchFunctions", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS, + "searchFiles", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES, ], init: null, nullable: false, @@ -145,18 +146,32 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { case "functions-button": { control = this.self().createListItem( this.tr("Functions"), - "@MaterialIcons/functions/18", + "@MaterialIcons/functions/16", "functions" ); const contextDropDown = this.getChildControl("context-drop-down"); contextDropDown.add(control); break; } + case "files-button": { + control = this.self().createListItem( + this.tr("Files"), + "@FontAwesome5Solid/file-alt/14", + "files" + ); + const contextDropDown = this.getChildControl("context-drop-down"); + contextDropDown.add(control); + break; + } + case "filters-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)); + this._add(control); + break; case "filter-buttons": control = new qx.ui.toolbar.ToolBar().set({ backgroundColor: osparc.dashboard.SearchBarFilter.BG_COLOR, }); - this._add(control); + this.getChildControl("filters-layout").add(control); break; case "shared-with-button": control = new qx.ui.toolbar.MenuButton(this.tr("Shared with"), "@FontAwesome5Solid/share-alt/12"); @@ -168,6 +183,14 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { this.__addTagsMenu(control); this.getChildControl("filter-buttons").add(control); break; + case "date-filters": + control = new osparc.filter.DateFilters(); + control.addListener("change", e => { + const dateRange = e.getData(); + this.__filter("modifiedAt", dateRange); + }); + this.getChildControl("filters-layout").add(control); + break; } return control || this.base(arguments, id); }, @@ -186,6 +209,7 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { if (osparc.product.Utils.showFunctions()) { this.getChildControl("functions-button"); } + this.getChildControl("files-button"); if (contextDropDown.getChildren().length === 1) { contextDropDown.hide(); } @@ -206,6 +230,9 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { case "functions": this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS); break; + case "files": + this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES); + break; } } }); @@ -260,30 +287,42 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { const searchBarFilter = this.getChildControl("search-bar-filter"); const sharedWithButton = this.getChildControl("shared-with-button"); const tagsButton = this.getChildControl("tags-button"); + const dateFilters = this.getChildControl("date-filters"); switch (value) { case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: contextDropDown.setSelection([this.getChildControl("my-projects-button")]); searchBarFilter.getChildControl("text-field").setPlaceholder(this.tr("Search in My projects")); - sharedWithButton.setVisibility("visible"); - tagsButton.setVisibility("visible"); + sharedWithButton.show(); + tagsButton.show(); + dateFilters.exclude(); break; case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES: contextDropDown.setSelection([this.getChildControl("templates-button")]); searchBarFilter.getChildControl("text-field").setPlaceholder(this.tr("Search in Templates")); - sharedWithButton.setVisibility("excluded"); - tagsButton.setVisibility("visible"); + sharedWithButton.exclude(); + tagsButton.show(); + dateFilters.exclude(); break; case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: contextDropDown.setSelection([this.getChildControl("public-projects-button")]); searchBarFilter.getChildControl("text-field").setPlaceholder(this.tr("Search in Public Projects")); - sharedWithButton.setVisibility("excluded"); - tagsButton.setVisibility("visible"); + sharedWithButton.exclude(); + tagsButton.show(); + dateFilters.exclude(); break; case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: contextDropDown.setSelection([this.getChildControl("functions-button")]); searchBarFilter.getChildControl("text-field").setPlaceholder(this.tr("Search in Functions")); - sharedWithButton.setVisibility("excluded"); - tagsButton.setVisibility("excluded"); + sharedWithButton.exclude(); + tagsButton.exclude(); + dateFilters.exclude(); + break; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES: + contextDropDown.setSelection([this.getChildControl("files-button")]); + searchBarFilter.getChildControl("text-field").setPlaceholder(this.tr("Search in Files")); + sharedWithButton.exclude(); + tagsButton.exclude(); + dateFilters.exclude(); break; } }, 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 d2ead1e53bf1..cd03aeac3cdb 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -59,6 +59,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { SEARCH_TEMPLATES: "searchTemplates", SEARCH_PUBLIC_TEMPLATES: "searchPublicTemplates", SEARCH_FUNCTIONS: "searchFunctions", + SEARCH_FILES: "searchFiles", } }, @@ -75,6 +76,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { "searchTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES, "searchPublicTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES, "searchFunctions", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS, + "searchFiles", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES, ], nullable: false, init: "studiesAndFolders", @@ -113,8 +115,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __sortByButton: null, __workspacesList: null, __foldersList: null, - __loadingFolders: null, + __filesList: null, __loadingWorkspaces: null, + __loadingFolders: null, __lastUrlParams: null, // overridden @@ -176,7 +179,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, reloadMoreResources: function() { - this.__reloadStudies(); + if (this.getCurrentContext() === osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES) { + this.__reloadSearchFiles(); + } else { + this.__reloadStudies(); + } }, __reloadWorkspaces: function() { @@ -410,6 +417,78 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }); }, + __reloadSearchFiles: function() { + if ( + !osparc.auth.Manager.getInstance().isLoggedIn() || + this.getCurrentContext() !== osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES || + this._loadingResourcesBtn.isFetching() + ) { + return; + } + + this._loadingResourcesBtn.setFetching(true); + this._loadingResourcesBtn.setVisibility("visible"); + const filterData = this._searchBarFilter.getFilterData(); + const text = filterData.text ? encodeURIComponent(filterData.text) : ""; + const streamTasks = osparc.store.StreamTasks.getInstance(); + const existingStream = streamTasks.getStreamTask("files_search", text); + if (existingStream) { + this.__fetchFilesFromStream(existingStream); + } else { + streamTasks.abortStreamTasks(); + const streamPromise = osparc.store.Data.getInstance().searchFiles(text); + const pollingInterval = 2000; + streamTasks.createStreamTask("files_search", text, streamPromise, pollingInterval) + .then(newStream => this.__fetchFilesFromStream(newStream)); + } + }, + + __fetchFilesFromStream: function(stream) { + stream.fetchStream() + .then(streamData => { + const items = streamData["data"]["items"] || []; + const end = streamData["data"]["end"] || false; + stream.setEnd(end); + if (items.length) { + this.__addFilesToList(items); + } + // keep it not null to allow further fetching + this._resourcesContainer.getFlatList().nextRequest = !end; + // show no files found only if the stream ended and no files were found + if (end && this.__filesList.length === 0) { + this._resourcesContainer.getChildControl("no-resources-found").set({ + value: this.tr("No Files found"), + visibility: "visible", + }); + } + }) + .catch(err => { + osparc.FlashMessenger.logError(err); + // stop fetching + if (this._resourcesContainer.getFlatList()) { + this._resourcesContainer.getFlatList().nextRequest = null; + } + }) + .finally(() => { + if (this._resourcesContainer.getFlatList() && this._resourcesContainer.getFlatList().nextRequest) { + this._loadingResourcesBtn.set({ + visibility: "visible", + fetching: true + }); + // delay the next request to avoid flooding the server + setTimeout(() => { + this._loadingResourcesBtn.setFetching(false); // make it false before calling moreResourcesRequired + this._moreResourcesRequired(); + }, 2000); + } else if (this._loadingResourcesBtn) { + this._loadingResourcesBtn.set({ + visibility: "excluded", + fetching: false + }); + } + }); + }, + __resetStudiesList: function() { this._resourcesList = []; // It will remove the study cards @@ -443,18 +522,6 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }); }, - __setFoldersToList: function(folders) { - this.__foldersList = folders; - folders.forEach(folder => folder["resourceType"] = "folder"); - this.__reloadFolderCards(); - }, - - __setWorkspacesToList: function(workspaces) { - this.__workspacesList = workspaces; - workspaces.forEach(workspace => workspace["resourceType"] = "workspace"); - this.__reloadWorkspaceCards(); - }, - _reloadCards: function() { const fetching = this._loadingResourcesBtn ? this._loadingResourcesBtn.getFetching() : false; const visibility = this._loadingResourcesBtn ? this._loadingResourcesBtn.getVisibility() : "excluded"; @@ -487,6 +554,12 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, // WORKSPACES + __setWorkspacesToList: function(workspaces) { + this.__workspacesList = workspaces; + workspaces.forEach(workspace => workspace["resourceType"] = "workspace"); + this.__reloadWorkspaceCards(); + }, + __reloadWorkspaceCards: function() { this._resourcesContainer.setWorkspacesToList(this.__workspacesList); this._resourcesContainer.reloadWorkspaces(); @@ -553,6 +626,12 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { // /WORKSPACES // FOLDERS + __setFoldersToList: function(folders) { + this.__foldersList = folders; + folders.forEach(folder => folder["resourceType"] = "folder"); + this.__reloadFolderCards(); + }, + __reloadFolderCards: function() { this._resourcesContainer.setFoldersToList(this.__foldersList); this._resourcesContainer.reloadFolders(); @@ -696,6 +775,59 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, // /FOLDERS + // FILES + __setFilesToList: function(files) { + this.__filesList = files; + files.forEach(file => file["resourceType"] = "file"); + this.__reloadFileCards(); + }, + + __addFilesToList: function(filesList) { + filesList.forEach(fileData => { + const idx = this.__filesList.findIndex(fl => fl["path"] === fileData["path"]); + if (idx === -1) { + this.__filesList.push(fileData); + } + }); + this.__reloadFileCards(); + }, + + __reloadFileCards: function() { + this._resourcesContainer.setFilesToList(this.__filesList); + this._resourcesContainer.reloadFiles(); + }, + + __resetFilesList: function() { + this._resourcesList = []; + // It will remove the file cards + this._reloadCards(); + }, + + _openLocation: function(fileData) { + const projectId = fileData["projectId"]; + const path = fileData["path"]; + this.__openStudyDetails(projectId, path); + }, + + __openStudyDetails: function(projectId, path) { + osparc.store.Study.getInstance().getOne(projectId) + .then(studyData => { + if (studyData) { + const studyDataCopy = osparc.data.model.Study.deepCloneStudyObject(studyData); + studyDataCopy["resourceType"] = "study"; + const { + resourceDetails, + window, + } = osparc.dashboard.ResourceDetails.popUpInWindow(studyDataCopy); + resourceDetails.addListener("openStudy", () => { + const openCB = () => window.close(); + this._startStudyById(projectId, openCB); + }); + } + }); + }, + // /FILES + __configureStudyCards: function(cards) { cards.forEach(card => { card.setMultiSelectionMode(this.getMultiSelection()); @@ -920,6 +1052,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: requestParams.type = "user"; break; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES: + // nothing to add + break; } if (this.getCurrentContext().includes("search")) { @@ -1001,6 +1136,13 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { } }, + invalidateFiles: function() { + this.__resetFilesList(); + if (this._resourcesContainer.getFlatList()) { + this._resourcesContainer.getFlatList().nextRequest = null; + } + }, + __addNewPlusButton: function() { const newPlusButton = new osparc.dashboard.NewPlusButton(); this._leftFilters.add(newPlusButton); @@ -1222,6 +1364,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: curatedContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS; break; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES: + curatedContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES; + break; default: curatedContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS; break; @@ -1252,6 +1397,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES, osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES, osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS, + osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES, ].includes(searchContext)) { this._changeContext(searchContext); } @@ -1327,6 +1473,13 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { backToContext = osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS; } break; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES: + if (isSearchContext) { + searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES; + } else { + backToContext = osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS; + } + break; default: if (isSearchContext) { searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS; @@ -1378,6 +1531,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { // reset lists this.__setWorkspacesToList([]); this.__setFoldersToList([]); + this.__setFilesToList([]); this._resourcesList = []; this._resourcesContainer.setResourcesToList(this._resourcesList); this._resourcesContainer.reloadCards("studies"); @@ -1445,6 +1599,14 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this.invalidateFunctions(); this.__reloadStudies(); break; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES: + this._searchBarFilter.getChildControl("text-field").setPlaceholder("Search Files"); + // Files can't be sorted and don't support list view + this._toolbar.exclude(); + this._loadingResourcesBtn.setFetching(false); + this.invalidateFiles(); + this.__reloadSearchFiles(); + break; case osparc.dashboard.StudyBrowser.CONTEXT.TRASH: this._searchBarFilter.resetFilters(); this._searchBarFilter.getChildControl("text-field").setPlaceholder("Search in My Projects"); 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 421479da04c6..acd6c79d9512 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js @@ -337,6 +337,11 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: this.__setIcon("@FontAwesome5Solid/search/24"); title.setValue(this.tr("Functions results")); + break; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES: + this.__setIcon("@FontAwesome5Solid/search/24"); + title.setValue(this.tr("Files results")); + break; } }, diff --git a/services/static-webserver/client/source/class/osparc/data/PollTask.js b/services/static-webserver/client/source/class/osparc/data/PollTask.js index cf6194233feb..e00a8da36d5c 100644 --- a/services/static-webserver/client/source/class/osparc/data/PollTask.js +++ b/services/static-webserver/client/source/class/osparc/data/PollTask.js @@ -41,8 +41,7 @@ qx.Class.define("osparc.data.PollTask", { }); } - this.__retries = 3; - this.__pollTaskState(); + this._startPolling(); } }, @@ -77,7 +76,7 @@ qx.Class.define("osparc.data.PollTask", { resultHref: { check: "String", - nullable: false + nullable: true }, abortHref: { @@ -125,6 +124,11 @@ qx.Class.define("osparc.data.PollTask", { __retries: null, __aborting: null, + _startPolling: function() { + this.__retries = 3; + this.__pollTaskState(); + }, + __pollTaskState: function() { const statusPath = this.self().extractPathname(this.getStatusHref()); fetch(statusPath) 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 aa2d1de06535..59d9d2aba8f9 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -1377,6 +1377,10 @@ qx.Class.define("osparc.data.Resources", { method: "POST", url: statics.API + "/storage/locations/{locationId}:export-data" }, + searchFiles: { + method: "POST", + url: statics.API + "/storage/locations/{locationId}:search" + }, batchDelete: { method: "POST", url: statics.API + "/storage/locations/{locationId}/-/paths:batchDelete" diff --git a/services/static-webserver/client/source/class/osparc/data/StreamTask.js b/services/static-webserver/client/source/class/osparc/data/StreamTask.js new file mode 100644 index 000000000000..39ddc46f512c --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/data/StreamTask.js @@ -0,0 +1,99 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * @ignore(fetch) + */ + +qx.Class.define("osparc.data.StreamTask", { + extend: osparc.data.PollTask, + + construct: function(streamData, interval = 500) { + this.set({ + streamHref: streamData["stream_href"] || null, + }); + + this.base(arguments, streamData, interval); + }, + + events: { + "streamReceived": "qx.event.type.Data", + }, + + properties: { + streamHref: { + check: "String", + nullable: false, + init: null, + }, + + end: { + check: "Boolean", + nullable: false, + init: false, + }, + + pageSize: { + check: "Number", + nullable: false, + init: 5, + }, + }, + + members: { + // override + _startPolling: function() { + return; + }, + + fetchStream: function() { + return new Promise((resolve, reject) => { + if (!this.isEnd()) { + const streamPath = osparc.data.PollTask.extractPathname(this.getStreamHref()); + const url = `${streamPath}?limit=${this.getPageSize()}`; + fetch(url) + .then(resp => { + if (resp.status === 200) { + return resp.json(); + } + const errMsg = qx.locale.Manager.tr("Unsuccessful streaming"); + const err = new Error(errMsg); + this.fireDataEvent("pollingError", err); + throw err; + }) + .then(streamData => { + if ("error" in streamData && streamData["error"]) { + throw streamData["error"]; + } + const end = streamData["data"]["end"] || false; + if (end) { + this.setEnd(true); + } + if ("data" in streamData && streamData["data"]) { + resolve(streamData); + } + throw new Error("Missing stream data"); + }) + .catch(err => { + this.fireDataEvent("pollingError", err); + reject(err); + }); + } + }); + }, + } +}); diff --git a/services/static-webserver/client/source/class/osparc/data/model/File.js b/services/static-webserver/client/source/class/osparc/data/model/File.js new file mode 100644 index 000000000000..b671232d897f --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/data/model/File.js @@ -0,0 +1,92 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * Class that stores File data. + */ + +qx.Class.define("osparc.data.model.File", { + extend: qx.core.Object, + + /** + * @param fileData {Object} Object containing the serialized File Data + */ + construct: function(fileData) { + this.base(arguments); + + this.set({ + name: fileData.name, + projectId: fileData.projectId, + path: fileData.path, + createdAt: new Date(fileData.createdAt), + modifiedAt: new Date(fileData.modifiedAt), + isDirectory: fileData.isDirectory || false, + size: fileData.size || null, + }); + }, + + properties: { + name: { + check: "String", + nullable: false, + init: null, + event: "changeName" + }, + + projectId: { + check: "String", + nullable: false, + init: null, + event: "changeProjectId" + }, + + path: { + check: "String", + nullable: false, + init: null, + event: "changePath" + }, + + createdAt: { + check: "Date", + nullable: false, + init: null, + event: "changeCreatedAt" + }, + + modifiedAt: { + check: "Date", + nullable: false, + init: null, + event: "changeModifiedAt" + }, + + isDirectory: { + check: "Boolean", + nullable: false, + init: false, + event: "changeIsDirectory" + }, + + size: { + check: "Number", + nullable: true, + init: null, + event: "changeSize" + }, + }, +}); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js b/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js index 7c44a204b747..7b75887b8462 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/ResourceInTableViewer.js @@ -66,7 +66,7 @@ qx.Class.define("osparc.desktop.credits.ResourceInTableViewer", { this._add(control); break; case "date-filters": - control = new osparc.desktop.credits.DateFilters(); + control = new osparc.filter.DateFilters(); control.addListener("change", e => { const table = this.getChildControl("table"); table.getTableModel().setFilters(e.getData()); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js index 610ab82754e6..408ff327e14e 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js @@ -58,7 +58,7 @@ qx.Class.define("osparc.desktop.credits.Transactions", { // FEATURE TOGGLE // const filterContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox()) - // this.__dateFilters = new osparc.desktop.credits.DateFilters(); + // this.__dateFilters = new osparc.filter.DateFilters(); // this.__dateFilters.addListener("change", e => this.__saveFilters(e.getData())); // filterContainer.add(this.__dateFilters); diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/DateFilters.js b/services/static-webserver/client/source/class/osparc/filter/DateFilters.js similarity index 86% rename from services/static-webserver/client/source/class/osparc/desktop/credits/DateFilters.js rename to services/static-webserver/client/source/class/osparc/filter/DateFilters.js index 35d5e9d8dff1..93073bbc4533 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/DateFilters.js +++ b/services/static-webserver/client/source/class/osparc/filter/DateFilters.js @@ -5,13 +5,13 @@ * Authors: Ignacio Pascual (ignapas) */ -qx.Class.define("osparc.desktop.credits.DateFilters", { +qx.Class.define("osparc.filter.DateFilters", { extend: qx.ui.core.Widget, construct() { this.base(arguments); - this._setLayout(new qx.ui.layout.HBox(7)); - this._buildLayout(); + this._setLayout(new qx.ui.layout.HBox(6)); + this.__buildLayout(); }, events: { @@ -19,7 +19,7 @@ qx.Class.define("osparc.desktop.credits.DateFilters", { }, members: { - _buildLayout() { + __buildLayout() { this._removeAll(); // Range defaults: today @@ -76,7 +76,7 @@ qx.Class.define("osparc.desktop.credits.DateFilters", { lastYear.setYear(today.getFullYear() - 1); this.__from.setValue(lastYear); this.__until.setValue(today); - }) + }); this._add(lastYearBtn); }, @@ -86,13 +86,13 @@ qx.Class.define("osparc.desktop.credits.DateFilters", { container.add(lbl); const datepicker = new qx.ui.form.DateField(); datepicker.setValue(initDate ? initDate : new Date()); - datepicker.addListener("changeValue", e => this._changeHandler(e)); + datepicker.addListener("changeValue", e => this.__changeHandler(e)); container.add(datepicker); this._add(container); return datepicker; }, - _changeHandler(e) { + __changeHandler: function(e) { const timestampFrom = this.__from.getValue().getTime(); const timestampUntil = this.__until.getValue().getTime(); if (timestampFrom > timestampUntil) { @@ -105,21 +105,17 @@ qx.Class.define("osparc.desktop.credits.DateFilters", { } return; } - const from = osparc.utils.Utils.formatDateYyyyMmDd(this.__from.getValue()); - const until = osparc.utils.Utils.formatDateYyyyMmDd(this.__until.getValue()); - this.fireDataEvent("change", { - from, - until - }); + const value = this.getValue(); + this.fireDataEvent("change", value); }, - getValue() { - const from = osparc.utils.Utils.formatDateYyyyMmDd(this.__from.getValue()) - const until = osparc.utils.Utils.formatDateYyyyMmDd(this.__until.getValue()) + getValue: function() { + const from = osparc.utils.Utils.formatDateYyyyMmDd(this.__from.getValue()); + const until = osparc.utils.Utils.formatDateYyyyMmDd(this.__until.getValue()); return { from, until - } + }; } } }); diff --git a/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js b/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js index 75657b36fe1f..da0fa30810e4 100644 --- a/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js +++ b/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js @@ -90,6 +90,19 @@ qx.Class.define("osparc.navigation.NavigationBar", { minHeight: 30 }, + RIGHT_BUTTON_POS: { + AVATARS: 0, + EXPIRATION: 1, + TASKS: 2, + JOBS: 3, + NOTIFICATIONS: 4, + HELP: 5, + CREDITS: 6, + LOGIN: 7, + USER_MENU: 8, + USER_MENU_COMPACT: 9, + }, + RIGHT_BUTTON_OPTS: { cursor: "pointer", alignX: "center", @@ -259,7 +272,7 @@ qx.Class.define("osparc.navigation.NavigationBar", { alignY: "middle", visibility: "excluded", }); - this.getChildControl("right-items").add(control); + this.getChildControl("right-items").addAt(control, this.self().RIGHT_BUTTON_POS.AVATARS); break; } case "expiration-icon": { @@ -285,38 +298,38 @@ qx.Class.define("osparc.navigation.NavigationBar", { return "excluded"; } }); - this.getChildControl("right-items").add(control); + this.getChildControl("right-items").addAt(control, this.self().RIGHT_BUTTON_POS.EXPIRATION); break; } case "tasks-button": control = new osparc.task.TasksButton().set({ ...this.self().RIGHT_BUTTON_OPTS }); - this.getChildControl("right-items").add(control); + this.getChildControl("right-items").addAt(control, this.self().RIGHT_BUTTON_POS.TASKS); break; case "jobs-button": control = new osparc.jobs.JobsButton().set({ ...this.self().RIGHT_BUTTON_OPTS }); - this.getChildControl("right-items").add(control); + this.getChildControl("right-items").addAt(control, this.self().RIGHT_BUTTON_POS.JOBS); break; case "notifications-button": control = new osparc.notification.NotificationsButton().set({ ...this.self().RIGHT_BUTTON_OPTS }); - this.getChildControl("right-items").add(control); + this.getChildControl("right-items").addAt(control, this.self().RIGHT_BUTTON_POS.NOTIFICATIONS); break; case "help-button": control = new osparc.support.SupportButton().set({ ...this.self().RIGHT_BUTTON_OPTS }); - this.getChildControl("right-items").add(control); + this.getChildControl("right-items").addAt(control, this.self().RIGHT_BUTTON_POS.HELP); break; case "credits-button": control = new osparc.desktop.credits.CreditsIndicatorButton().set({ ...this.self().RIGHT_BUTTON_OPTS }); - this.getChildControl("right-items").add(control); + this.getChildControl("right-items").addAt(control, this.self().RIGHT_BUTTON_POS.CREDITS); break; case "log-in-button": { control = this.__createLoginBtn().set({ @@ -327,20 +340,20 @@ qx.Class.define("osparc.navigation.NavigationBar", { authData.bind("guest", control, "visibility", { converter: isGuest => isGuest ? "visible" : "excluded" }); - this.getChildControl("right-items").add(control); + this.getChildControl("right-items").addAt(control, this.self().RIGHT_BUTTON_POS.LOGIN); break; } case "user-menu": control = new osparc.navigation.UserMenuButton(); control.populateMenu(); control.set(this.self().BUTTON_OPTIONS); - this.getChildControl("right-items").add(control); + this.getChildControl("right-items").addAt(control, this.self().RIGHT_BUTTON_POS.USER_MENU); break; case "user-menu-compact": control = new osparc.navigation.UserMenuButton(); control.populateMenuCompact(); control.set(this.self().BUTTON_OPTIONS); - this.getChildControl("right-items").add(control); + this.getChildControl("right-items").addAt(control, this.self().RIGHT_BUTTON_POS.USER_MENU_COMPACT); break; } return control || this.base(arguments, id); diff --git a/services/static-webserver/client/source/class/osparc/store/Data.js b/services/static-webserver/client/source/class/osparc/store/Data.js index a1fd4f47add1..09971d1181b6 100644 --- a/services/static-webserver/client/source/class/osparc/store/Data.js +++ b/services/static-webserver/client/source/class/osparc/store/Data.js @@ -250,6 +250,24 @@ qx.Class.define("osparc.store.Data", { return osparc.data.Resources.fetch("storagePaths", "multiDownload", params); }, + searchFiles: function(searchText, modifiedAtFrom = null, modifiedAtTo = null) { + const params = { + url: { + locationId: 0, + }, + data: { + filters: { + namePattern: "*" + searchText + "*", + } + } + }; + if (modifiedAtFrom && modifiedAtTo) { + params.data["filters"]["modifiedAtFrom"] = modifiedAtFrom; + params.data["filters"]["modifiedAtTo"] = modifiedAtTo; + } + return osparc.data.Resources.fetch("storagePaths", "searchFiles", params); + }, + deleteFiles: function(paths) { if (!osparc.data.Permissions.getInstance().canDo("study.node.data.delete", true)) { return null; 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 b421d301c1ec..c71816377da0 100644 --- a/services/static-webserver/client/source/class/osparc/store/Store.js +++ b/services/static-webserver/client/source/class/osparc/store/Store.js @@ -87,6 +87,7 @@ qx.Class.define("osparc.store.Store", { "searchTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES, "searchPublicTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES, "searchFunctions", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS, + "searchFiles", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FILES, ], init: "studiesAndFolders", nullable: false, diff --git a/services/static-webserver/client/source/class/osparc/store/StreamTasks.js b/services/static-webserver/client/source/class/osparc/store/StreamTasks.js new file mode 100644 index 000000000000..1152a90c33db --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/store/StreamTasks.js @@ -0,0 +1,82 @@ +/* ************************************************************************ + + 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.store.StreamTasks", { + extend: qx.core.Object, + type: "singleton", + + properties: { + tasks: { + check: "Object", + init: {}, + nullable: false, + } + }, + + statics: { + actionToInternalId: function(action, params) { + return action + "_" + JSON.stringify(params); + }, + }, + + members: { + createStreamTask: function(action, params, streamPromise, interval) { + return streamPromise + .then(streamData => { + if (!("stream_href" in streamData)) { + throw new Error("Stream href missing"); + } + return this.__addStreamTask(action, params, streamData, interval); + }) + .catch(err => Promise.reject(err)); + }, + + getStreamTask: function(action, params) { + const internalId = osparc.store.StreamTasks.actionToInternalId(action, params); + const tasks = this.getTasks(); + return tasks[internalId] || null; + }, + + abortStreamTasks: function() { + const tasks = this.getTasks(); + Object.values(tasks).forEach(stream => { + if (stream.isEnd() === false) { + stream.abortRequested(); + } + // and remove + this.__removeStreamTask(stream); + }); + }, + + __addStreamTask: function(action, params, streamData, interval) { + const internalId = osparc.store.StreamTasks.actionToInternalId(action, params); + const stream = new osparc.data.StreamTask(streamData, interval); + stream.addListener("taskAborted", () => this.__removeStreamTask(stream), this); + const tasks = this.getTasks(); + tasks[internalId] = stream; + return stream; + }, + + __removeStreamTask: function(stream) { + const tasks = this.getTasks(); + const internalId = Object.keys(tasks).find(key => tasks[key] === stream); + if (internalId) { + delete tasks[internalId]; + } + }, + } +}); diff --git a/services/static-webserver/client/source/class/osparc/support/ConversationPage.js b/services/static-webserver/client/source/class/osparc/support/ConversationPage.js index 48474a66c84b..a5932ab0c3ee 100644 --- a/services/static-webserver/client/source/class/osparc/support/ConversationPage.js +++ b/services/static-webserver/client/source/class/osparc/support/ConversationPage.js @@ -68,7 +68,9 @@ qx.Class.define("osparc.support.ConversationPage", { backgroundColor: "transparent" }); control.addListener("execute", () => { - this.getConversation().setReadBy(true); + if (this.getConversation()) { + this.getConversation().setReadBy(true); + } this.setConversation(null); this.fireEvent("backToConversations"); }); diff --git a/services/static-webserver/client/source/class/osparc/wrapper/BookACallIframe.js b/services/static-webserver/client/source/class/osparc/wrapper/BookACallIframe.js index 5f674a3e0304..17354f11104d 100644 --- a/services/static-webserver/client/source/class/osparc/wrapper/BookACallIframe.js +++ b/services/static-webserver/client/source/class/osparc/wrapper/BookACallIframe.js @@ -39,6 +39,10 @@ qx.Class.define("osparc.wrapper.BookACallIframe", { }, statics: { + NAME: "easy!appointments", + VERSION: "1.5.2", + URL: "https://easyappointments.org/", + DEV_SERVICE_URL: "http://10.43.103.145/index.php", }, diff --git a/services/static-webserver/client/source/class/osparc/wrapper/RocketPreview.js b/services/static-webserver/client/source/class/osparc/wrapper/RocketPreview.js index 668b7195312c..3ddd243ab335 100644 --- a/services/static-webserver/client/source/class/osparc/wrapper/RocketPreview.js +++ b/services/static-webserver/client/source/class/osparc/wrapper/RocketPreview.js @@ -48,6 +48,10 @@ qx.Class.define("osparc.wrapper.RocketPreview", { }, statics: { + NAME: "Sim4Life", + VERSION: "latest", + URL: "https://sim4life.swiss/", + INDEX_HTML: "rocketPreview/build/index.html", /** diff --git a/services/storage/src/simcore_service_storage/api/_worker_tasks/_simcore_s3.py b/services/storage/src/simcore_service_storage/api/_worker_tasks/_simcore_s3.py index 85b3912c0221..549774bb1026 100644 --- a/services/storage/src/simcore_service_storage/api/_worker_tasks/_simcore_s3.py +++ b/services/storage/src/simcore_service_storage/api/_worker_tasks/_simcore_s3.py @@ -162,7 +162,11 @@ async def search( project_id=project_id, name_pattern=name_pattern, modified_at=modified_at, + limit=1, # NOTE: yield items as they come ): + if not items: + continue + data = [ TaskStreamItem( data=SearchResultItem(