Skip to content

Commit 1713021

Browse files
✨ [Frontend] New + Button (#7089)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 5d052a6 commit 1713021

File tree

29 files changed

+801
-212
lines changed

29 files changed

+801
-212
lines changed

services/static-webserver/client/source/class/osparc/dashboard/Dashboard.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ qx.Class.define("osparc.dashboard.Dashboard", {
3838
construct: function() {
3939
this.base(arguments);
4040

41-
osparc.utils.Utils.setIdToWidget(this.getChildControl("bar"), "dashboardTabs");
42-
osparc.utils.Utils.setIdToWidget(this, "dashboard");
41+
this.getChildControl("bar").set({
42+
visibility: "excluded",
43+
});
4344

4445
this.set({
4546
contentPadding: this.self().PADDING,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2025 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
qx.Class.define("osparc.dashboard.NewPlusButton", {
19+
extend: qx.ui.form.MenuButton,
20+
21+
construct: function() {
22+
this.base(arguments);
23+
24+
this.set({
25+
appearance: "strong-button",
26+
icon: osparc.dashboard.CardBase.NEW_ICON + "20",
27+
label: this.tr("New"),
28+
font: "text-16",
29+
gap: 15,
30+
padding: 15,
31+
paddingRight: 20,
32+
allowGrowX: false,
33+
});
34+
35+
osparc.utils.Utils.setIdToWidget(this, "newPlusBtn");
36+
37+
this.setMenu(new osparc.dashboard.NewPlusMenu());
38+
},
39+
});
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2025 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
qx.Class.define("osparc.dashboard.NewPlusMenu", {
19+
extend: qx.ui.menu.Menu,
20+
21+
construct: function() {
22+
this.base(arguments);
23+
24+
osparc.utils.Utils.prettifyMenu(this);
25+
26+
this.set({
27+
position: "bottom-left",
28+
spacingX: 20,
29+
});
30+
31+
this.__categoryHeaders = [];
32+
33+
this.__addItems();
34+
},
35+
36+
events: {
37+
"createFolder": "qx.event.type.Data",
38+
"newEmptyStudyClicked": "qx.event.type.Data",
39+
"newStudyFromTemplateClicked": "qx.event.type.Data",
40+
"newStudyFromServiceClicked": "qx.event.type.Data",
41+
},
42+
43+
statics: {
44+
createMenuButton: function(icon, title, infoText) {
45+
title = osparc.utils.Utils.replaceTokens(
46+
title,
47+
"replace_me_product_name",
48+
osparc.store.StaticInfo.getInstance().getDisplayName()
49+
);
50+
const menuButton = new qx.ui.menu.Button().set({
51+
icon: icon || null,
52+
label: title,
53+
font: "text-16",
54+
allowGrowX: true,
55+
});
56+
menuButton.getChildControl("icon").set({
57+
alignX: "center",
58+
});
59+
menuButton.getChildControl("label").set({
60+
rich: true,
61+
marginRight: 20,
62+
});
63+
if (infoText) {
64+
infoText = osparc.utils.Utils.replaceTokens(
65+
title,
66+
"replace_me_product_name",
67+
osparc.store.StaticInfo.getInstance().getDisplayName()
68+
);
69+
const infoHint = new osparc.ui.hint.InfoHint(infoText).set({
70+
source: osparc.ui.hint.InfoHint.INFO_ICON + "/16",
71+
});
72+
// where the shortcut is supposed to go
73+
// eslint-disable-next-line no-underscore-dangle
74+
menuButton._add(infoHint, {column: 2});
75+
}
76+
return menuButton;
77+
},
78+
79+
createHeader: function(icon, label, infoText) {
80+
return this.createMenuButton(icon, label, infoText).set({
81+
anonymous: true,
82+
cursor: "default",
83+
font: "text-14",
84+
textColor: "text-darker",
85+
});
86+
},
87+
},
88+
89+
members: {
90+
__categoryHeaders: null,
91+
92+
_createChildControlImpl: function(id) {
93+
let control;
94+
switch (id) {
95+
case "new-folder":
96+
control = this.self().createMenuButton(
97+
osparc.dashboard.CardBase.NEW_ICON + "16",
98+
this.tr("New Folder"),
99+
);
100+
osparc.utils.Utils.setIdToWidget(control, "newFolderButton");
101+
control.addListener("tap", () => this.__createNewFolder());
102+
this.add(control);
103+
break;
104+
}
105+
return control || this.base(arguments, id);
106+
},
107+
108+
__addItems: async function() {
109+
this.getChildControl("new-folder");
110+
this.addSeparator();
111+
await this.__addNewStudyItems();
112+
},
113+
114+
__addNewStudyItems: async function() {
115+
await Promise.all([
116+
osparc.store.Products.getInstance().getNewStudyConfig(),
117+
osparc.data.Resources.get("templates")
118+
]).then(values => {
119+
const newStudiesData = values[0];
120+
const templates = values[1];
121+
if (newStudiesData["categories"]) {
122+
this.__addCategories(newStudiesData["categories"]);
123+
}
124+
newStudiesData["resources"].forEach(newStudyData => {
125+
if (newStudyData["showDisabled"]) {
126+
this.__addDisabledButton(newStudyData);
127+
} else if (newStudyData["resourceType"] === "study") {
128+
this.__addEmptyStudyButton(newStudyData);
129+
} else if (newStudyData["resourceType"] === "template") {
130+
this.__addFromTemplateButton(newStudyData, templates);
131+
} else if (newStudyData["resourceType"] === "service") {
132+
this.__addFromServiceButton(newStudyData);
133+
}
134+
});
135+
});
136+
},
137+
138+
__getLastIdxFromCategory: function(categoryId) {
139+
for (let i=this.getChildren().length-1; i>=0; i--) {
140+
const child = this.getChildren()[i];
141+
if (child && child["categoryId"] && child["categoryId"] === categoryId) {
142+
return i;
143+
}
144+
}
145+
return null;
146+
},
147+
148+
__addCategories: function(categories) {
149+
categories.forEach(category => {
150+
const categoryHeader = this.self().createHeader(null, category["title"], category["description"]);
151+
categoryHeader["categoryId"] = category["id"];
152+
if (this.__categoryHeaders.length) {
153+
// add spacing between categories
154+
categoryHeader.setMarginTop(10);
155+
}
156+
this.__categoryHeaders.push(categoryHeader);
157+
this.add(categoryHeader);
158+
});
159+
},
160+
161+
__addIcon: function(menuButton, resourceInfo, resourceMetadata) {
162+
let source = null;
163+
if (resourceInfo && "icon" in resourceInfo) {
164+
// first the one set in the new_studies
165+
source = resourceInfo["icon"];
166+
} else if (resourceMetadata && "thumbnail" in resourceMetadata) {
167+
// second the one from the resource
168+
source = resourceMetadata["thumbnail"];
169+
}
170+
171+
if (source) {
172+
const thumbnail = new osparc.ui.basic.Thumbnail(source, 24, 24).set({
173+
minHeight: 24,
174+
minWidth: 24,
175+
});
176+
thumbnail.getChildControl("image").set({
177+
anonymous: true,
178+
decorator: "rounded",
179+
});
180+
// eslint-disable-next-line no-underscore-dangle
181+
menuButton._add(thumbnail, {column: 0});
182+
}
183+
},
184+
185+
__addFromResourceButton: function(menuButton, category) {
186+
let idx = null;
187+
if (category) {
188+
idx = this.__getLastIdxFromCategory(category);
189+
}
190+
if (idx) {
191+
menuButton["categoryId"] = category;
192+
this.addAt(menuButton, idx+1);
193+
} else {
194+
this.add(menuButton);
195+
}
196+
},
197+
198+
__addDisabledButton: function(newStudyData) {
199+
const menuButton = this.self().createMenuButton(null, newStudyData.title, newStudyData.reason);
200+
osparc.utils.Utils.setIdToWidget(menuButton, newStudyData.idToWidget);
201+
menuButton.setEnabled(false);
202+
203+
this.__addIcon(menuButton, newStudyData);
204+
this.__addFromResourceButton(menuButton, newStudyData.category);
205+
},
206+
207+
__addEmptyStudyButton: function(newStudyData) {
208+
const menuButton = this.self().createMenuButton(null, newStudyData.title);
209+
osparc.utils.Utils.setIdToWidget(menuButton, newStudyData.idToWidget);
210+
211+
menuButton.addListener("tap", () => {
212+
this.fireDataEvent("newEmptyStudyClicked", {
213+
newStudyLabel: newStudyData.newStudyLabel,
214+
});
215+
});
216+
217+
this.__addIcon(menuButton, newStudyData);
218+
this.__addFromResourceButton(menuButton, newStudyData.category);
219+
},
220+
221+
__addFromTemplateButton: function(newStudyData, templates) {
222+
const menuButton = this.self().createMenuButton(null, newStudyData.title);
223+
osparc.utils.Utils.setIdToWidget(menuButton, newStudyData.idToWidget);
224+
// disable it until found in templates store
225+
menuButton.setEnabled(false);
226+
227+
let templateMetadata = templates.find(t => t.name === newStudyData.expectedTemplateLabel);
228+
if (templateMetadata) {
229+
menuButton.setEnabled(true);
230+
menuButton.addListener("tap", () => {
231+
this.fireDataEvent("newStudyFromTemplateClicked", {
232+
templateData: templateMetadata,
233+
newStudyLabel: newStudyData.newStudyLabel,
234+
});
235+
});
236+
this.__addIcon(menuButton, newStudyData, templateMetadata);
237+
this.__addFromResourceButton(menuButton, newStudyData.category);
238+
}
239+
},
240+
241+
__addFromServiceButton: function(newStudyData) {
242+
const menuButton = this.self().createMenuButton(null, newStudyData.title);
243+
osparc.utils.Utils.setIdToWidget(menuButton, newStudyData.idToWidget);
244+
// disable it until found in services store
245+
menuButton.setEnabled(false);
246+
247+
const key = newStudyData.expectedKey;
248+
// Include deprecated versions, they should all be updatable to a non deprecated version
249+
const versions = osparc.service.Utils.getVersions(key, false);
250+
if (versions.length && newStudyData) {
251+
// scale to latest compatible
252+
const latestVersion = versions[0];
253+
const latestCompatible = osparc.service.Utils.getLatestCompatible(key, latestVersion);
254+
osparc.store.Services.getService(latestCompatible["key"], latestCompatible["version"])
255+
.then(latestMetadata => {
256+
// make sure this one is not deprecated
257+
if (osparc.service.Utils.isDeprecated(latestMetadata)) {
258+
return;
259+
}
260+
menuButton.setEnabled(true);
261+
menuButton.addListener("tap", () => {
262+
this.fireDataEvent("newStudyFromServiceClicked", {
263+
serviceMetadata: latestMetadata,
264+
newStudyLabel: newStudyData.newStudyLabel,
265+
});
266+
});
267+
268+
const cb = e => {
269+
this.hide();
270+
// so that is not consumed by the menu button itself
271+
e.stopPropagation();
272+
latestMetadata["resourceType"] = "service";
273+
const resourceDetails = new osparc.dashboard.ResourceDetails(latestMetadata);
274+
osparc.dashboard.ResourceDetails.popUpInWindow(resourceDetails);
275+
}
276+
const infoButton = new osparc.ui.basic.IconButton(osparc.ui.hint.InfoHint.INFO_ICON + "/16", cb);
277+
// where the shortcut is supposed to go
278+
// eslint-disable-next-line no-underscore-dangle
279+
menuButton._add(infoButton, {column: 2});
280+
281+
this.__addIcon(menuButton, newStudyData, latestMetadata);
282+
this.__addFromResourceButton(menuButton, newStudyData.category);
283+
})
284+
}
285+
},
286+
287+
__createNewFolder: function() {
288+
const newFolder = true;
289+
const folderEditor = new osparc.editor.FolderEditor(newFolder);
290+
const title = this.tr("New Folder");
291+
const win = osparc.ui.window.Window.popUpInWindow(folderEditor, title, 300, 120);
292+
folderEditor.addListener("createFolder", () => {
293+
const name = folderEditor.getLabel();
294+
this.fireDataEvent("createFolder", {
295+
name,
296+
});
297+
win.close();
298+
});
299+
folderEditor.addListener("cancel", () => win.close());
300+
},
301+
},
302+
});

services/static-webserver/client/source/class/osparc/dashboard/NewStudies.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ qx.Class.define("osparc.dashboard.NewStudies", {
8282
this._add(noGroupContainer);
8383

8484
Array.from(this.__groups).forEach(group => {
85-
const groupContainer = this.__createGroupContainer(group.id, group.label, "transparent");
85+
let headerLabel = group.title;
86+
headerLabel += "description" in group ? (". " + group["description"]) : "";
87+
const groupContainer = this.__createGroupContainer(group.id, headerLabel, "transparent");
8688
this._add(groupContainer);
8789
});
8890
} else {
@@ -106,7 +108,18 @@ qx.Class.define("osparc.dashboard.NewStudies", {
106108
this.__newStudies.forEach(resourceData => {
107109
const cards = this.__resourceToCards(resourceData);
108110
cards.forEach(newCard => {
109-
newCard.setEnabled(!(resourceData.showDisabled));
111+
if (resourceData.showDisabled) {
112+
newCard.setEnabled(false);
113+
if (resourceData.reason) {
114+
const reason = osparc.utils.Utils.replaceTokens(
115+
resourceData.reason,
116+
"replace_me_product_name",
117+
osparc.store.StaticInfo.getInstance().getDisplayName()
118+
);
119+
const descLabel = newCard.getChildControl("subtitle-text");
120+
descLabel.setValue(reason.toString());
121+
}
122+
}
110123
newCards.push(newCard);
111124
});
112125
});

0 commit comments

Comments
 (0)