Skip to content

Commit df5a1a8

Browse files
authored
✨ [Frontend] Description UI (#6296)
1 parent b0b4013 commit df5a1a8

File tree

7 files changed

+202
-1746
lines changed

7 files changed

+202
-1746
lines changed

services/static-webserver/client/Manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"svg/svg.path.js",
3030
"jsondiffpatch/jsondiffpatch.min.js",
3131
"jsontreeviewer/jsonTree.js",
32-
"marked/marked.js",
32+
"marked/marked.min.js",
3333
"DOMPurify/purify.min.js"
3434
],
3535
"css": [

services/static-webserver/client/source/class/osparc/info/ServiceLarge.js

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ qx.Class.define("osparc.info.ServiceLarge", {
9292

9393
const title = this.__createTitle();
9494
const titleLayout = this.__createViewWithEdit(title, this.__openTitleEditor);
95-
vBox.add(titleLayout);
9695

9796
const extraInfo = this.__extraInfo();
9897
const extraInfoLayout = this.__createExtraInfo(extraInfo);
@@ -109,28 +108,57 @@ qx.Class.define("osparc.info.ServiceLarge", {
109108
alignX: "center"
110109
});
111110

112-
const hBox = new qx.ui.container.Composite(new qx.ui.layout.HBox(3).set({
111+
const infoAndThumbnail = new qx.ui.container.Composite(new qx.ui.layout.HBox(3).set({
113112
alignX: "center"
114113
}));
115-
hBox.add(extraInfoLayout);
116-
hBox.add(thumbnailLayout, {
114+
infoAndThumbnail.add(extraInfoLayout);
115+
infoAndThumbnail.add(thumbnailLayout, {
117116
flex: 1
118117
});
119-
vBox.add(hBox);
118+
119+
let descriptionUi = null;
120+
if (osparc.service.Utils.canIWrite(this.getService()["accessRights"])) {
121+
descriptionUi = this.__createDescriptionUi();
122+
}
120123

121124
const description = this.__createDescription();
122125
const editInTitle = this.__createViewWithEdit(description.getChildren()[0], this.__openDescriptionEditor);
123126
description.addAt(editInTitle, 0);
124-
vBox.add(description);
125127

126-
const resources = this.__createResources();
127-
vBox.add(resources);
128+
let resources = null;
129+
if (!osparc.desktop.credits.Utils.areWalletsEnabled()) {
130+
resources = this.__createResources();
131+
}
128132

129133
const copyMetadataButton = new qx.ui.form.Button(this.tr("Copy Raw metadata"), "@FontAwesome5Solid/copy/12").set({
130134
allowGrowX: false
131135
});
132136
copyMetadataButton.addListener("execute", () => osparc.utils.Utils.copyTextToClipboard(osparc.utils.Utils.prettifyJson(this.getService())), this);
133-
vBox.add(copyMetadataButton);
137+
138+
139+
if (
140+
this.getService()["descriptionUi"] &&
141+
!osparc.service.Utils.canIWrite(this.getService()["accessRights"]) &&
142+
description.getChildren().length > 1
143+
) {
144+
// Show description only
145+
vBox.add(description.getChildren()[1]);
146+
if (osparc.data.Permissions.getInstance().isTester()) {
147+
// Also copyMetadataButton if tester
148+
vBox.add(copyMetadataButton);
149+
}
150+
} else {
151+
vBox.add(titleLayout);
152+
vBox.add(infoAndThumbnail);
153+
if (descriptionUi) {
154+
vBox.add(descriptionUi);
155+
}
156+
vBox.add(description);
157+
if (resources) {
158+
vBox.add(resources);
159+
}
160+
vBox.add(copyMetadataButton);
161+
}
134162

135163
const scrollContainer = new qx.ui.container.Scroll();
136164
scrollContainer.add(vBox);
@@ -342,13 +370,25 @@ qx.Class.define("osparc.info.ServiceLarge", {
342370
return thumbnail;
343371
},
344372

373+
__createDescriptionUi: function() {
374+
const cbAutoPorts = new qx.ui.form.CheckBox().set({
375+
label: this.tr("Show Description only"),
376+
toolTipText: this.tr("From all the metadata shown in this view,\nonly the Description will be shown to Users."),
377+
iconPosition: "right",
378+
});
379+
cbAutoPorts.setValue(Boolean(this.getService()["descriptionUi"]));
380+
cbAutoPorts.addListener("changeValue", e => {
381+
this.__patchService("descriptionUi", e.getData());
382+
});
383+
return cbAutoPorts;
384+
},
385+
345386
__createDescription: function() {
346-
const maxHeight = 400;
347-
return osparc.info.ServiceUtils.createDescription(this.getService(), maxHeight);
387+
return osparc.info.ServiceUtils.createDescription(this.getService());
348388
},
349389

350390
__createResources: function() {
351-
const resourcesLayout = osparc.info.ServiceUtils.createResourcesInfo();
391+
const resourcesLayout = osparc.info.ServiceUtils.createResourcesInfoCompact();
352392
resourcesLayout.exclude();
353393
let promise = null;
354394
if (this.getNodeId()) {
@@ -365,7 +405,7 @@ qx.Class.define("osparc.info.ServiceLarge", {
365405
promise
366406
.then(serviceResources => {
367407
resourcesLayout.show();
368-
osparc.info.ServiceUtils.resourcesToResourcesInfo(resourcesLayout, serviceResources);
408+
osparc.info.ServiceUtils.resourcesToResourcesInfoCompact(resourcesLayout, serviceResources);
369409
})
370410
.catch(err => console.error(err));
371411
return resourcesLayout;
@@ -473,6 +513,7 @@ qx.Class.define("osparc.info.ServiceLarge", {
473513
},
474514

475515
__patchService: function(key, value) {
516+
this.setEnabled(false);
476517
const serviceDataCopy = osparc.utils.Utils.deepCloneObject(this.getService());
477518
osparc.store.Services.patchServiceData(serviceDataCopy, key, value)
478519
.then(() => {
@@ -483,7 +524,8 @@ qx.Class.define("osparc.info.ServiceLarge", {
483524
console.error(err);
484525
const msg = err.message || this.tr("There was an error while updating the information.");
485526
osparc.FlashMessenger.getInstance().logAs(msg, "ERROR");
486-
});
527+
})
528+
.finally(() => this.setEnabled(true));
487529
}
488530
}
489531
});

services/static-webserver/client/source/class/osparc/info/ServiceUtils.js

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ qx.Class.define("osparc.info.ServiceUtils", {
172172
* @param serviceData {Object} Serialized Service Object
173173
* @param maxHeight {Number} description's maxHeight
174174
*/
175-
createDescription: function(serviceData, maxHeight) {
175+
createDescription: function(serviceData) {
176176
const descriptionLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5).set({
177177
alignY: "middle"
178178
}));
@@ -184,14 +184,38 @@ qx.Class.define("osparc.info.ServiceUtils", {
184184

185185
const description = new osparc.ui.markdown.Markdown().set({
186186
noMargin: true,
187-
maxHeight: maxHeight
188187
});
189-
description.setValue(serviceData["description"]);
188+
// display markdown link content if that's the case
189+
if (
190+
osparc.utils.Utils.isValidHttpUrl(serviceData["description"]) &&
191+
serviceData["description"].slice(-3) === ".md"
192+
) {
193+
// if it's a link, fetch the content
194+
fetch(serviceData["description"])
195+
.then(response => response.blob())
196+
.then(blob => blob.text())
197+
.then(markdown => {
198+
description.setValue(markdown)
199+
})
200+
.catch(err => {
201+
console.error(err);
202+
description.setValue(serviceData["description"]);
203+
});
204+
} else {
205+
description.setValue(serviceData["description"]);
206+
}
190207
descriptionLayout.add(description);
191208

192209
return descriptionLayout;
193210
},
194211

212+
RESOURCES_INFO: {
213+
"limit": {
214+
label: qx.locale.Manager.tr("Limit"),
215+
tooltip: qx.locale.Manager.tr("Runtime check:<br>The service can consume a maximum of 'limit' resources - if it attempts to use more resources than this limit, it will be stopped")
216+
}
217+
},
218+
195219
createResourcesInfo: function() {
196220
const resourcesLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5).set({
197221
alignY: "middle"
@@ -226,14 +250,37 @@ qx.Class.define("osparc.info.ServiceUtils", {
226250
return resourcesLayout;
227251
},
228252

229-
RESOURCES_INFO: {
230-
"limit": {
231-
label: qx.locale.Manager.tr("Limit"),
232-
tooltip: qx.locale.Manager.tr("Runtime check:<br>The service can consume a maximum of 'limit' resources - if it attempts to use more resources than this limit, it will be stopped")
233-
}
253+
createResourcesInfoCompact: function() {
254+
const resourcesLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5).set({
255+
alignY: "middle"
256+
}));
257+
258+
const headerLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
259+
const label = new qx.ui.basic.Label(qx.locale.Manager.tr("Resource Limits")).set({
260+
font: "text-13"
261+
});
262+
headerLayout.add(label);
263+
const infoHint = new osparc.ui.hint.InfoHint(this.RESOURCES_INFO["limit"].tooltip);
264+
headerLayout.add(infoHint);
265+
resourcesLayout.add(headerLayout);
266+
267+
const grid = new qx.ui.layout.Grid(10, 5);
268+
grid.setColumnAlign(0, "left", "middle"); // resource type
269+
grid.setColumnAlign(1, "left", "middle"); // resource limit value
270+
const resourcesInfo = new qx.ui.container.Composite(grid).set({
271+
allowGrowX: false,
272+
alignX: "left",
273+
alignY: "middle"
274+
});
275+
resourcesLayout.add(resourcesInfo);
276+
277+
return resourcesLayout;
234278
},
235279

236280
resourcesToResourcesInfo: function(resourcesLayout, imagesResourcesInfo) {
281+
if (resourcesLayout.getChildren().length < 2) {
282+
return;
283+
}
237284
const layout = resourcesLayout.getChildren()[1];
238285
let row = 1;
239286
Object.entries(imagesResourcesInfo).forEach(([imageName, imageInfo]) => {
@@ -280,6 +327,49 @@ qx.Class.define("osparc.info.ServiceUtils", {
280327
});
281328
},
282329

330+
resourcesToResourcesInfoCompact: function(resourcesLayout, imagesResourcesInfo) {
331+
if (resourcesLayout.getChildren().length < 2) {
332+
return;
333+
}
334+
const gridLayout = resourcesLayout.getChildren()[1];
335+
336+
const compactInfo = {};
337+
Object.values(imagesResourcesInfo).forEach(imageInfo => {
338+
const resourcesInfo = imageInfo["resources"];
339+
Object.keys(resourcesInfo).forEach(resourceKey => {
340+
if (resourcesInfo[resourceKey]["limit"]) {
341+
if (!(resourceKey in compactInfo)) {
342+
compactInfo[resourceKey] = 0;
343+
}
344+
compactInfo[resourceKey] += resourcesInfo[resourceKey]["limit"]
345+
}
346+
});
347+
});
348+
349+
let row = 0;
350+
Object.entries(compactInfo).forEach(([resourceKey, limitsSumUp]) => {
351+
let label = resourceKey;
352+
let value = limitsSumUp;
353+
if (resourceKey === "RAM") {
354+
label += " (GiB)";
355+
value = osparc.utils.Utils.bytesToGiB(value);
356+
}
357+
gridLayout.add(new qx.ui.basic.Label(label).set({
358+
font: "text-13"
359+
}), {
360+
row,
361+
column: 0
362+
});
363+
gridLayout.add(new qx.ui.basic.Label(String(value)).set({
364+
font: "text-13"
365+
}), {
366+
row,
367+
column: 1
368+
});
369+
row++;
370+
});
371+
},
372+
283373
/**
284374
* @param serviceData {Object} Serialized Service Object
285375
*/

services/static-webserver/client/source/class/osparc/theme/Appearance.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1125,7 +1125,7 @@ qx.Theme.define("osparc.theme.Appearance", {
11251125
// showTimeout is themeable so it can be tuned
11261126
// it was defaulted to 700 which was too short
11271127
showTimeout: 2000,
1128-
hideTimeout: 5000,
1128+
hideTimeout: 6000,
11291129
})
11301130
},
11311131

services/static-webserver/client/source/class/osparc/ui/markdown/Markdown.js

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
/**
10-
* @asset(marked/marked.js)
10+
* @asset(marked/marked.min.js)
1111
* @ignore(marked)
1212
*/
1313

@@ -32,7 +32,7 @@ qx.Class.define("osparc.ui.markdown.Markdown", {
3232
resolve(marked);
3333
} else {
3434
const loader = new qx.util.DynamicScriptLoader([
35-
"marked/marked.js"
35+
"marked/marked.min.js"
3636
]);
3737
loader.addListenerOnce("ready", () => {
3838
resolve(marked);
@@ -62,7 +62,7 @@ qx.Class.define("osparc.ui.markdown.Markdown", {
6262
*/
6363
value: {
6464
check: "String",
65-
apply: "_applyMarkdown"
65+
apply: "__applyMarkdown"
6666
},
6767

6868
noMargin: {
@@ -77,20 +77,44 @@ qx.Class.define("osparc.ui.markdown.Markdown", {
7777
* Apply function for the markdown property. Compiles the markdown text to HTML and applies it to the value property of the label.
7878
* @param {String} value Plain text accepting markdown syntax.
7979
*/
80-
_applyMarkdown: function(value = "") {
80+
__applyMarkdown: function(value = "") {
8181
this.__loadMarked.then(() => {
82+
// trying to prettify:
83+
// - links: color with own colors
84+
// - headers: add margins
85+
// - line height: increase to 1.5
86+
/*
87+
const walkTokens = token => {
88+
// Check if the token is a link
89+
if (token.type === 'link' && token.tokens.length > 0) {
90+
// Check if the link contains an image token
91+
const containsImage = token.tokens.some(t => t.type === "image");
92+
// If the link does not contain an image, modify the text to include color styling
93+
if (!containsImage) {
94+
const linkColor = qx.theme.manager.Color.getInstance().resolve("link");
95+
token.text = `<span style="color: ${linkColor};">${token.text}</span>`;
96+
}
97+
}
98+
};
99+
marked.use({ walkTokens });
100+
*/
101+
/*
82102
const renderer = new marked.Renderer();
83-
84-
const linkRenderer = renderer.link;
85-
renderer.link = (href, title, text) => {
103+
renderer.link = ({href, title, tokens}) => {
104+
// Check if the tokens array contains an image token
105+
const hasImageToken = tokens.some(token => token.type === "image");
106+
if (hasImageToken) {
107+
// Return the link HTML as is for image links (badges)
108+
return `<a href="${href}" title="${title || ''}">${tokens.map(token => token.text || '').join('')}</a>`;
109+
}
110+
// text links
86111
const linkColor = qx.theme.manager.Color.getInstance().resolve("link");
87-
const html = linkRenderer.call(renderer, href, title, text);
88-
// eslint-disable-next-line quotes
89-
const linkWithRightColor = html.replace(/^<a /, '<a style="color:'+ linkColor + ' !important"');
90-
return linkWithRightColor;
112+
return `<a href="${href}" title="${title || ''}" style="color: ${linkColor};>${tokens.map(token => token.text || '').join('')}</a>`;
91113
};
114+
marked.use({ renderer });
115+
*/
92116

93-
const html = marked(value, { renderer });
117+
const html = marked.parse(value);
94118

95119
const safeHtml = osparc.wrapper.DOMPurify.getInstance().sanitize(html);
96120
this.setHtml(safeHtml);
@@ -124,8 +148,12 @@ qx.Class.define("osparc.ui.markdown.Markdown", {
124148
if (domElement === null) {
125149
return;
126150
}
151+
this.getContentElement().setStyle({
152+
"line-height": 1.5
153+
});
127154
if (domElement && domElement.children) {
128155
const elemHeight = this.__getChildrenElementHeight(domElement.children);
156+
console.log("resizeMe elemHeight", elemHeight);
129157
if (this.getMaxHeight() && elemHeight > this.getMaxHeight()) {
130158
this.setHeight(elemHeight);
131159
} else {

0 commit comments

Comments
 (0)