From 6ee513bd134da392aa6f807b0391fca3383cc745 Mon Sep 17 00:00:00 2001 From: Will Barnett Date: Wed, 8 Nov 2023 11:58:38 +0000 Subject: [PATCH 1/3] Made panel icons customisable by activity config. --- platform/src/Playground.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/platform/src/Playground.js b/platform/src/Playground.js index 2df59d2..62ae59d 100644 --- a/platform/src/Playground.js +++ b/platform/src/Playground.js @@ -205,8 +205,8 @@ function initialisePanels() { /** * Create a panel for a given panel config entry * - * @param {string} panel - * @return {Panel} + * @param {Object} activity config panel definition + * @return {Panel} platform Panel */ function createPanelForDefinitionId(panel){ const panelDefinition = panel.ref; @@ -221,7 +221,6 @@ function initialisePanels() { newPanel = new ProgramPanel(newPanelId); // Set from the tool panel definition - newPanel.setIcon(panelDefinition.icon); newPanel.setEditorMode(panelDefinition.language); newPanel.setType(panelDefinition.language); @@ -244,8 +243,6 @@ function initialisePanels() { const panelDef = toolsManager.getPanelDefinition(newPanelId); newPanel = new OutputPanel(newPanelId, panelDefinition.language, outputType, outputLanguage); - - newPanel.setIcon(panelDefinition.icon); newPanel.hideEditor(); newPanel.showDiagram(); @@ -257,7 +254,6 @@ function initialisePanels() { newPanel = new XtextEditorPanel(newPanelId, editorUrl, panel.extension); - newPanel.setIcon(panelDefinition.icon); newPanel.setType(panelDefinition.language); break; @@ -270,6 +266,12 @@ function initialisePanels() { // Add elements common to all panels newPanel.setTitle(panel.name); + if(panel.icon){ + newPanel.setIcon(panel.icon); + } else { + newPanel.setIcon(panelDefinition.icon); + } + if (panelDefinition.buttons != null){ var buttons = panel.ref.buttons.map( (btn) => { From 6f90c6895414e08c835501a04bbfe87a0397b9ed Mon Sep 17 00:00:00 2001 From: Will Barnett Date: Fri, 10 Nov 2023 11:38:58 +0000 Subject: [PATCH 2/3] Added support for the customisation of panel icons and buttons in activity config files, added Button object to improve cohesion of the code handling buttons, and removed button definitions from panels which are overridden by panel definitions. --- platform/src/Button.js | 80 ++++++++++++++++++++++++++++ platform/src/ConsolePanel.js | 15 ++++-- platform/src/MetamodelPanel.js | 17 ------ platform/src/ModelPanel.js | 17 ------ platform/src/OutputPanel.js | 18 ++++--- platform/src/Panel.js | 17 +----- platform/src/Playground.js | 66 +++++++++++------------ platform/test/spec/testButtonSpec.js | 51 ++++++++++++++++++ 8 files changed, 186 insertions(+), 95 deletions(-) create mode 100644 platform/src/Button.js create mode 100644 platform/test/spec/testButtonSpec.js diff --git a/platform/src/Button.js b/platform/src/Button.js new file mode 100644 index 0000000..2d817ee --- /dev/null +++ b/platform/src/Button.js @@ -0,0 +1,80 @@ + +var buttonTypes= { + BUTTON_ACTION: 'BUTTON_ACTION', + BUTTON_HELP: 'BUTTON_HELP' +} + +class Button { + id; + icon; + hint; + action; + type; + + /** + * Create a Button + * @param {object} buttonConfigObject + * @param {string} parentPanel + */ + constructor(buttonConfigObject, parentPanel){ + this.id= buttonConfigObject.id; + this.icon= buttonConfigObject.icon; + this.hint= buttonConfigObject.hint; + this.action = buttonConfigObject.action; + + // Set button's onclick action + if (buttonConfigObject["url"] != undefined) { + this.type = buttonTypes.BUTTON_HELP; + this.action = "window.open('" + buttonConfigObject.url + "');"; + + } else if (buttonConfigObject["actionfunction"] != undefined) { + this.type = buttonTypes.BUTTON_ACTION; + this.action = "runAction( '" + parentPanel + "', '" + buttonConfigObject.id +"' )"; + + } else if (buttonConfigObject["internal"] != undefined) { + this.action = buttonConfigObject.internal; + + } else { + console.log( "Button '" + buttonConfigObject.id + "' with uknown key."); + } + } + + + buttonHtml() { + return ""; + } + + /** + * Get a string representation of the button for its display + * @returns {String} DOM object with html, cls and onclick properties + */ + getView() { + var buttonData={}; + + buttonData.html= this.buttonHtml(); + buttonData.cls= "sys-button"; + buttonData.onclick= this.action; + + return buttonData; + } + + + /** + * Create an array of buttons from an array of configurations + * @param {object[]} buttonConfigs + * @param {string} parentPanel + * @returns {Button[]} the Button objects + */ + static createButtons(buttonConfigs, parentPanel){ + + let buttons= []; + + buttonConfigs.forEach((config)=>{ + buttons.push( new Button(config, parentPanel) ); + }) + + return buttons; + } +} + +export {Button} \ No newline at end of file diff --git a/platform/src/ConsolePanel.js b/platform/src/ConsolePanel.js index ddd4c13..a15403e 100644 --- a/platform/src/ConsolePanel.js +++ b/platform/src/ConsolePanel.js @@ -1,5 +1,6 @@ import { Panel } from "./Panel.js"; import { define } from "ace-builds"; +import { Button } from "./Button.js"; class ConsolePanel extends Panel { @@ -13,11 +14,15 @@ class ConsolePanel extends Panel { } getButtons() { - return [{ - html: this.buttonHtml("clear", "Clear the console"), - cls: "sys-button", - onclick: "consolePanel.setValue('')" - }]; + let clearButton = new Button({ + id:"clear", + hint:"Clear the console", + internal: `panels.find((p) => p.id==="${this.id}").editor.setValue('')`, + icon: "clear" + }, this.id + ); + + return [clearButton.getView()]; } setOutput(str) { diff --git a/platform/src/MetamodelPanel.js b/platform/src/MetamodelPanel.js index 918b6a7..03a1ca4 100644 --- a/platform/src/MetamodelPanel.js +++ b/platform/src/MetamodelPanel.js @@ -3,7 +3,6 @@ import { ModelPanel } from './ModelPanel.js'; class MetamodelPanel extends ModelPanel { constructor(id) { super(id, true, null); - this.element.dataset.customButtons = JSON.stringify(this.getButtons()); this.setTitleAndIcon("Metamodel", "emfatic"); } @@ -11,22 +10,6 @@ class MetamodelPanel extends ModelPanel { this.editor.getSession().setMode("ace/mode/emfatic"); } - getButtons() { - return [{ - html: this.buttonHtml("help", "Emfatic language reference"), - cls: "sys-button", - onclick: "window.open('https://www.eclipse.org/epsilon/doc/articles/playground/#emfatic-metamodels-in-the-playground');" - },{ - html: this.buttonHtml("refresh", "Render the metamodel class diagram"), - cls: "sys-button", - onclick: this.id + "Panel.refreshDiagram()" - },{ - html: this.buttonHtml("diagram", "Show/hide the metamodel class diagram"), - cls: "sys-button", - onclick: "toggle('" + this.id + "Diagram', function(){" + this.id + "Panel.refreshDiagram();})" - }]; - } - refreshDiagram() { this.refreshDiagramImpl(backend.getEmfaticToPlantUMLService(), this.id + "Diagram", "metamodel", null, this.getEditor()); } diff --git a/platform/src/ModelPanel.js b/platform/src/ModelPanel.js index 2192025..968ea53 100644 --- a/platform/src/ModelPanel.js +++ b/platform/src/ModelPanel.js @@ -13,7 +13,6 @@ class ModelPanel extends Panel { this.editable = editable; this.metamodelPanel = metamodelPanel; this.setupSyntaxHighlighting(); - this.element.dataset.customButtons = JSON.stringify(this.getButtons()); this.setTitleAndIcon("Model", "flexmi"); } @@ -62,22 +61,6 @@ class ModelPanel extends Panel { } } - getButtons() { - return this.editable ? [{ - html: this.buttonHtml("help", "Flexmi language reference"), - cls: "sys-button", - onclick: "window.open('https://www.eclipse.org/epsilon/doc/flexmi');" - }, { - html: this.buttonHtml("refresh", "Render the model object diagram"), - cls: "sys-button", - onclick: this.id + "Panel.refreshDiagram()" - }, { - html: this.buttonHtml("diagram", "Show/hide the model object diagram"), - cls: "sys-button", - onclick: "toggle('" + this.id + "Diagram', function(){" + this.id + "Panel.refreshDiagram();})" - }] : []; - } - /* TODO: Rename to something more sensible */ refreshDiagramImpl(url, diagramId, diagramName, modelEditor, metamodelEditor) { var xhr = new XMLHttpRequest(); diff --git a/platform/src/OutputPanel.js b/platform/src/OutputPanel.js index b4beaee..0b246ed 100644 --- a/platform/src/OutputPanel.js +++ b/platform/src/OutputPanel.js @@ -1,5 +1,7 @@ + import { ModelPanel } from "./ModelPanel.js"; -import { language } from "./Playground.js"; +import { language } from "./Playground.js" +import { Button } from "./Button.js"; class OutputPanel extends ModelPanel { @@ -21,11 +23,15 @@ class OutputPanel extends ModelPanel { setupSyntaxHighlighting() {} getButtons() { - return (this.outputType == "code") ? [{ - html: this.buttonHtml("highlight", "Set generated text language"), - cls: "sys-button", - onclick: this.id + "Panel.setOutputLanguage()" - }] : []; + let highlightButton = new Button({ + id:"highlight", + hint:"Set generated text language", + internal: `panels.find((p) => p.id==="${this.id}").editor.setOutputLanguage()`, + icon: "highlight" + }, this.id + ); + + return (this.outputType == "code") ? [highlightButton.getView()] : []; } getSelect() { diff --git a/platform/src/Panel.js b/platform/src/Panel.js index 00e95a3..b4b2f1b 100644 --- a/platform/src/Panel.js +++ b/platform/src/Panel.js @@ -97,27 +97,14 @@ class Panel { return this.type; } - buttonHtml(icon, hint) { - return ""; - } - - /** * Add the buttons to the page - * @param {object[]} buttons Objects with attributes: icon, hint, action - * - * TODO Support image files for icon + * @param {Button[]} buttons the Button objects to add */ addButtons(buttons){ var buttonViewData= buttons.map( (btn) => { - var buttonData={}; - - buttonData.html= this.buttonHtml(btn.icon, btn.hint); - buttonData.cls= "sys-button"; - buttonData.onclick= btn.action; - - return buttonData; + return btn.getView(); }); buttonViewData.reverse(); // So they are displayed in the order they are defined diff --git a/platform/src/Playground.js b/platform/src/Playground.js index 418a389..503581a 100644 --- a/platform/src/Playground.js +++ b/platform/src/Playground.js @@ -21,6 +21,7 @@ import { OutputPanel } from "./OutputPanel.js"; import { TestPanel } from './TestPanel.js'; import { BlankPanel } from './BlankPanel .js'; import { XtextEditorPanel } from './XtextEditorPanel.js'; +import { Button } from './Button.js'; import { Preloader } from './Preloader.js'; import { Backend } from './Backend.js'; @@ -323,50 +324,38 @@ function initialisePanels() { // Add elements common to all panels newPanel.setTitle(panel.name); - if(panel.icon){ + if(panel.icon != null){ newPanel.setIcon(panel.icon); } else { newPanel.setIcon(panelDefinition.icon); } - if (panelDefinition.buttons != null){ - - var buttons = panel.ref.buttons.map( (btn) => { - var buttonData = {}; - - buttonData.icon = btn.icon; - buttonData.hint = btn.hint; - buttonData.action = generateButtonOnclickHtml(btn, panel.id); - - return buttonData; + if (panel.buttons == null && panelDefinition.buttons != null){ + // No activity defined buttons + newPanel.addButtons( Button.createButtons( panelDefinition.buttons, panel.id)); + + } else if (panel.buttons != null && panelDefinition.buttons != null) { + // The activity has defined the buttons + let resolvedButtonConfigs = panel.buttons.map(btn =>{ + let resolvedButton; + + if (btn.ref){ + // button reference so resolve + resolvedButton= panelDefinition.buttons.find((pdBtn)=> pdBtn.id===btn.ref); + } else { + // activity defined button + resolvedButton= btn; + } + return resolvedButton; }); - - newPanel.addButtons(buttons); - } - - + panel.buttons = resolvedButtonConfigs; + newPanel.addButtons( Button.createButtons( resolvedButtonConfigs, panel.id)); + } + return newPanel; } -function generateButtonOnclickHtml(button, panelId){ - - var onclickHtml; - - if (button["url"] != undefined) { - onclickHtml = "window.open('" + button.url + "');"; - - } else if (button["actionfunction"] != undefined) { - onclickHtml = "runAction( '" + panelId + "', '" + button.id +"' )"; - - } else { - console.log( "Button '" + button.id + "' with uknown key."); - } - - return onclickHtml; -} - - function copyToClipboard(str) { var el = document.createElement('textarea'); el.value = str; @@ -818,7 +807,14 @@ function runAction(source, sourceButton) { // Get the action var action = activityManager.getActionForCurrentActivity(source, sourceButton); - const buttonConfig = action.source.ref.buttons.find( btn => btn.id == sourceButton ); + let buttonConfig; + if(action.source.buttons){ + //Buttons defined by activity + buttonConfig= action.source.buttons.find( btn => btn.id == sourceButton ); + } else { + //Buttons defined by tool + buttonConfig= action.source.ref.buttons.find( btn => btn.id == sourceButton ); + } const toolActionFunction = toolsManager.getActionFunction( buttonConfig.actionfunction ); // TODO tidy up by resolving tool references // Create map containing panel values diff --git a/platform/test/spec/testButtonSpec.js b/platform/test/spec/testButtonSpec.js new file mode 100644 index 0000000..405e995 --- /dev/null +++ b/platform/test/spec/testButtonSpec.js @@ -0,0 +1,51 @@ + +import {Button} from "../../src/Button.js" + + +describe("Button", () => { + + const btnConfigAf = { + id: "1", + icon: "ico", + actionfunction: "af", + hint: "hn" + } + + it("can be created", () => { + let btn = new Button(btnConfigAf, "pid"); + expect(btn instanceof Button).toBe(true); + }) + + it("has an id set by a config object", () => { + let btn = new Button(btnConfigAf, "pid"); + expect(btn.id).toBe("1"); + }) + + it("has an icon set by a config object", () => { + let btn = new Button(btnConfigAf, "pid"); + expect(btn.icon).toBe("ico"); + }) + + it("has an hint set by a config object", () => { + let btn = new Button(btnConfigAf, "pid"); + expect(btn.hint).toBe("hn"); + }) + + it("getView - outputs a DOM representation for customButtons properties", () => { + const expectedDomObject = { + "html": "", + "cls": "sys-button", + "onclick": "runAction( 'pid', '1' )" + } + + let btn = new Button(btnConfigAf, "pid"); + expect(btn.getView()).toEqual(expectedDomObject); + }) + + it("createButtons - creates multiple buttons from an array of button objects", () => { + let btns = Button.createButtons([btnConfigAf, btnConfigAf, btnConfigAf, btnConfigAf], "pid"); + + expect(btns.length).toBe(4); + expect(btns[0]).toEqual(new Button(btnConfigAf, "pid")); + }) +}) From 1032f28d23a6bfd62a52818ba38a225ddbc0cf43 Mon Sep 17 00:00:00 2001 From: Will Barnett Date: Fri, 10 Nov 2023 19:03:28 +0000 Subject: [PATCH 3/3] To address review comments from mdenet/educationplatform#106: revised built-in panels to use addButtons(), added missing semicolon, and corrected misleading comment. --- platform/src/ConsolePanel.js | 25 ++++++++++++------------- platform/src/OutputPanel.js | 29 +++++++++++++++-------------- platform/src/Panel.js | 15 ++++++++------- platform/src/Playground.js | 4 ++-- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/platform/src/ConsolePanel.js b/platform/src/ConsolePanel.js index a15403e..13e82e0 100644 --- a/platform/src/ConsolePanel.js +++ b/platform/src/ConsolePanel.js @@ -8,21 +8,20 @@ class ConsolePanel extends Panel { super(id); this.editor.setReadOnly(true); this.editor.setValue("", 1); - this.element.dataset.customButtons = JSON.stringify(this.getButtons()); - this.detectHyperlinks(this.editor); - this.setTitleAndIcon("Console", "console"); - } - getButtons() { - let clearButton = new Button({ - id:"clear", - hint:"Clear the console", - internal: `panels.find((p) => p.id==="${this.id}").editor.setValue('')`, - icon: "clear" - }, this.id + let buttons = []; + let clearButton = new Button( + { id:"clear", + hint:"Clear the console", + internal: `panels.find((p) => p.id==="${this.id}").editor.setValue('')`, + icon: "clear" }, + this.id ); - - return [clearButton.getView()]; + buttons.push(clearButton); + this.addButtons(buttons); + + this.detectHyperlinks(this.editor); + this.setTitleAndIcon("Console", "console"); } setOutput(str) { diff --git a/platform/src/OutputPanel.js b/platform/src/OutputPanel.js index 0b246ed..a6df7c2 100644 --- a/platform/src/OutputPanel.js +++ b/platform/src/OutputPanel.js @@ -1,6 +1,6 @@ import { ModelPanel } from "./ModelPanel.js"; -import { language } from "./Playground.js" +import { language } from "./Playground.js"; import { Button } from "./Button.js"; class OutputPanel extends ModelPanel { @@ -15,25 +15,26 @@ class OutputPanel extends ModelPanel { this.outputType = outputType; this.outputLanguage = outputLanguage; this.language = language; - this.element.dataset.customButtons = JSON.stringify(this.getButtons()); - this.getEditor().getSession().setMode("ace/mode/" + outputLanguage.toLowerCase()); - //this.getEditor().getSession().setUseWrapMode(false); - } - setupSyntaxHighlighting() {} - - getButtons() { - let highlightButton = new Button({ - id:"highlight", + let buttons = []; + if (this.outputType == "code"){ + let highlightButton = new Button( + { id:"highlight", hint:"Set generated text language", internal: `panels.find((p) => p.id==="${this.id}").editor.setOutputLanguage()`, - icon: "highlight" - }, this.id - ); + icon: "highlight" }, + this.id + ); + buttons.push(highlightButton); + } + this.addButtons(buttons); - return (this.outputType == "code") ? [highlightButton.getView()] : []; + this.getEditor().getSession().setMode("ace/mode/" + outputLanguage.toLowerCase()); } + setupSyntaxHighlighting() {} + + getSelect() { return Metro.getPlugin("#generatedFiles", 'select'); } diff --git a/platform/src/Panel.js b/platform/src/Panel.js index b4b2f1b..af624df 100644 --- a/platform/src/Panel.js +++ b/platform/src/Panel.js @@ -99,17 +99,18 @@ class Panel { /** * Add the buttons to the page - * @param {Button[]} buttons the Button objects to add + * @param {Button[]} buttons - The Button objects to add. */ addButtons(buttons){ + if (buttons.length > 0){ + var buttonViewData= buttons.map( (btn) => { + return btn.getView(); + }); - var buttonViewData= buttons.map( (btn) => { - return btn.getView(); - }); + buttonViewData.reverse(); // So they are displayed in the order they are defined - buttonViewData.reverse(); // So they are displayed in the order they are defined - - this.element.dataset.customButtons = JSON.stringify(buttonViewData); + this.element.dataset.customButtons = JSON.stringify(buttonViewData); + } } diff --git a/platform/src/Playground.js b/platform/src/Playground.js index 503581a..af24dde 100644 --- a/platform/src/Playground.js +++ b/platform/src/Playground.js @@ -263,8 +263,8 @@ function initialisePanels() { /** * Create a panel for a given panel config entry * - * @param {Object} activity config panel definition - * @return {Panel} platform Panel + * @param {Object} panel - The activity config panel definition. + * @return {Panel} the platform Panel */ function createPanelForDefinitionId(panel){ const panelDefinition = panel.ref;