diff --git a/services/static-webserver/client/source/class/osparc/editor/AnnotationEditor.js b/services/static-webserver/client/source/class/osparc/editor/AnnotationEditor.js index ec0f956c590..f62e7b8ef1e 100644 --- a/services/static-webserver/client/source/class/osparc/editor/AnnotationEditor.js +++ b/services/static-webserver/client/source/class/osparc/editor/AnnotationEditor.js @@ -21,20 +21,22 @@ qx.Class.define("osparc.editor.AnnotationEditor", { construct: function(annotation) { this.base(arguments); - const layout = new qx.ui.layout.Grid(5, 5); - layout.setColumnAlign(0, "right", "middle"); - layout.setColumnAlign(1, "left", "middle"); - this._setLayout(layout); + this._setLayout(new qx.ui.layout.VBox(10)); - this.set({ - padding: 10 - }); + this.__form = new qx.ui.form.Form(); + this.getChildControl("form-renderer"); if (annotation) { this.setAnnotation(annotation); } }, + events: { + "addAnnotation": "qx.event.type.Event", + "cancel": "qx.event.type.Event", + "deleteAnnotation": "qx.event.type.Event", + }, + properties: { annotation: { check: "osparc.workbench.Annotation", @@ -52,113 +54,155 @@ qx.Class.define("osparc.editor.AnnotationEditor", { }, members: { - __addColor: function() { - this._add(new qx.ui.basic.Label(this.tr("Color")), { - row: 0, - column: 0 - }); - const colorPicker = new osparc.form.ColorPicker(); - this._add(colorPicker, { - row: 0, - column: 1 - }); - return colorPicker; + __form: null, + + getForm: function() { + return this.__form; }, - __applyAnnotation: function(annotation) { - this._removeAll(); + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "form-renderer": + control = new qx.ui.form.renderer.Single(this.__form); + this._add(control); + break; + case "text-field": + control = new qx.ui.form.TextField(); + this.__form.add(control, "Text", null, "text"); + break; + case "text-area": + control = new qx.ui.form.TextArea().set({ + autoSize: true, + minHeight: 70, + maxHeight: 140 + }); + this.__form.add(control, "Note", null, "note"); + break; + case "color-picker": + control = new osparc.form.ColorPicker(); + this.__form.add(control, "Color", null, "color"); + break; + case "font-size": + control = new qx.ui.form.Spinner(); + this.__form.add(control, "Size", null, "size"); + break; + case "buttons-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({ + alignX: "right" + })); + this._add(control); + break; + case "cancel-btn": { + const buttons = this.getChildControl("buttons-layout"); + control = new qx.ui.form.Button(this.tr("Cancel")).set({ + appearance: "form-button-text" + }); + control.addListener("execute", () => this.fireEvent("cancel"), this); + buttons.add(control); + break; + } + case "add-btn": { + const buttons = this.getChildControl("buttons-layout"); + control = new qx.ui.form.Button(this.tr("Add")).set({ + appearance: "form-button" + }); + control.addListener("execute", () => this.fireEvent("addAnnotation"), this); + buttons.add(control); + break; + } + case "delete-btn": { + const buttons = this.getChildControl("buttons-layout"); + control = new qx.ui.form.Button(this.tr("Delete")).set({ + appearance: "danger-button" + }); + control.addListener("execute", () => this.fireEvent("deleteAnnotation"), this); + buttons.add(control); + break; + } + } + return control || this.base(arguments, id); + }, + __applyAnnotation: function(annotation) { if (annotation === null) { return; } - let row = 0; - if (["text", "rect"].includes(annotation.getType())) { - const colorPicker = this.__addColor(); - annotation.bind("color", colorPicker, "color"); - colorPicker.bind("color", annotation, "color"); - row++; - } - const attrs = annotation.getAttributes(); if (annotation.getType() === "text") { - this._add(new qx.ui.basic.Label(this.tr("Text")), { - row, - column: 0 + const textField = this.getChildControl("text-field").set({ + value: attrs.text }); - const textField = new qx.ui.form.TextField(attrs.text); textField.addListener("changeValue", e => annotation.setText(e.getData())); - this._add(textField, { - row, - column: 1 - }); - row++; } else if (annotation.getType() === "note") { - this._add(new qx.ui.basic.Label(this.tr("Note")), { - row, - column: 0 - }); - const textArea = new qx.ui.form.TextArea(attrs.text).set({ - autoSize: true, - minHeight: 70, - maxHeight: 140 + const textArea = this.getChildControl("text-area").set({ + value: attrs.text }); textArea.addListener("changeValue", e => annotation.setText(e.getData())); - this._add(textArea, { - row, - column: 1 - }); - row++; + } + + if (["text", "rect"].includes(annotation.getType())) { + const colorPicker = this.getChildControl("color-picker"); + annotation.bind("color", colorPicker, "value"); + colorPicker.bind("value", annotation, "color"); } if (annotation.getType() === "text") { - this._add(new qx.ui.basic.Label(this.tr("Size")), { - row, - column: 0 - }); - const fontSizeField = new qx.ui.form.Spinner(attrs.fontSize); + const fontSizeField = this.getChildControl("font-size").set({ + value: attrs.fontSize + }) fontSizeField.addListener("changeValue", e => annotation.setFontSize(e.getData())); - this._add(fontSizeField, { - row, - column: 1 - }); - row++; } - - this.__makeItModal(); }, __applyMarker: function(marker) { - this._removeAll(); - if (marker === null) { return; } - const colorPicker = this.__addColor(); + const colorPicker = this.getChildControl("color-picker"); marker.bind("color", colorPicker, "color"); colorPicker.bind("color", marker, "color"); + }, + + addDeleteButton: function() { + this.getChildControl("delete-btn"); + }, - this.__makeItModal(); + addAddButtons: function() { + this.getChildControl("cancel-btn"); + this.getChildControl("add-btn"); + + // Listen to "Enter" key + this.addListener("keypress", keyEvent => { + if (keyEvent.getKeyIdentifier() === "Enter") { + this.fireEvent("addAnnotation"); + } + }, this); }, - __makeItModal: function() { + makeItModal: function() { + this.set({ + padding: 10 + }); + this.show(); - const showHint = () => this.show(); - const hideHint = () => this.exclude(); + const showEditor = () => this.show(); + const hideEditor = () => this.exclude(); const tapListener = event => { if (osparc.utils.Utils.isMouseOnElement(this, event)) { return; } - hideHint(); + hideEditor(); this.set({ annotation: null, marker: null }); document.removeEventListener("mousedown", tapListener); }; - showHint(); + showEditor(); document.addEventListener("mousedown", tapListener); } } diff --git a/services/static-webserver/client/source/class/osparc/form/ColorPicker.js b/services/static-webserver/client/source/class/osparc/form/ColorPicker.js index 7c1498f189f..f8a9ab9ec64 100644 --- a/services/static-webserver/client/source/class/osparc/form/ColorPicker.js +++ b/services/static-webserver/client/source/class/osparc/form/ColorPicker.js @@ -8,55 +8,74 @@ qx.Class.define("osparc.form.ColorPicker", { extend: qx.ui.core.Widget, + include : [ + qx.ui.form.MForm + ], + implement : [ + qx.ui.form.IStringForm, + qx.ui.form.IForm + ], construct: function() { this.base(arguments); - this._setLayout(new qx.ui.layout.HBox()); + const layout = new qx.ui.layout.HBox(); + this._setLayout(layout); - this._add(this.getChildControl("random-button")); - this._add(this.getChildControl("selector-button")); - this._add(this.getChildControl("color-input")); + this.getChildControl("random-button"); + this.getChildControl("selector-button"); + this.getChildControl("color-input"); }, properties: { - color: { + value: { check: "Color", - event: "changeColor", + event: "changeValue", init: "#303030" } }, + // eslint-disable-next-line qx-rules/no-refs-in-members members: { + _forwardStates: { + focused : true, + invalid : true + }, + _createChildControlImpl: function(id) { let control; switch (id) { case "random-button": control = new qx.ui.form.Button(null, "@FontAwesome5Solid/sync-alt/12"); - control.addListener("execute", () => this.setColor(osparc.utils.Utils.getRandomColor()), this); - this.bind("color", control, "backgroundColor"); - this.bind("color", control, "textColor", { + control.addListener("execute", () => this.setValue(osparc.utils.Utils.getRandomColor()), this); + this.bind("value", control, "backgroundColor"); + this.bind("value", control, "textColor", { converter: value => qx.theme.manager.Color.getInstance().resolve(osparc.utils.Utils.getContrastedTextColor(value)) }); + this._add(control); break; case "selector-button": control = new qx.ui.form.Button(null, "@FontAwesome5Solid/eye-dropper/12"); control.addListener("execute", () => this.__openColorSelector(), this); - this.bind("color", control, "backgroundColor"); - this.bind("color", control, "textColor", { + this.bind("value", control, "backgroundColor"); + this.bind("value", control, "textColor", { converter: value => qx.theme.manager.Color.getInstance().resolve(osparc.utils.Utils.getContrastedTextColor(value)) }); + this._add(control); break; case "color-input": control = new qx.ui.form.TextField().set({ - width: 60, + width: 80, required: true }); - this.bind("color", control, "value"); + this._add(control, { + flex: 1 + }); + this.bind("value", control, "value"); control.addListener("changeValue", e => { const newColor = e.getData(); if (osparc.utils.Validators.hexColor(newColor, control)) { - this.setColor(newColor); + this.setValue(newColor); } }); break; @@ -66,12 +85,30 @@ qx.Class.define("osparc.form.ColorPicker", { __openColorSelector: function() { const colorSelector = new qx.ui.control.ColorSelector(); - const rgb = qx.util.ColorUtil.hexStringToRgb(this.getColor()); + const rgb = qx.util.ColorUtil.hexStringToRgb(this.getValue()); colorSelector.setRed(rgb[0]); colorSelector.setGreen(rgb[1]); colorSelector.setBlue(rgb[2]); osparc.ui.window.Window.popUpInWindow(colorSelector, this.tr("Pick a color"), 590, 380); - colorSelector.addListener("changeValue", e => this.setColor(e.getData())); - } + colorSelector.addListener("changeValue", e => this.setValue(e.getData())); + }, + + // overridden + resetValue: function() { + this.getChildControl("color-input").resetValue(); + }, + + // overridden + focus: function() { + this.base(arguments); + this.getChildControl("color-input").getFocusElement().focus(); + }, + + // overridden + tabFocus: function() { + const field = this.getChildControl("color-input"); + field.getFocusElement().focus(); + field.selectAllText(); + }, } }); diff --git a/services/static-webserver/client/source/class/osparc/info/ServiceUtils.js b/services/static-webserver/client/source/class/osparc/info/ServiceUtils.js index 0c4594d3376..4ebd0569c0c 100644 --- a/services/static-webserver/client/source/class/osparc/info/ServiceUtils.js +++ b/services/static-webserver/client/source/class/osparc/info/ServiceUtils.js @@ -86,12 +86,13 @@ qx.Class.define("osparc.info.ServiceUtils", { * @param serviceData {Object} Serialized Service Object */ createContact: function(serviceData) { - const owner = new qx.ui.basic.Label(); - owner.set({ - value: osparc.utils.Utils.getNameFromEmail(serviceData["contact"]), - toolTipText: serviceData["contact"] + const contact = new qx.ui.basic.Label(); + contact.set({ + value: osparc.store.Support.mailToText(serviceData["contact"], (serviceData["name"] + ":" + serviceData["version"])), + selectable: true, + rich: true }); - return owner; + return contact; }, /** diff --git a/services/static-webserver/client/source/class/osparc/store/Support.js b/services/static-webserver/client/source/class/osparc/store/Support.js index 508ad582644..e79de4d1a27 100644 --- a/services/static-webserver/client/source/class/osparc/store/Support.js +++ b/services/static-webserver/client/source/class/osparc/store/Support.js @@ -137,7 +137,7 @@ qx.Class.define("osparc.store.Support", { mailToText: function(email, subject) { const color = qx.theme.manager.Color.getInstance().resolve("text"); - const textLink = `
  ${email}  
`; + const textLink = `
${email}  
`; return textLink; }, diff --git a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js index 8850ddf9312..e76fc5d09fa 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js @@ -141,6 +141,7 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { __annotationInitPos: null, __selectedAnnotations: null, __annotationEditor: null, + __annotationLastColor: null, __applyStudy: function(study) { study.getWorkbench().addListener("reloadModel", () => this.__reloadCurrentModel(), this); @@ -197,7 +198,6 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { this.__addStartHint(); this.__addToolHint(); this.__addDeleteItemButton(); - this.__annotationEditorView(); }, __addStartHint: function() { @@ -235,10 +235,6 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { if (this.__isSelectedItemAnEdge()) { this.__removeEdge(this.__getEdgeUI(this.__selectedItemId)); this.__selectedItemChanged(null); - } else if (this.__isSelectedItemAnAnnotation()) { - const id = this.__selectedItemId; - this.__selectedItemChanged(null); - this.__removeAnnotation(id); } }, this); @@ -248,16 +244,23 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { }); }, - __annotationEditorView: function() { + __getAnnotationEditorView: function() { + if (this.__annotationEditor) { + this.__workbenchLayer.remove(this.__annotationEditor); + } + const annotationEditor = this.__annotationEditor = new osparc.editor.AnnotationEditor().set({ backgroundColor: "background-main-2", visibility: "excluded" }); + annotationEditor.addDeleteButton(); this.__workbenchLayer.add(annotationEditor, { top: 10, right: 10 }); + + return annotationEditor; }, __getWorkbench: function() { @@ -1182,13 +1185,21 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { } else if (this.__isSelectedItemAnAnnotation()) { const annotation = this.__getAnnotation(newID); this.__setSelectedAnnotations([annotation]); - this.__annotationEditor.setAnnotation(annotation); + const annotationEditor = this.__getAnnotationEditorView(); + annotationEditor.setAnnotation(annotation); + annotationEditor.makeItModal(); + annotationEditor.addListener("deleteAnnotation", () => { + annotationEditor.exclude(); + this.__selectedItemChanged(null); + this.__removeAnnotation(annotation.getId()); + }, this); + annotation.addListener("changeColor", e => this.__annotationLastColor = e.getData()); } else { this.fireDataEvent("changeSelectedNode", newID); } if (this.__deleteItemButton) { - this.__deleteItemButton.setVisibility(this.__isSelectedItemAnEdge() || this.__isSelectedItemAnAnnotation() ? "visible" : "excluded"); + this.__deleteItemButton.setVisibility(this.__isSelectedItemAnEdge() ? "visible" : "excluded"); } }, @@ -1260,7 +1271,10 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { }, drawText: { "text": "\uf040", // pencil - "action": () => this.startAnnotationsText() + "action": () => { + const pointerPos = this.__pointerEventToWorkbenchPos(e); + this.startAnnotationsText(pointerPos); + } }, drawRect: { "text": "\uf044", // brush with rect @@ -1577,11 +1591,16 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { this.__toolHint.setValue(this.tr("Draw a rectangle")); }, - startAnnotationsText: function() { + startAnnotationsText: function(workbenchPos) { this.__annotatingNote = false; this.__annotatingText = true; this.__annotatingRect = false; - this.__toolHint.setValue(this.tr("Pick the position")); + if (workbenchPos) { + this.__annotationInitPos = workbenchPos; + this.__mouseUp(); + } else { + this.__toolHint.setValue(this.tr("Pick the position")); + } }, __openNodeRenamer: function(nodeId) { @@ -1605,7 +1624,9 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { const node = this.getStudy().getWorkbench().getNode(nodeId); const marker = node.getMarker(); if (marker) { - this.__annotationEditor.setMarker(marker); + const annotationEditor = this.__getAnnotationEditorView(); + annotationEditor.setMarker(marker); + annotationEditor.makeItModal(); } } }, @@ -1684,15 +1705,6 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { this.__removeEdge(this.__getEdgeUI(this.__selectedItemId)); this.__selectedItemChanged(null); } - if (this.__isSelectedItemAnAnnotation()) { - const selectedAnnotation = this.__getAnnotation(this.__selectedItemId); - // Only delete if it's a rectangle, for the other cases the user might be editing the text - if (selectedAnnotation.getType() === "rect") { - const id = this.__selectedItemId; - this.__selectedItemChanged(null); - this.__removeAnnotation(id); - } - } break; case "Escape": this.resetSelection(); @@ -1866,20 +1878,23 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { const width = Math.abs(initPos.x - currentPos.x); const height = Math.abs(initPos.y - currentPos.y); if ([null, undefined].includes(this.__rectAnnotationRepr)) { - this.__rectAnnotationRepr = this.__svgLayer.drawAnnotationRect(width, height, x, y, osparc.workbench.Annotation.DEFAULT_COLOR); + const color = this.__annotationLastColor ? this.__annotationLastColor : osparc.workbench.Annotation.DEFAULT_COLOR; + this.__rectAnnotationRepr = this.__svgLayer.drawAnnotationRect(width, height, x, y, color); } else { osparc.wrapper.Svg.updateRect(this.__rectAnnotationRepr, width, height, x, y); } }, __consolidateAnnotation: function(type, initPos, annotation) { + const color = this.__annotationLastColor ? this.__annotationLastColor : osparc.workbench.Annotation.DEFAULT_COLOR; const serializeData = { type, + color, attributes: {} }; if (type === "rect") { if ([null, undefined].includes(annotation)) { - osparc.FlashMessenger.getInstance().logAs(this.tr("Draw a rectanlge first"), "WARNING"); + osparc.FlashMessenger.getInstance().logAs(this.tr("Draw a rectangle first"), "WARNING"); return false; } serializeData.attributes = osparc.wrapper.Svg.getRectAttributes(annotation); @@ -1905,16 +1920,36 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { } else if (type === "rect") { this.__addAnnotation(serializeData); } else if (type === "text") { - const title = this.tr("Add Text"); - const titleEditor = new osparc.widget.Renamer(null, null, title); - titleEditor.addListener("labelChanged", e => { - titleEditor.close(); - serializeData.attributes.text = e.getData()["newLabel"]; - serializeData.attributes.fontSize = 12; + const tempAnnotation = new osparc.workbench.Annotation(null, { + type: "text", + color, + attributes: { + text: "", + fontSize: 12 + } + }); + const annotationEditor = new osparc.editor.AnnotationEditor(tempAnnotation); + annotationEditor.addAddButtons(); + tempAnnotation.addListener("changeColor", e => this.__annotationLastColor = e.getData()); + annotationEditor.addListener("appear", () => { + const textField = annotationEditor.getChildControl("text-field"); + textField.focus(); + textField.activate(); + }); + const win = osparc.ui.window.Window.popUpInWindow(annotationEditor, "Add Text Annotation", 220, 135).set({ + clickAwayClose: true, + showClose: true + }); + annotationEditor.addListener("addAnnotation", () => { + win.close(); + const form = annotationEditor.getForm(); + serializeData.attributes.text = form.getItem("text").getValue(); + serializeData.attributes.color = form.getItem("color").getValue(); + serializeData.color = form.getItem("color").getValue(); + serializeData.attributes.fontSize = form.getItem("size").getValue(); this.__addAnnotation(serializeData); }, this); - titleEditor.center(); - titleEditor.open(); + win.open(); } return true; },