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 1332df084a9..830aff303fc 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -1237,7 +1237,23 @@ qx.Class.define("osparc.data.Resources", { delete: { method: "DELETE", url: statics.API + "/tags/{tagId}" - } + }, + getAccessRights: { + method: "GET", + url: statics.API + "/tags/{tagId}/groups" + }, + putAccessRights: { + method: "PUT", + url: statics.API + "/tags/{tagId}/groups/{groupId}" + }, + postAccessRights: { + method: "POST", + url: statics.API + "/tags/{tagId}/groups/{groupId}" + }, + deleteAccessRights: { + method: "DELETE", + url: statics.API + "/tags/{tagId}/groups/{groupId}" + }, } } }; diff --git a/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/GeneralPage.js b/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/GeneralPage.js index dffafeabc72..1c218487d31 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/GeneralPage.js +++ b/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/GeneralPage.js @@ -147,6 +147,7 @@ qx.Class.define("osparc.desktop.preferences.pages.GeneralPage", { allowGrowX: false, enabled: true }); + const preferences = osparc.Preferences.getInstance(); preferences.bind("lowDiskSpaceThreshold", diskUsageSpinner, "value"); diskUsageSpinner.addListener("changeValue", e => osparc.Preferences.patchPreferenceField("lowDiskSpaceThreshold", diskUsageSpinner, e.getData())); diff --git a/services/static-webserver/client/source/class/osparc/file/FilePicker.js b/services/static-webserver/client/source/class/osparc/file/FilePicker.js index 67d67c8455a..0a04558952d 100644 --- a/services/static-webserver/client/source/class/osparc/file/FilePicker.js +++ b/services/static-webserver/client/source/class/osparc/file/FilePicker.js @@ -545,7 +545,8 @@ qx.Class.define("osparc.file.FilePicker", { flex: 1 }); treeFolderLayout.add(treeLayout, 0); - const folderViewer = new osparc.file.FolderViewer(); + const allowMultiselection = false; + const folderViewer = new osparc.file.FolderViewer(allowMultiselection); treeFolderLayout.add(folderViewer, 1); filesTree.addListener("selectionChanged", () => { diff --git a/services/static-webserver/client/source/class/osparc/file/FileTreeItem.js b/services/static-webserver/client/source/class/osparc/file/FileTreeItem.js index 45fe07c10ce..5e7a4a02236 100644 --- a/services/static-webserver/client/source/class/osparc/file/FileTreeItem.js +++ b/services/static-webserver/client/source/class/osparc/file/FileTreeItem.js @@ -45,6 +45,11 @@ qx.Class.define("osparc.file.FileTreeItem", { construct: function() { this.base(arguments); + this.set({ + indent: 12, // defaults to 19, + decorator: "rounded", + }); + // create a date format like "Oct. 19, 2018 11:31 AM" this._dateFormat = new qx.util.format.DateFormat( qx.locale.Date.getDateFormat("medium") + " " + diff --git a/services/static-webserver/client/source/class/osparc/file/FolderContent.js b/services/static-webserver/client/source/class/osparc/file/FolderContent.js new file mode 100644 index 00000000000..7424f89ed45 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/file/FolderContent.js @@ -0,0 +1,283 @@ +/* ************************************************************************ + + 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.file.FolderContent", { + extend: qx.ui.container.Stack, + + construct: function() { + this.base(arguments); + + this.getChildControl("icons-layout"); + this.getChildControl("table"); + }, + + properties: { + folder: { + check: "qx.core.Object", + init: null, + nullable: true, + event: "changeFolder", + apply: "__applyFolder" + }, + + mode: { + check: ["list", "icons"], + init: "icons", + nullable: false, + event: "changeMode", + apply: "__reloadFolderContent" + }, + + multiSelect: { + check: "Boolean", + init: false, + nullable: false, + event: "changeMultiSelect", + apply: "__reloadFolderContent" + }, + }, + + events: { + "selectionChanged": "qx.event.type.Data", // tap + "multiSelectionChanged": "qx.event.type.Data", // tap + "itemSelected": "qx.event.type.Data", // dbltap + "requestDatasetFiles": "qx.event.type.Data", + }, + + statics: { + getItemButton: function() { + const item = new qx.ui.form.ToggleButton().set({ + iconPosition: "top", + width: 100, + height: 80, + padding: 2 + }); + item.getChildControl("label").set({ + font: "text-12", + rich: true, + textAlign: "center", + maxWidth: 100, + maxHeight: 33 // two lines + }); + osparc.utils.Utils.setIdToWidget(item, "FolderViewerItem"); + return item; + }, + + T_POS: { + TYPE: 0, + NAME: 1, + DATE: 2, + SIZE: 3, + ID: 4 + } + }, + + members: { + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "table": { + const tableModel = new qx.ui.table.model.Simple(); + tableModel.setColumns([ + "", + this.tr("Name"), + this.tr("Date Modified"), + this.tr("Size"), + this.tr("Id") + ]); + control = new osparc.ui.table.Table(tableModel, { + // tableColumnModel: obj => new qx.ui.table.columnmodel.Resize(obj), + initiallyHiddenColumns: [this.self().T_POS.ID] + }); + control.getTableColumnModel().setDataCellRenderer(this.self().T_POS.TYPE, new qx.ui.table.cellrenderer.Image()); + control.setColumnWidth(this.self().T_POS.TYPE, 30); + control.setColumnWidth(this.self().T_POS.NAME, 360); + control.setColumnWidth(this.self().T_POS.DATE, 170); + control.setColumnWidth(this.self().T_POS.SIZE, 70); + this.bind("mode", control, "visibility", { + converter: mode => mode === "list" ? "visible" : "excluded" + }); + const scroll = new qx.ui.container.Scroll(); + scroll.add(control); + this.add(scroll); + break; + } + case "icons-layout": { + control = new qx.ui.container.Composite(new qx.ui.layout.Flow(5, 5)); + osparc.utils.Utils.setIdToWidget(control, "FolderViewerIconsContent"); + this.bind("mode", control, "visibility", { + converter: mode => mode === "icons" ? "visible" : "excluded" + }); + const scroll = new qx.ui.container.Scroll(); + scroll.add(control); + this.add(scroll); + break; + } + } + return control || this.base(arguments, id); + }, + + __convertEntries: function(content) { + const datas = []; + content.forEach(entry => { + const data = { + icon: entry.getIcon ? entry.getIcon() : this.__getIcon(entry), + label: entry.getLabel(), + lastModified: entry.getLastModified ? osparc.utils.Utils.formatDateAndTime(new Date(entry.getLastModified())) : "", + size: entry.getSize ? osparc.utils.Utils.bytesToSize(entry.getSize()) : "", + itemId: entry.getItemId ? entry.getItemId() : null, + entry: entry, + }; + datas.push(data); + }); + const items = []; + if (this.getMode() === "list") { + datas.forEach(data => { + const row = []; + row.push(data["icon"]); + row.push(data["label"]); + row.push(data["lastModified"]); + row.push(data["size"]); + if (data["itemId"]) { + row.push(data["itemId"]); + } + row.entry = data["entry"]; + items.push(row); + }); + } else if (this.getMode() === "icons") { + datas.forEach(data => { + let toolTip = data["label"]; + if (data["size"]) { + toolTip += "
" + data["size"]; + } + if (data["lastModified"]) { + toolTip += "
" + data["lastModified"]; + } + const item = this.self().getItemButton().set({ + label: data["label"], + icon: data["icon"], + toolTipText: toolTip + }); + const icon = item.getChildControl("icon", true); + if (icon.getSource() === "@FontAwesome5Solid/circle-notch/12") { + icon.setPadding(0); + icon.setMarginRight(4); + icon.getContentElement().addClass("rotate"); + } + if (data["itemId"]) { + item.itemId = data["itemId"]; + } + this.__attachListenersToItems(item, data["entry"]); + items.push(item); + }); + } + return items; + }, + + __getIcon: function(entry) { + return osparc.file.FilesTree.isDir(entry) ? "@MaterialIcons/folder" : "@MaterialIcons/insert_drive_file"; + }, + + __getEntries: function() { + if (this.getFolder()) { + const children = this.getFolder().getChildren().toArray(); + return this.__convertEntries(children); + } + return []; + }, + + __applyFolder: function(folder) { + if (folder) { + if (folder.getLoaded && !folder.getLoaded()) { + this.fireDataEvent("requestDatasetFiles", { + locationId: folder.getLocation(), + datasetId: folder.getPath() + }); + } + + folder.getChildren().addListener("change", () => { + this.__reloadFolderContent(); + }, this); + } + + this.__reloadFolderContent(); + }, + + __reloadFolderContent: function() { + const entries = this.__getEntries(); + if (this.getMode() === "list") { + const table = this.getChildControl("table"); + table.setData(entries); + this.__attachListenersTotable(table); + } else if (this.getMode() === "icons") { + const iconsLayout = this.getChildControl("icons-layout"); + iconsLayout.removeAll(); + const iconsGroup = new qx.ui.form.RadioGroup().set({ + allowEmptySelection: true + }); + entries.forEach(entry => { + if (!this.isMultiSelect()) { + iconsGroup.add(entry); + } + iconsLayout.add(entry); + }); + } + this.setSelection([this.getSelectables()[this.getMode() === "icons" ? 0 : 1]]); + }, + + __itemTapped: function(item) { + if (this.isMultiSelect()) { + this.fireDataEvent("multiSelectionChanged", item); + } else { + this.fireDataEvent("selectionChanged", item); + } + }, + + __itemDblTapped: function(item) { + this.fireDataEvent("itemSelected", item); + if (osparc.file.FilesTree.isDir(item)) { + this.setFolder(item); + } + }, + + __attachListenersToItems: function(btn, entry) { + btn.addListener("tap", () => { + this.__itemTapped(entry); + }, this); + btn.addListener("dbltap", () => { + this.__itemDblTapped(entry); + }, this); + }, + + __attachListenersTotable: function(table) { + table.addListener("cellTap", e => { + const selectedRow = e.getRow(); + const rowData = table.getTableModel().getRowData(selectedRow); + if ("entry" in rowData) { + this.__itemTapped(rowData.entry); + } + }, this); + table.addListener("cellDbltap", e => { + const selectedRow = e.getRow(); + const rowData = table.getTableModel().getRowData(selectedRow); + if ("entry" in rowData) { + this.__itemDblTapped(rowData.entry); + } + }, this); + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/file/FolderViewer.js b/services/static-webserver/client/source/class/osparc/file/FolderViewer.js index 26fb4433bf3..218c3a90a49 100644 --- a/services/static-webserver/client/source/class/osparc/file/FolderViewer.js +++ b/services/static-webserver/client/source/class/osparc/file/FolderViewer.js @@ -22,7 +22,7 @@ qx.Class.define("osparc.file.FolderViewer", { extend: qx.ui.core.Widget, - construct: function() { + construct: function(allowMultiselection = true) { this.base(arguments); this._setLayout(new qx.ui.layout.VBox(10)); @@ -32,10 +32,13 @@ qx.Class.define("osparc.file.FolderViewer", { const folderUpBtn = this.getChildControl("folder-up"); folderUpBtn.addListener("execute", () => this.fireDataEvent("folderUp", this.getFolder()), this); this.getChildControl("folder-path"); - this.getChildControl("view-options-icons"); - this.getChildControl("view-options-list"); - this.getChildControl("icons-layout"); - this.getChildControl("table"); + let multiSelectButton = null; + if (allowMultiselection) { + multiSelectButton = this.getChildControl("multi-select-button"); + } + const iconsButton = this.getChildControl("view-options-icons"); + const listButton = this.getChildControl("view-options-list"); + const folderContent = this.getChildControl("folder-content"); this.bind("folder", this.getChildControl("folder-up"), "enabled", { converter: folder => Boolean(folder && folder.getPathLabel && folder.getPathLabel().length > 1) @@ -44,6 +47,18 @@ qx.Class.define("osparc.file.FolderViewer", { this.bind("folder", this.getChildControl("folder-path"), "value", { converter: folder => folder ? folder.getPathLabel().join(" / ") : this.tr("Select folder") }); + + this.bind("folder", folderContent, "folder"); + + if (allowMultiselection) { + multiSelectButton.addListener("changeValue", e => folderContent.setMultiSelect(e.getData())); + } + iconsButton.addListener("execute", () => folderContent.setMode("icons")); + listButton.addListener("execute", () => folderContent.setMode("list")); + + folderContent.addListener("selectionChanged", e => this.fireDataEvent("selectionChanged", e.getData())); + folderContent.addListener("itemSelected", e => this.fireDataEvent("itemSelected", e.getData())); + folderContent.addListener("requestDatasetFiles", e => this.fireDataEvent("requestDatasetFiles", e.getData())); }, properties: { @@ -52,16 +67,7 @@ qx.Class.define("osparc.file.FolderViewer", { init: null, nullable: true, event: "changeFolder", - apply: "__applyFolder" }, - - mode: { - check: ["list", "icons"], - init: "icons", - nullable: false, - event: "changeMode", - apply: "__reloadFolderContent" - } }, events: { @@ -71,33 +77,6 @@ qx.Class.define("osparc.file.FolderViewer", { "requestDatasetFiles": "qx.event.type.Data" }, - statics: { - getItemButton: function() { - const item = new qx.ui.form.ToggleButton().set({ - iconPosition: "top", - width: 100, - height: 80, - padding: 3 - }); - item.getChildControl("label").set({ - rich: true, - textAlign: "center", - maxWidth: 100, - maxHeight: 31 - }); - osparc.utils.Utils.setIdToWidget(item, "FolderViewerItem"); - return item; - }, - - T_POS: { - TYPE: 0, - NAME: 1, - DATE: 2, - SIZE: 3, - ID: 4 - } - }, - members: { _createChildControlImpl: function(id) { let control; @@ -128,6 +107,14 @@ qx.Class.define("osparc.file.FolderViewer", { }); break; } + case "multi-select-button": + control = new qx.ui.form.ToggleButton(this.tr("Multiselect")).set({ + value: false, + marginRight: 10, + }); + const header = this.getChildControl("header"); + header.addAt(control, 2); + break; case "view-options-rgroup": control = new qx.ui.form.RadioGroup(); break; @@ -135,203 +122,27 @@ qx.Class.define("osparc.file.FolderViewer", { control = new qx.ui.form.ToggleButton(null, "@MaterialIcons/apps/18"); const group = this.getChildControl("view-options-rgroup"); group.add(control); - control.addListener("execute", () => { - this.setMode("icons"); - }); const header = this.getChildControl("header"); - header.addAt(control, 2); + header.addAt(control, 3); break; } case "view-options-list": { control = new qx.ui.form.ToggleButton(null, "@MaterialIcons/reorder/18"); const group = this.getChildControl("view-options-rgroup"); group.add(control); - control.addListener("execute", () => { - this.setMode("list"); - }); const header = this.getChildControl("header"); - header.addAt(control, 3); + header.addAt(control, 4); break; } - case "content-stack": { - control = new qx.ui.container.Stack(); + case "folder-content": { + control = new osparc.file.FolderContent(); this._add(control, { flex: 1 }); break; } - case "table": { - const tableModel = new qx.ui.table.model.Simple(); - tableModel.setColumns([ - "", - this.tr("Name"), - this.tr("Date Modified"), - this.tr("Size"), - this.tr("Id") - ]); - control = new osparc.ui.table.Table(tableModel, { - // tableColumnModel: obj => new qx.ui.table.columnmodel.Resize(obj), - initiallyHiddenColumns: [this.self().T_POS.ID] - }); - control.getTableColumnModel().setDataCellRenderer(this.self().T_POS.TYPE, new qx.ui.table.cellrenderer.Image()); - control.setColumnWidth(this.self().T_POS.TYPE, 30); - control.setColumnWidth(this.self().T_POS.NAME, 360); - control.setColumnWidth(this.self().T_POS.DATE, 170); - control.setColumnWidth(this.self().T_POS.SIZE, 70); - this.bind("mode", control, "visibility", { - converter: mode => mode === "list" ? "visible" : "excluded" - }); - const scroll = new qx.ui.container.Scroll(); - scroll.add(control); - this.getChildControl("content-stack").add(scroll); - break; - } - case "icons-layout": { - control = new qx.ui.container.Composite(new qx.ui.layout.Flow(5, 5)); - osparc.utils.Utils.setIdToWidget(control, "FolderViewerIconsContent"); - this.bind("mode", control, "visibility", { - converter: mode => mode === "icons" ? "visible" : "excluded" - }); - const scroll = new qx.ui.container.Scroll(); - scroll.add(control); - this.getChildControl("content-stack").add(scroll); - break; - } } return control || this.base(arguments, id); }, - - __convertEntries: function(content) { - const items = []; - if (this.getMode() === "list") { - content.forEach(entry => { - const row = []; - row.push(entry.getIcon ? entry.getIcon() : this.__getIcon(entry)); - row.push(entry.getLabel()); - row.push(entry.getLastModified ? osparc.utils.Utils.formatDateAndTime(new Date(entry.getLastModified())) : ""); - row.push(entry.getSize ? osparc.utils.Utils.bytesToSize(entry.getSize()) : ""); - if (entry.getItemId) { - row.push(entry.getItemId()); - } - row.entry = entry; - items.push(row); - }); - } else if (this.getMode() === "icons") { - content.forEach(entry => { - let tt = entry.getLabel(); - if (entry.getSize) { - tt += "
" + osparc.utils.Utils.bytesToSize(entry.getSize()); - } - if (entry.getLastModified) { - tt += "
" + osparc.utils.Utils.formatDateAndTime(new Date(entry.getLastModified())); - } - const item = this.self().getItemButton().set({ - label: entry.getLabel(), - icon: entry.getIcon ? entry.getIcon() : this.__getIcon(entry), - toolTipText: tt - }); - const icon = item.getChildControl("icon", true); - if (icon.getSource() === "@FontAwesome5Solid/circle-notch/12") { - icon.setPadding(0); - icon.setMarginRight(4); - icon.getContentElement().addClass("rotate"); - } - - if (entry.getItemId) { - item.itemId = entry.getItemId(); - this.__attachListenersToItems(item, entry); - } - items.push(item); - }); - } - return items; - }, - - __getIcon: function(entry) { - return osparc.file.FilesTree.isDir(entry) ? "@MaterialIcons/folder" : "@MaterialIcons/insert_drive_file"; - }, - - __getEntries: function() { - if (this.getFolder()) { - const children = this.getFolder().getChildren().toArray(); - return this.__convertEntries(children); - } - return []; - }, - - __applyFolder: function(folder) { - if (folder) { - if (folder.getLoaded && !folder.getLoaded()) { - this.fireDataEvent("requestDatasetFiles", { - locationId: folder.getLocation(), - datasetId: folder.getPath() - }); - } - - folder.getChildren().addListener("change", () => { - this.__reloadFolderContent(); - }, this); - } - - this.__reloadFolderContent(); - }, - - __reloadFolderContent: function() { - const entries = this.__getEntries(); - if (this.getMode() === "list") { - const table = this.getChildControl("table"); - table.setData(entries); - this.__attachListenersTotable(table); - } else if (this.getMode() === "icons") { - const iconsLayout = this.getChildControl("icons-layout"); - iconsLayout.removeAll(); - const iconsGroup = new qx.ui.form.RadioGroup().set({ - allowEmptySelection: true - }); - entries.forEach(entry => { - iconsGroup.add(entry); - iconsLayout.add(entry); - }); - } - const stack = this.getChildControl("content-stack"); - stack.setSelection([stack.getSelectables()[this.getMode() === "icons" ? 0 : 1]]); - }, - - __itemTapped: function(item) { - this.fireDataEvent("selectionChanged", item); - }, - - __itemDblTapped: function(item) { - this.fireDataEvent("itemSelected", item); - if (osparc.file.FilesTree.isDir(item)) { - this.setFolder(item); - } - }, - - __attachListenersToItems: function(btn, entry) { - btn.addListener("tap", () => { - this.__itemTapped(entry); - }, this); - btn.addListener("dbltap", () => { - this.__itemDblTapped(entry); - }, this); - }, - - __attachListenersTotable: function(table) { - table.addListener("cellTap", e => { - const selectedRow = e.getRow(); - const rowData = table.getTableModel().getRowData(selectedRow); - if ("entry" in rowData) { - this.__itemTapped(rowData.entry); - } - }, this); - table.addListener("cellDbltap", e => { - const selectedRow = e.getRow(); - const rowData = table.getTableModel().getRowData(selectedRow); - if ("entry" in rowData) { - this.__itemDblTapped(rowData.entry); - } - }, this); - } } }); diff --git a/services/static-webserver/client/source/class/osparc/store/Tags.js b/services/static-webserver/client/source/class/osparc/store/Tags.js index c07e9d8d8ae..748300f0cb9 100644 --- a/services/static-webserver/client/source/class/osparc/store/Tags.js +++ b/services/static-webserver/client/source/class/osparc/store/Tags.js @@ -45,6 +45,7 @@ qx.Class.define("osparc.store.Tags", { tagsData.forEach(tagData => { const tag = this.__addToCache(tagData); tags.push(tag); + this.fetchAccessRights(tag); }); return tags; }); @@ -54,6 +55,10 @@ qx.Class.define("osparc.store.Tags", { return this.tagsCached; }, + getTag: function(tagId = null) { + return this.tagsCached.find(f => f.getTagId() === tagId); + }, + postTag: function(newTagData) { const params = { data: newTagData @@ -97,8 +102,15 @@ qx.Class.define("osparc.store.Tags", { .catch(console.error); }, - getTag: function(tagId = null) { - return this.tagsCached.find(f => f.getTagId() === tagId); + fetchAccessRights: function(tag) { + const params = { + url: { + "tagId": tag.getTagId() + } + }; + osparc.data.Resources.fetch("tags", "getAccessRights", params) + .then(accessRights => tag.setAccessRights(accessRights)) + .catch(err => console.error(err)); }, __addToCache: function(tagData) {