Skip to content

Commit 25fbe83

Browse files
authored
✨ [Frontend] Coins icon for the credits indicator (#5993)
1 parent b84b85f commit 25fbe83

File tree

10 files changed

+315
-65
lines changed

10 files changed

+315
-65
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,14 @@ qx.Class.define("osparc.dashboard.GridButtonBase", {
349349
iconLayout.recheckSize();
350350
},
351351

352+
replaceIcon: function(newIcon) {
353+
const plusIcon = this.getChildControl("icon");
354+
plusIcon.exclude();
355+
356+
const bodyLayout = this.getChildControl("body");
357+
bodyLayout.add(newIcon, {flex: 1});
358+
},
359+
352360
/**
353361
* Event handler for the pointer over event.
354362
*/

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,14 @@ qx.Class.define("osparc.dashboard.NewStudies", {
148148
newPlanButton.setCardKey(templateInfo.idToWidget);
149149
osparc.utils.Utils.setIdToWidget(newPlanButton, templateInfo.idToWidget);
150150
if (templateInfo.billable) {
151-
osparc.desktop.credits.Utils.setCreditsIconToButton(newPlanButton);
151+
// replace the plus button with the creditsImage
152+
const creditsImage = new osparc.desktop.credits.CreditsImage();
153+
creditsImage.getChildControl("image").set({
154+
width: 60,
155+
height: 60
156+
})
157+
newPlanButton.replaceIcon(creditsImage);
158+
152159
newPlanButton.addListener("execute", () => {
153160
const store = osparc.store.Store.getInstance();
154161
const credits = store.getContextWallet().getCreditsAvailable()
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2024 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.desktop.credits.CreditsImage", {
19+
extend: osparc.ui.basic.SVGImage,
20+
21+
construct: function() {
22+
this.base(arguments, "osparc/coins-solid.svg");
23+
24+
const store = osparc.store.Store.getInstance();
25+
store.addListener("changeContextWallet", this.__updateWallet, this);
26+
this.__updateWallet();
27+
},
28+
29+
members: {
30+
__updateWallet: function() {
31+
const store = osparc.store.Store.getInstance();
32+
const contextWallet = store.getContextWallet();
33+
if (contextWallet) {
34+
contextWallet.addListener("changeCreditsAvailable", this.__updateColor, this);
35+
this.__updateColor();
36+
}
37+
},
38+
39+
__updateColor: function() {
40+
const store = osparc.store.Store.getInstance();
41+
const contextWallet = store.getContextWallet();
42+
if (contextWallet) {
43+
const credits = contextWallet.getCreditsAvailable();
44+
const creditsColorKeyword = osparc.desktop.credits.Utils.creditsToColor(credits, "strong-main");
45+
this.setImageColor(creditsColorKeyword);
46+
}
47+
}
48+
}
49+
});

services/static-webserver/client/source/class/osparc/desktop/credits/CreditsIndicatorButton.js

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,32 @@
1616
************************************************************************ */
1717

1818
qx.Class.define("osparc.desktop.credits.CreditsIndicatorButton", {
19-
extend: qx.ui.form.Button,
19+
extend: osparc.desktop.credits.CreditsImage,
2020

2121
construct: function() {
2222
this.base(arguments);
2323

2424
this.set({
25-
backgroundColor: "transparent"
25+
cursor: "pointer",
26+
padding: [3, 8]
2627
});
2728

28-
const store = osparc.store.Store.getInstance();
29-
store.bind("contextWallet", this, "wallet");
29+
this.getChildControl("image").set({
30+
width: 24,
31+
height: 24
32+
});
3033

3134
this.__creditsContainer = new osparc.desktop.credits.CreditsNavBarContainer();
3235
this.__creditsContainer.exclude();
3336

3437
this.addListener("tap", this.__buttonTapped, this);
3538
},
3639

37-
properties: {
38-
wallet: {
39-
check: "osparc.data.model.Wallet",
40-
init: null,
41-
nullable: true,
42-
event: "changeWallet",
43-
apply: "__applyWallet"
44-
}
45-
},
4640

4741
members: {
4842
__creditsContainer: null,
4943
__tappedOut: null,
5044

51-
__applyWallet: function() {
52-
osparc.desktop.credits.Utils.setCreditsIconToButton(this);
53-
},
54-
5545
__buttonTapped: function() {
5646
if (this.__tappedOut) {
5747
this.__tappedOut = false;

services/static-webserver/client/source/class/osparc/desktop/credits/Utils.js

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,6 @@ qx.Class.define("osparc.desktop.credits.Utils", {
2727
return Boolean(statics && statics["isPaymentEnabled"]);
2828
},
2929

30-
setCreditsIconToButton: function(button) {
31-
button.setIcon(osparc.desktop.credits.Utils.CREDITS_ICON);
32-
const store = osparc.store.Store.getInstance();
33-
const contextWallet = store.getContextWallet();
34-
if (contextWallet) {
35-
contextWallet.bind("creditsAvailable", button, "textColor", {
36-
converter: c => osparc.desktop.credits.Utils.creditsToColor(c, "strong-main")
37-
});
38-
}
39-
},
40-
4130
getNoWriteAccessInformationLabel: function() {
4231
return new qx.ui.basic.Label().set({
4332
value: qx.locale.Manager.tr("You can't access this information"),

services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,7 @@ qx.Class.define("osparc.navigation.NavigationBar", {
239239
break;
240240
}
241241
case "credits-button":
242-
control = new osparc.desktop.credits.CreditsIndicatorButton().set({
243-
maxHeight: 32
244-
});
245-
control.getChildControl("icon").set({
246-
maxHeight: 24,
247-
scale: true
248-
});
242+
control = new osparc.desktop.credits.CreditsIndicatorButton();
249243
osparc.utils.Utils.setIdToWidget(control, "creditsNavigationBtn");
250244
this.getChildControl("right-items").add(control);
251245
break;
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2024 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+
/**
19+
* Widget that displays a SVG image and supports changing its color.
20+
* It is meant to be used for those images that are not available in the catalogs of font icons we include.
21+
*/
22+
23+
24+
qx.Class.define("osparc.ui.basic.SVGImage", {
25+
extend: osparc.ui.layout.CenteredGrid,
26+
27+
/**
28+
* @param source
29+
*/
30+
construct: function(source) {
31+
this.base(arguments);
32+
33+
if (source) {
34+
this.setSource(source);
35+
}
36+
},
37+
38+
properties: {
39+
source: {
40+
check: "String",
41+
init: null,
42+
nullable: false,
43+
apply: "__applySource"
44+
},
45+
46+
imageColor: {
47+
check: "String",
48+
init: null,
49+
nullable: false,
50+
event: "changeImageColor",
51+
apply: "__applyImageColor"
52+
},
53+
},
54+
55+
statics: {
56+
keywordToCSSFilter: function(keyword) {
57+
// use the following link to extended supported colors
58+
// https://isotropic.co/tool/hex-color-to-css-filter/
59+
let filter = null;
60+
switch (keyword) {
61+
case "danger-red": // "#FF2D2D"
62+
filter = "invert(13%) sepia(89%) saturate(5752%) hue-rotate(346deg) brightness(85%) contrast(109%)";
63+
break;
64+
case "warning-yellow": // #F8DB1F
65+
filter = "invert(90%) sepia(99%) saturate(7500%) hue-rotate(331deg) brightness(95%) contrast(108%)";
66+
break;
67+
case "ready-green": // #58A6FF
68+
filter = "invert(66%) sepia(24%) saturate(5763%) hue-rotate(188deg) brightness(101%) contrast(101%)";
69+
break;
70+
case "text": // light or dark
71+
if (qx.theme.manager.Meta.getInstance().getTheme().basename === "ThemeLight") {
72+
// ThemeLight #282828
73+
filter = "invert(10%) sepia(4%) saturate(19%) hue-rotate(354deg) brightness(102%) contrast(86%)";
74+
} else {
75+
// ThemeDark #D8D8D8
76+
filter = "invert(66%) sepia(24%) saturate(5763%) hue-rotate(188deg) brightness(101%) contrast(101%)";
77+
}
78+
break;
79+
case "strong-main": // it depends on the product
80+
if (qx.theme.manager.Meta.getInstance().getTheme().name.includes(".s4l.")) {
81+
// "rgba(0, 144, 208, 1)"
82+
filter = "invert(55%) sepia(73%) saturate(6976%) hue-rotate(177deg) brightness(100%) contrast(102%)";
83+
} else if (qx.theme.manager.Meta.getInstance().getTheme().name.includes(".tis.")) {
84+
// "rgba(105, 105, 255, 1)"
85+
filter = "invert(36%) sepia(74%) saturate(2007%) hue-rotate(225deg) brightness(102%) contrast(104%)";
86+
} else {
87+
// "rgba(131, 0, 191, 1)" osparc
88+
filter = "invert(13%) sepia(95%) saturate(6107%) hue-rotate(282deg) brightness(77%) contrast(115%)";
89+
}
90+
}
91+
return filter;
92+
},
93+
94+
// not very accurate
95+
rgbToCSSFilter: function(rgb) {
96+
const [r, g, b] = rgb.split(",").map(Number);
97+
98+
let [rf, gf, bf] = [r / 255, g / 255, b / 255];
99+
let [mi, ma] = [Math.min(rf, gf, bf), Math.max(rf, gf, bf)];
100+
let [h, s, l] = [0, 0, (mi + ma) / 2];
101+
102+
if (mi !== ma) {
103+
s = l < 0.5 ? (ma - mi) / (ma + mi) : (ma - mi) / (2 - ma - mi);
104+
switch (ma) {
105+
case rf:
106+
h = (gf - bf) / (ma - mi);
107+
break;
108+
case gf:
109+
h = 2 + (bf - rf) / (ma - mi);
110+
break;
111+
case bf:
112+
h = 4 + (rf - gf) / (ma - mi);
113+
break;
114+
}
115+
}
116+
117+
h = Math.round(h * 60);
118+
if (h < 0) {
119+
h += 360;
120+
}
121+
s = Math.round(s * 100);
122+
l = Math.round(l * 100);
123+
124+
const invertValue = l2 => 100 - l2;
125+
const sepiaValue = s2 => s2;
126+
const saturateValue = s3 => s3;
127+
const brightnessValue = l3 => l3;
128+
const contrastValue = l4 => l4 > 50 ? 50 : l4;
129+
return `invert(${invertValue(l)}%) sepia(${sepiaValue(s)}%) saturate(${saturateValue(s)}%) hue-rotate(${h}deg) brightness(${brightnessValue(l)}%) contrast(${contrastValue(l)}%)`;
130+
}
131+
},
132+
133+
members: {
134+
_createChildControlImpl: function(id) {
135+
let control;
136+
switch (id) {
137+
case "image":
138+
control = new qx.ui.basic.Image().set({
139+
scale: true,
140+
allowStretchX: true,
141+
allowStretchY: true,
142+
allowGrowX: true,
143+
allowGrowY: true,
144+
alignX: "center",
145+
alignY: "middle"
146+
});
147+
this.addCenteredWidget(control);
148+
break;
149+
}
150+
return control || this.base(arguments, id);
151+
},
152+
153+
__applySource: function(src) {
154+
if (src && src.includes(".svg")) {
155+
this.getChildControl("image").setSource(src);
156+
}
157+
},
158+
159+
/**
160+
* @param keywordOrRgb {string} predefined keyword or rgb in the folloing format "0,255,0"
161+
*/
162+
__applyImageColor: function(keywordOrRgb) {
163+
let filterValue = this.self().keywordToCSSFilter(keywordOrRgb);
164+
if (filterValue === null) {
165+
const hexColor = qx.theme.manager.Color.getInstance().resolve(keywordOrRgb);
166+
const rgbColor = qx.util.ColorUtil.hexStringToRgb(hexColor);
167+
filterValue = this.self().rgbToCSSFilter(rgbColor);
168+
}
169+
const myStyle = {
170+
"filter": filterValue
171+
};
172+
this.getChildControl("image").getContentElement().setStyles(myStyle);
173+
}
174+
}
175+
});

services/static-webserver/client/source/class/osparc/ui/basic/Thumbnail.js

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
* |_____x_____|flex Spacer|_____x_____|
2424
*/
2525
qx.Class.define("osparc.ui.basic.Thumbnail", {
26-
extend: qx.ui.core.Widget,
26+
extend: osparc.ui.layout.CenteredGrid,
2727

2828
/**
2929
* @param {String} source Source of the Image
@@ -33,30 +33,6 @@ qx.Class.define("osparc.ui.basic.Thumbnail", {
3333
construct: function(source, maxWidth, maxHeight) {
3434
this.base(arguments);
3535

36-
const layout = new qx.ui.layout.Grid();
37-
layout.setRowFlex(0, 1);
38-
layout.setRowFlex(2, 1);
39-
layout.setColumnFlex(0, 1);
40-
layout.setColumnFlex(2, 1);
41-
this._setLayout(layout);
42-
43-
[
44-
[0, 0],
45-
[0, 1],
46-
[0, 2],
47-
[1, 0],
48-
[1, 2],
49-
[2, 0],
50-
[2, 1],
51-
[2, 2]
52-
].forEach(quad => {
53-
const empty = new qx.ui.core.Spacer();
54-
this._add(empty, {
55-
row: quad[0],
56-
column: quad[1]
57-
});
58-
});
59-
6036
if (source) {
6137
this.setSource(source);
6238
}
@@ -98,10 +74,7 @@ qx.Class.define("osparc.ui.basic.Thumbnail", {
9874
alignX: "center",
9975
alignY: "middle"
10076
});
101-
this._add(control, {
102-
row: 1,
103-
column: 1
104-
});
77+
this.addCenteredWidget(control);
10578
break;
10679
}
10780
return control || this.base(arguments, id);

0 commit comments

Comments
 (0)