diff --git a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js index 0e8465a9a646..d2ceabbcfed4 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js @@ -52,7 +52,7 @@ qx.Class.define("osparc.dashboard.CardBase", { statics: { SHARE_ICON: "@FontAwesome5Solid/share-alt/13", SHARED_USER: "@FontAwesome5Solid/user/13", - SHARED_SUPPORT: "@FontAwesome5Solid/question/13", + SHARED_SUPPORT: "@FontAwesome5Solid/question-circle/13", SHARED_ORGS: "@FontAwesome5Solid/users/13", SHARED_ALL: "@FontAwesome5Solid/globe/13", PERM_READ: "@FontAwesome5Solid/eye/13", 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 4b5c8181db9f..5a779ef93a5d 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -1660,6 +1660,8 @@ qx.Class.define("osparc.data.Resources", { status = errorData.status; if (errorData["support_id"]) { supportId = errorData["support_id"]; + } else if (errorData["supportId"]) { + supportId = errorData["supportId"]; } } else { const req = e.getRequest(); diff --git a/services/static-webserver/client/source/class/osparc/data/model/Group.js b/services/static-webserver/client/source/class/osparc/data/model/Group.js index 174e1081ab84..11cc663337da 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Group.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Group.js @@ -96,7 +96,7 @@ qx.Class.define("osparc.data.model.Group", { statics: { getProperties: function() { return Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.Group)); - } + }, }, members: { diff --git a/services/static-webserver/client/source/class/osparc/desktop/organizations/OrganizationsList.js b/services/static-webserver/client/source/class/osparc/desktop/organizations/OrganizationsList.js index dd488f9b3287..2a3c89f00684 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/organizations/OrganizationsList.js +++ b/services/static-webserver/client/source/class/osparc/desktop/organizations/OrganizationsList.js @@ -69,6 +69,11 @@ qx.Class.define("osparc.desktop.organizations.OrganizationsList", { statics: { sortOrganizations: function(a, b) { + const collabTypeOrder = osparc.store.Groups.COLLAB_TYPE_ORDER; + const typeDiff = collabTypeOrder.indexOf(a.getGroupType()) - collabTypeOrder.indexOf(b.getGroupType()); + if (typeDiff !== 0) { + return typeDiff; + } const sorted = osparc.share.Collaborators.sortByAccessRights(a.getAccessRights(), b.getAccessRights()); if (sorted !== 0) { return sorted; @@ -84,7 +89,7 @@ qx.Class.define("osparc.desktop.organizations.OrganizationsList", { getOrgModel: function(orgId) { let org = null; this.__orgsModel.forEach(orgModel => { - if (orgModel.getGroupId() === parseInt(orgId)) { + if ("getGroupId" in orgModel && orgModel.getGroupId() === parseInt(orgId)) { org = orgModel; } }); @@ -141,6 +146,10 @@ qx.Class.define("osparc.desktop.organizations.OrganizationsList", { ctrl.bindProperty("description", "subtitle", null, item, id); ctrl.bindProperty("groupMembers", "groupMembers", null, item, id); ctrl.bindProperty("accessRights", "accessRights", null, item, id); + // handle separator + ctrl.bindProperty("isSeparator", "enabled", { + converter: val => !val // disable clicks on separator + }, item, id); }, configureItem: item => { item.subscribeToFilterGroup("organizationsList"); @@ -160,6 +169,15 @@ qx.Class.define("osparc.desktop.organizations.OrganizationsList", { const orgKey = e.getData(); this.__deleteOrganization(orgKey); }); + item.addListener("changeEnabled", e => { + if (!e.getData()) { + item.set({ + minHeight: 1, + maxHeight: 1, + decorator: "separator-strong", + }); + } + }); } }); @@ -184,7 +202,28 @@ qx.Class.define("osparc.desktop.organizations.OrganizationsList", { const groupsStore = osparc.store.Groups.getInstance(); const orgs = Object.values(groupsStore.getOrganizations()); orgs.sort(this.self().sortOrganizations); - orgs.forEach(org => orgsModel.append(org)); + + // insert a separator between product and non-product groups + const productGroup = [ + osparc.store.Groups.COLLAB_TYPE.EVERYONE, + osparc.store.Groups.COLLAB_TYPE.SUPPORT, + ]; + const hasProductGroup = orgs.some(org => productGroup.includes(org.getGroupType())); + const hasNonProductGroup = orgs.some(org => !productGroup.includes(org.getGroupType())); + let separatorInserted = false; + orgs.forEach(org => { + const isProductGroup = productGroup.includes(org.getGroupType()); + // Only insert separator if both sides exist + if (!isProductGroup && hasProductGroup && hasNonProductGroup && !separatorInserted) { + const separator = { + isSeparator: true + }; + orgsModel.append(qx.data.marshal.Json.createModel(separator)); + separatorInserted = true; + } + orgsModel.append(org); + }); + this.setOrganizationsLoaded(true); if (orgId) { this.fireDataEvent("organizationSelected", orgId); diff --git a/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js b/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js index d003e056d2b9..8ef76e88b807 100644 --- a/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js +++ b/services/static-webserver/client/source/class/osparc/filter/CollaboratorToggleButton.js @@ -56,6 +56,10 @@ qx.Class.define("osparc.filter.CollaboratorToggleButton", { } } this.setIcon(iconPath); + this.getChildControl("icon").set({ + width: 17, // align with widest icon: "users" + scale: true, + }); this.setLabel(label); if (toolTipText) { const infoButton = new osparc.ui.hint.InfoHint(toolTipText); diff --git a/services/static-webserver/client/source/class/osparc/filter/OrganizationsAndMembers.js b/services/static-webserver/client/source/class/osparc/filter/OrganizationsAndMembers.js index 67103aa43e48..33cedf21cb02 100644 --- a/services/static-webserver/client/source/class/osparc/filter/OrganizationsAndMembers.js +++ b/services/static-webserver/client/source/class/osparc/filter/OrganizationsAndMembers.js @@ -64,13 +64,7 @@ qx.Class.define("osparc.filter.OrganizationsAndMembers", { const visibleCollaborators = Object.values(this.__visibleCollaborators); - // define the priority order - const collabTypeOrder = [ - osparc.store.Groups.COLLAB_TYPE.EVERYONE, - osparc.store.Groups.COLLAB_TYPE.SUPPORT, - osparc.store.Groups.COLLAB_TYPE.ORGANIZATION, - osparc.store.Groups.COLLAB_TYPE.USER - ]; + const collabTypeOrder = osparc.store.Groups.COLLAB_TYPE_ORDER; // sort them first visibleCollaborators.sort((a, b) => { const typeDiff = collabTypeOrder.indexOf(a["collabType"]) - collabTypeOrder.indexOf(b["collabType"]); diff --git a/services/static-webserver/client/source/class/osparc/share/Collaborators.js b/services/static-webserver/client/source/class/osparc/share/Collaborators.js index 5686938e7588..3ade7f6e71a0 100644 --- a/services/static-webserver/client/source/class/osparc/share/Collaborators.js +++ b/services/static-webserver/client/source/class/osparc/share/Collaborators.js @@ -43,6 +43,15 @@ qx.Class.define("osparc.share.Collaborators", { }, statics: { + sortProductGroupsFirst: function(a, b) { + const collabTypeOrder = osparc.store.Groups.COLLAB_TYPE_ORDER; + const indexA = collabTypeOrder.indexOf(a["collabType"]); + const indexB = collabTypeOrder.indexOf(b["collabType"]); + const posA = indexA === -1 ? Number.MAX_SAFE_INTEGER : indexA; + const posB = indexB === -1 ? Number.MAX_SAFE_INTEGER : indexB; + return posA - posB; + }, + sortByAccessRights: function(aAccessRights, bAccessRights) { if (aAccessRights["delete"] !== bAccessRights["delete"]) { return bAccessRights["delete"] - aAccessRights["delete"]; @@ -57,9 +66,16 @@ qx.Class.define("osparc.share.Collaborators", { }, sortStudyOrServiceCollabs: function(a, b) { + // product related groups first + let sorted = null; + sorted = this.self().sortProductGroupsFirst(a, b); + if (sorted !== 0) { + return sorted; + } + + // then by access rights const aAccessRights = a["accessRights"]; const bAccessRights = b["accessRights"]; - let sorted = null; if ("delete" in aAccessRights) { // studies sorted = this.self().sortByAccessRights(aAccessRights, bAccessRights); @@ -334,6 +350,10 @@ qx.Class.define("osparc.share.Collaborators", { ctrl.bindProperty("resourceType", "resourceType", null, item, id); // Resource type ctrl.bindProperty("accessRights", "accessRights", null, item, id); ctrl.bindProperty("showOptions", "showOptions", null, item, id); + // handle separator + ctrl.bindProperty("isSeparator", "enabled", { + converter: val => !val // disable clicks on separator + }, item, id); }, configureItem: item => { item.getChildControl("thumbnail").getContentElement() @@ -380,6 +400,15 @@ qx.Class.define("osparc.share.Collaborators", { } this._deleteMember(orgMember, item); }); + item.addListener("changeEnabled", e => { + if (!e.getData()) { + item.set({ + minHeight: 1, + maxHeight: 1, + decorator: "separator-strong", + }); + } + }); } }); vBox.add(collaboratorsUIList, { @@ -462,8 +491,10 @@ qx.Class.define("osparc.share.Collaborators", { // organization if (everyoneGroupIds.includes(parseInt(gid))) { collaborator["thumbnail"] = "@FontAwesome5Solid/globe/32"; + collaborator["collabType"] = osparc.store.Groups.COLLAB_TYPE.EVERYONE; // needed for sorting per product related groups } else if (supportGroup && supportGroup.getGroupId() === parseInt(gid)) { - collaborator["thumbnail"] = supportGroup.getThumbnail(); + collaborator["thumbnail"] = "@FontAwesome5Solid/question-circle/32"; + collaborator["collabType"] = osparc.store.Groups.COLLAB_TYPE.SUPPORT; // needed for sorting per product related groups } else if (!collaborator["thumbnail"]) { collaborator["thumbnail"] = "@FontAwesome5Solid/users/26"; } @@ -475,7 +506,27 @@ qx.Class.define("osparc.share.Collaborators", { } } collaboratorsList.sort(this.self().sortStudyOrServiceCollabs); - collaboratorsList.forEach(c => this.__collaboratorsModel.append(qx.data.marshal.Json.createModel(c))); + + // insert a separator between product and non-product groups + const productGroup = [ + osparc.store.Groups.COLLAB_TYPE.EVERYONE, + osparc.store.Groups.COLLAB_TYPE.SUPPORT, + ]; + const hasProductGroup = collaboratorsList.some(c => productGroup.includes(c.collabType)); + const hasNonProductGroup = collaboratorsList.some(c => !productGroup.includes(c.collabType)); + let separatorInserted = false; + collaboratorsList.forEach(c => { + const isProductGroup = productGroup.includes(c.collabType); + // Only insert separator if both sides exist + if (!isProductGroup && hasProductGroup && hasNonProductGroup && !separatorInserted) { + const separator = { + isSeparator: true + }; + this.__collaboratorsModel.append(qx.data.marshal.Json.createModel(separator)); + separatorInserted = true; + } + this.__collaboratorsModel.append(qx.data.marshal.Json.createModel(c)); + }); }, _addEditors: function(gids) { diff --git a/services/static-webserver/client/source/class/osparc/store/Groups.js b/services/static-webserver/client/source/class/osparc/store/Groups.js index 191adf1b2ad2..672d0befa9ac 100644 --- a/services/static-webserver/client/source/class/osparc/store/Groups.js +++ b/services/static-webserver/client/source/class/osparc/store/Groups.js @@ -62,6 +62,13 @@ qx.Class.define("osparc.store.Groups", { ORGANIZATION: "organization", USER: "user", }, + + COLLAB_TYPE_ORDER: [ + "everyone", // osparc.store.Groups.COLLAB_TYPE.EVERYONE + "support", // osparc.store.Groups.COLLAB_TYPE.SUPPORT, + "organization", // osparc.store.Groups.COLLAB_TYPE.ORGANIZATION + "user", // osparc.store.Groups.COLLAB_TYPE.USER + ], }, members: { diff --git a/services/static-webserver/client/source/class/osparc/store/Services.js b/services/static-webserver/client/source/class/osparc/store/Services.js index eb6803f95d68..07df08d4337e 100644 --- a/services/static-webserver/client/source/class/osparc/store/Services.js +++ b/services/static-webserver/client/source/class/osparc/store/Services.js @@ -67,7 +67,7 @@ qx.Class.define("osparc.store.Services", { const services = this.__servicesCached; if (key in services && version in services[key]) { const historyEntry = osparc.service.Utils.getHistoryEntry(services[key][version]); - if (historyEntry["compatibility"] && historyEntry["compatibility"]["canUpdateTo"]) { + if (historyEntry && historyEntry["compatibility"] && historyEntry["compatibility"]["canUpdateTo"]) { const canUpdateTo = historyEntry["compatibility"]["canUpdateTo"]; return { key: "key" in canUpdateTo ? canUpdateTo["key"] : key, // key is optional diff --git a/services/static-webserver/client/source/class/osparc/theme/Decoration.js b/services/static-webserver/client/source/class/osparc/theme/Decoration.js index 2a32cae25598..e564f1ea29f9 100644 --- a/services/static-webserver/client/source/class/osparc/theme/Decoration.js +++ b/services/static-webserver/client/source/class/osparc/theme/Decoration.js @@ -26,6 +26,13 @@ qx.Theme.define("osparc.theme.Decoration", { } }, + "separator-strong": { + style: { + widthTop: 1, + colorTop: "product-color", + } + }, + "border-simple": { include: "border", style: {