From 284fe5087a15b48fc2618eb5b9630e8e2f4df0c5 Mon Sep 17 00:00:00 2001 From: Will Barnett Date: Tue, 12 Mar 2024 23:58:52 +0000 Subject: [PATCH 1/6] Move platform methods to class for testability --- platform/src/Playground.js | 1626 ++++++++++++++++++------------------ 1 file changed, 819 insertions(+), 807 deletions(-) diff --git a/platform/src/Playground.js b/platform/src/Playground.js index 0cb8714..5c90a3d 100644 --- a/platform/src/Playground.js +++ b/platform/src/Playground.js @@ -1,5 +1,4 @@ /*global $ -- jquery is exterally imported*/ -/*global TOKEN_SERVER_URL -- is set by environment variable*/ /*global FEEDBACK_SURVEY_URL -- is set by environment variable*/ /*global Metro -- Metro is externally imported*/ @@ -35,7 +34,7 @@ import { Layout } from './Layout.js'; import { PlaygroundUtility } from './PlaygroundUtility.js'; import { jsonRequest, jsonRequestConversion, ARRAY_ANY_ELEMENT, urlParamPrivateRepo } from './Utility.js'; -const TOKEN_HANDLER_URL = TOKEN_SERVER_URL || "http://127.0.0.1:10000"; +const TOKEN_HANDLER_URL = PlaygroundUtility.getTokenServerAddress(); const COMMON_UTILITY_URL = window.location.href.replace(window.location.search,"") + "common/utility.json"; var outputType = "text"; @@ -50,1018 +49,1031 @@ export var toolsManager; var urlParameters = new URLSearchParams(window.location.search); -if (FEEDBACK_SURVEY_URL){ - PlaygroundUtility.setFeedbackButtonUrl(FEEDBACK_SURVEY_URL); - PlaygroundUtility.showFeedbackButton(); -} -document.getElementById("btnnologin").onclick= () => { +class EducationPlatform { - PlaygroundUtility.hideLogin(); -} + initialize(){ + /* + * Setup the browser environment + */ + if (FEEDBACK_SURVEY_URL){ + PlaygroundUtility.setFeedbackButtonUrl(FEEDBACK_SURVEY_URL); + PlaygroundUtility.showFeedbackButton(); + } + document.getElementById("btnnologin").onclick= () => { -if (!urlParamPrivateRepo()){ - // Public repo so no need to authenticate - initializeActivity(); - -} else { - PlaygroundUtility.showLogin(); -} + PlaygroundUtility.hideLogin(); + } -document.getElementById("btnlogin").onclick= async () => { - // Get github url - const urlRequest = { url: window.location.href }; - let authServerDetails= await jsonRequest(TOKEN_HANDLER_URL + "/mdenet-auth/login/url", - JSON.stringify(urlRequest) ); + if (!urlParamPrivateRepo()){ + // Public repo so no need to authenticate + this.initializeActivity(); + + } else { + PlaygroundUtility.showLogin(); + } - + document.getElementById("btnlogin").onclick= async () => { - authServerDetails = JSON.parse(authServerDetails); + // Get github url + const urlRequest = { url: window.location.href }; + let authServerDetails= await jsonRequest(TOKEN_HANDLER_URL + "/mdenet-auth/login/url", + JSON.stringify(urlRequest) ); - // Authenticate redirect - window.location.href = authServerDetails.url; -} + -if (urlParameters.has("code") && urlParameters.has("state") ){ - // Returning from authentication redirect - PlaygroundUtility.hideLogin(); - - //Complete authentication - const tokenRequest = {}; - tokenRequest.state = urlParameters.get("state"); - tokenRequest.code = urlParameters.get("code"); - - //TODO loading box - let authDetails = jsonRequest(TOKEN_HANDLER_URL + "/mdenet-auth/login/token", - JSON.stringify(tokenRequest), true ); - authDetails.then( () => { - document.getElementById('save')?.classList.remove('hidden'); - window.sessionStorage.setItem("isAuthenticated", true); - initializeActivity(); - } ); -} + authServerDetails = JSON.parse(authServerDetails); -// Clean authentication parameters from url -urlParameters.delete("code"); -urlParameters.delete("state"); - - -// Encode ':' and '/' with toString -// Skips the default encoding to so that the URL can be reused -let params = []; -for (const [key, value] of urlParameters) { - // For a specific key ('activities' in this case), you add it to the array without encoding - if (key === 'activities') { - params.push(`${key}=${value}`); - } else { - // For all other parameters, you still want to encode them - params.push(`${key}=${encodeURIComponent(value)}`); - } -} -// Now join all the parameters with '&' to form the query string -let queryString = params.join('&'); + // Authenticate redirect + window.location.href = authServerDetails.url; + } -// Use replaceState to update the URL without encoding the parameter -window.history.replaceState({}, document.title, "?" + queryString); + if (urlParameters.has("code") && urlParameters.has("state") ){ + // Returning from authentication redirect + PlaygroundUtility.hideLogin(); + + //Complete authentication + const tokenRequest = {}; + tokenRequest.state = urlParameters.get("state"); + tokenRequest.code = urlParameters.get("code"); + + //TODO loading box + let authDetails = jsonRequest(TOKEN_HANDLER_URL + "/mdenet-auth/login/token", + JSON.stringify(tokenRequest), true ); + authDetails.then( () => { + document.getElementById('save')?.classList.remove('hidden'); + window.sessionStorage.setItem("isAuthenticated", true); + this.initializeActivity(); + } ); + } + // Clean authentication parameters from url + urlParameters.delete("code"); + urlParameters.delete("state"); -function initializeActivity(){ - let errors = []; + // Encode ':' and '/' with toString + // Skips the default encoding to so that the URL can be reused + let params = []; + for (const [key, value] of urlParameters) { + // For a specific key ('activities' in this case), you add it to the array without encoding + if (key === 'activities') { + params.push(`${key}=${value}`); + } else { + // For all other parameters, you still want to encode them + params.push(`${key}=${encodeURIComponent(value)}`); + } + } + // Now join all the parameters with '&' to form the query string + let queryString = params.join('&'); - if (!urlParameters.has("activities")) { - // No activity configuration has been given - errors.push(new EducationPlatformError("No activity configuration has been specified.")); + // Use replaceState to update the URL without encoding the parameter + window.history.replaceState({}, document.title, "?" + queryString); } - if (errors.length==0){ - // An activity configuration has been provided - toolsManager = new ToolsManager(); - activityManager = new ActivityManager( (toolsManager.getPanelDefinition).bind(toolsManager), fileHandler ); - activityManager.initializeActivities(); - errors = errors.concat(activityManager.getConfigErrors()); - } - - if (errors.length==0){ - // The activities have been validated - toolsManager.setToolsUrls( activityManager.getToolUrls().add(COMMON_UTILITY_URL) ); - errors = errors.concat(toolsManager.getConfigErrors()); - } - if (errors.length==0){ - // The tools have been validated - activityManager.showActivitiesNavEntries(); - // Import tool grammar highlighting - const toolImports = toolsManager.getToolsGrammarImports(); + initializeActivity(){ + + let errors = []; - for(let ipt of toolImports) { - ace.config.setModuleUrl(ipt.module, ipt.url); + if (!urlParameters.has("activities")) { + // No activity configuration has been given + errors.push(new EducationPlatformError("No activity configuration has been specified.")); } - // Add Tool styles for icons - for (let toolUrl of activityManager.getToolUrls()){ - let toolBaseUrl = toolUrl.substring(0, toolUrl.lastIndexOf("/")); - var link = document.createElement("link"); - link.setAttribute("rel", 'stylesheet'); - link.setAttribute("href", toolBaseUrl + "/icons.css"); - document.head.appendChild(link); + if (errors.length==0){ + // An activity configuration has been provided + toolsManager = new ToolsManager(); + activityManager = new ActivityManager( (toolsManager.getPanelDefinition).bind(toolsManager), fileHandler ); + activityManager.initializeActivities(); + errors = errors.concat(activityManager.getConfigErrors()); + } + + if (errors.length==0){ + // The activities have been validated + toolsManager.setToolsUrls( activityManager.getToolUrls().add(COMMON_UTILITY_URL) ); + errors = errors.concat(toolsManager.getConfigErrors()); } - - activity = activityManager.getSelectedActivity(); - // Validate the resolved activity - errors = errors.concat( ActivityValidator.validate(activity, toolsManager.tools) ); - } + if (errors.length==0){ + // The tools have been validated + activityManager.showActivitiesNavEntries(); - if (errors.length==0){ - // The resolved activity has been validated - initializePanels(); - } + // Import tool grammar highlighting + const toolImports = toolsManager.getToolsGrammarImports(); - if (errors.length > 0) { - displayErrors(errors); - } -} + for(let ipt of toolImports) { + ace.config.setModuleUrl(ipt.module, ipt.url); + } -function displayErrors(errors){ + // Add Tool styles for icons + for (let toolUrl of activityManager.getToolUrls()){ + let toolBaseUrl = toolUrl.substring(0, toolUrl.lastIndexOf("/")); + var link = document.createElement("link"); + link.setAttribute("rel", 'stylesheet'); + link.setAttribute("href", toolBaseUrl + "/icons.css"); + document.head.appendChild(link); + } + + activity = activityManager.getSelectedActivity(); - const contentPanelName = "content-panel"; - - panels.push(new BlankPanel(contentPanelName)); - panels[0].setVisible(true); - - new Layout().createFromPanels("navview-content", panels); - - PlaygroundUtility.showMenu(); - - Metro.init(); - fit(); - - var contentPanelDiv = document.getElementById(contentPanelName); + // Validate the resolved activity + errors = errors.concat( ActivityValidator.validate(activity, toolsManager.tools) ); + } - // EP Errors - const platformErrors= errors.filter((e)=> e.constructor.name === EducationPlatformError.name); + if (errors.length==0){ + // The resolved activity has been validated + this.initializePanels(); + } - if (platformErrors.length > 0){ - let contentTitle = document.createElement("h2"); - contentTitle.innerText = "Education Platform Errors:"; - contentPanelDiv.append(contentTitle); + if (errors.length > 0) { + this.displayErrors(errors); + } + } - platformErrors.forEach( (err) => { - let content = document.createElement("p"); - content.append(document.createTextNode(err.message)); + displayErrors(errors){ - contentPanelDiv.append(content); - }); + const contentPanelName = "content-panel"; + + panels.push(new BlankPanel(contentPanelName)); + panels[0].setVisible(true); + + new Layout().createFromPanels("navview-content", panels); + + PlaygroundUtility.showMenu(); + + Metro.init(); + this.fit(); + + var contentPanelDiv = document.getElementById(contentPanelName); - contentPanelDiv.append(document.createElement("p")); - } + // EP Errors + const platformErrors= errors.filter((e)=> e.constructor.name === EducationPlatformError.name); - // Config File Errors - const configErrors= errors.filter((e)=> e.constructor.name === ConfigValidationError.name); + if (platformErrors.length > 0){ + let contentTitle = document.createElement("h2"); + contentTitle.innerText = "Education Platform Errors:"; + contentPanelDiv.append(contentTitle); - if(configErrors.length > 0){ - let contentTitle = document.createElement("h2"); - contentTitle.innerText = "Config File Errors:"; - contentPanelDiv.append(contentTitle); + platformErrors.forEach( (err) => { + let content = document.createElement("p"); + content.append(document.createTextNode(err.message)); - let contentLabels = document.createElement("b"); - contentLabels.innerText = "File | Category | Details | Location"; - contentPanelDiv.append(contentLabels); + contentPanelDiv.append(content); + }); - configErrors.forEach( (err) => { - let content = document.createElement("p"); - let contentText= `${err.fileType} | ${err.category} | ${err.message} | ${err.location}` ; - content.append(document.createTextNode(contentText)); + contentPanelDiv.append(document.createElement("p")); + } - contentPanelDiv.append(content); - }); - } + // Config File Errors + const configErrors= errors.filter((e)=> e.constructor.name === ConfigValidationError.name); - const otherErrors = errors.filter((e) => !(configErrors.includes(e) || platformErrors.includes(e))) - if (otherErrors.length > 0) { - let contentTitle = document.createElement("h2"); - contentTitle.innerText = "Errors:"; - contentPanelDiv.append(contentTitle); + if(configErrors.length > 0){ + let contentTitle = document.createElement("h2"); + contentTitle.innerText = "Config File Errors:"; + contentPanelDiv.append(contentTitle); - otherErrors.forEach( (err) => { - let content = document.createElement("p"); - let contentText= `${err.constructor.name}: ${err.message}` ; - content.append(document.createTextNode(contentText)); + let contentLabels = document.createElement("b"); + contentLabels.innerText = "File | Category | Details | Location"; + contentPanelDiv.append(contentLabels); - contentPanelDiv.append(content); - }); - } -} + configErrors.forEach( (err) => { + let content = document.createElement("p"); + let contentText= `${err.fileType} | ${err.category} | ${err.message} | ${err.location}` ; + content.append(document.createTextNode(contentText)); -function initializePanels() { - - if (activity.outputLanguage != null) { - outputLanguage = activity.outputLanguage; + contentPanelDiv.append(content); + }); + } + + const otherErrors = errors.filter((e) => !(configErrors.includes(e) || platformErrors.includes(e))) + if (otherErrors.length > 0) { + let contentTitle = document.createElement("h2"); + contentTitle.innerText = "Errors:"; + contentPanelDiv.append(contentTitle); + + otherErrors.forEach( (err) => { + let content = document.createElement("p"); + let contentText= `${err.constructor.name}: ${err.message}` ; + content.append(document.createTextNode(contentText)); + + contentPanelDiv.append(content); + }); + } } - - // Create panels for the given activites - for ( let apanel of activity.panels ){ - var newPanel = createPanelForDefinitionId(apanel); - if (newPanel != null){ - panels.push(newPanel); + initializePanels() { + + if (activity.outputLanguage != null) { + outputLanguage = activity.outputLanguage; } - } + + // Create panels for the given activites + for ( let apanel of activity.panels ){ + var newPanel = this.createPanelForDefinitionId(apanel); + if (newPanel != null){ + panels.push(newPanel); + } + } - new Layout().createFrom2dArray("navview-content", panels, activity.layout.area); + new Layout().createFrom2dArray("navview-content", panels, activity.layout.area); - PlaygroundUtility.showMenu(); - - document.addEventListener('click', function(evt) { - if (evt.target == document.getElementById("toggleNavViewPane")) { - setTimeout(function(){ fit(); }, 1000); - } - }); - Metro.init(); + PlaygroundUtility.showMenu(); + + document.addEventListener('click', function(evt) { + if (evt.target == document.getElementById("toggleNavViewPane")) { + setTimeout(function(){ this.fit(); }, 1000); + } + }); - activityManager.openActiveActivitiesSubMenu(); - - fit(); -} + Metro.init(); + activityManager.openActiveActivitiesSubMenu(); + + this.fit(); + } -/** - * Create a panel for a given panel config entry - * - * @param {Object} panel - The activity config panel definition. - * @return {Panel} the platform Panel - */ -function createPanelForDefinitionId(panel){ - const panelDefinition = panel.ref; - var newPanel = null; - const newPanelId= panel.id; + /** + * Create a panel for a given panel config entry + * + * @param {Object} panel - The activity config panel definition. + * @return {Panel} the platform Panel + */ + createPanelForDefinitionId(panel){ + const panelDefinition = panel.ref; + var newPanel = null; - if (panelDefinition != null){ + const newPanelId= panel.id; - switch(panelDefinition.panelclass) { - case "ProgramPanel": { - newPanel = new ProgramPanel(newPanelId); - newPanel.initialize(); - - // Set from the tool panel definition - newPanel.setEditorMode(panelDefinition.language); + if (panelDefinition != null){ - newPanel.setType(panelDefinition.language); + switch(panelDefinition.panelclass) { + case "ProgramPanel": { + newPanel = new ProgramPanel(newPanelId); + newPanel.initialize(); + + // Set from the tool panel definition + newPanel.setEditorMode(panelDefinition.language); - // Set from the activity - newPanel.setValue(panel.file); - newPanel.setValueSha(panel.sha); - newPanel.setFileUrl(panel.url); - break; - } - case "ConsolePanel": { - newPanel = new ConsolePanel(newPanelId); - newPanel.initialize(); - break; - } - case "OutputPanel": { - newPanel = new OutputPanel(newPanelId, panelDefinition.language, outputType, outputLanguage); - newPanel.initialize(); - break; - } - case "XtextEditorPanel": { - let editorUrl = sessionStorage.getItem(newPanelId); - - newPanel = new XtextEditorPanel(newPanelId); - newPanel.initialize(editorUrl, panel.extension); - newPanel.setType(panelDefinition.language); + newPanel.setType(panelDefinition.language); - // Set from the activity - newPanel.setValue(panel.file); - newPanel.setValueSha(panel.sha); - newPanel.setFileUrl(panel.url) + // Set from the activity + newPanel.setValue(panel.file); + newPanel.setValueSha(panel.sha); + newPanel.setFileUrl(panel.url); + break; + } + case "ConsolePanel": { + newPanel = new ConsolePanel(newPanelId); + newPanel.initialize(); + break; + } + case "OutputPanel": { + newPanel = new OutputPanel(newPanelId, panelDefinition.language, outputType, outputLanguage); + newPanel.initialize(); + break; + } + case "XtextEditorPanel": { + let editorUrl = sessionStorage.getItem(newPanelId); + + newPanel = new XtextEditorPanel(newPanelId); + newPanel.initialize(editorUrl, panel.extension); + newPanel.setType(panelDefinition.language); - break; - } - case "CompositePanel": { + // Set from the activity + newPanel.setValue(panel.file); + newPanel.setValueSha(panel.sha); + newPanel.setFileUrl(panel.url) - newPanel = new CompositePanel(newPanelId); - if (panel.childPanels) { - for (let childPanelConfig of panel.childPanels) { - var childPanel = createPanelForDefinitionId(childPanelConfig); - newPanel.addPanel(childPanel); + break; + } + case "CompositePanel": { + + newPanel = new CompositePanel(newPanelId); + if (panel.childPanels) { + for (let childPanelConfig of panel.childPanels) { + var childPanel = this.createPanelForDefinitionId(childPanelConfig); + newPanel.addPanel(childPanel); + } } + newPanel.initialize(); + + break; } - newPanel.initialize(); - - break; + // TODO create other panel types e.g. models and metamodels so the text is formatted correctly + default: { + newPanel = new TestPanel(newPanelId); + } } - // TODO create other panel types e.g. models and metamodels so the text is formatted correctly - default: { - newPanel = new TestPanel(newPanelId); - } - } - - // Add elements common to all panels - newPanel.setTitle(panel.name); - - if(panel.icon != null){ - newPanel.setIcon(panel.icon); - } else { - newPanel.setIcon(panelDefinition.icon); - } - if (panel.buttons == null && panelDefinition.buttons != null){ - // No activity defined buttons - newPanel.addButtons( Button.createButtons( panelDefinition.buttons, panel.id)); - - } else if (panel.buttons != null) { - // The activity has defined the buttons, some may be references to buttons defined in the tool spec - let resolvedButtonConfigs = panel.buttons.map(btn =>{ - let resolvedButton; - - if (btn.ref){ - if (panelDefinition.buttons != null) { - // button reference so resolve - resolvedButton = panelDefinition.buttons.find((pdBtn)=> pdBtn.id===btn.ref); + // Add elements common to all panels + newPanel.setTitle(panel.name); + + if(panel.icon != null){ + newPanel.setIcon(panel.icon); + } else { + newPanel.setIcon(panelDefinition.icon); + } + + if (panel.buttons == null && panelDefinition.buttons != null){ + // No activity defined buttons + newPanel.addButtons( Button.createButtons( panelDefinition.buttons, panel.id)); + + } else if (panel.buttons != null) { + // The activity has defined the buttons, some may be references to buttons defined in the tool spec + let resolvedButtonConfigs = panel.buttons.map(btn =>{ + let resolvedButton; + + if (btn.ref){ + if (panelDefinition.buttons != null) { + // button reference so resolve + resolvedButton = panelDefinition.buttons.find((pdBtn)=> pdBtn.id===btn.ref); + } + } else { + // activity defined button + resolvedButton = btn; } - } else { - // activity defined button - resolvedButton = btn; - } - return resolvedButton; - }); - panel.buttons = resolvedButtonConfigs; - newPanel.addButtons( Button.createButtons( resolvedButtonConfigs, panel.id)); + return resolvedButton; + }); + panel.buttons = resolvedButtonConfigs; + newPanel.addButtons( Button.createButtons( resolvedButtonConfigs, panel.id)); + } } + return newPanel; } - return newPanel; -} -function getPanelTitle(panelId) { - return $("#" + panelId)[0].dataset.titleCaption; -} + getPanelTitle(panelId) { + return $("#" + panelId)[0].dataset.titleCaption; + } -/** - * Invokes an action function by placing requests to all the required external tool functions - * - * TODO: To be moved to the ToolManager issue #40 - * - * @param {string} functionId the id of tool function - * @param {Map} parameterMap map from parameter name to its value and type - * @returns {Promise} promise to result of the action function - */ -function invokeActionFunction(functionId, parameterMap){ + /** + * Invokes an action function by placing requests to all the required external tool functions + * + * TODO: To be moved to the ToolManager issue #40 + * + * @param {string} functionId the id of tool function + * @param {Map} parameterMap map from parameter name to its value and type + * @returns {Promise} promise to result of the action function + */ + invokeActionFunction(functionId, parameterMap){ - let actionFunction = toolsManager.functionRegistry_resolve(functionId); + let actionFunction = toolsManager.functionRegistry_resolve(functionId); - let parameterPromises = []; + let parameterPromises = []; - for ( const paramName of parameterMap.keys() ){ /* TODO add defensive checks that every required value - is provided issue #57 */ + for ( const paramName of parameterMap.keys() ){ /* TODO add defensive checks that every required value + is provided issue #57 */ - let actionFunctionParam = actionFunction.getParameters().find( p => p.name === paramName); - - /* Check the given parameter types against the those of the requested action function. - If required, request conversion from available tool functions */ - let givenParameter = parameterMap.get(paramName); + let actionFunctionParam = actionFunction.getParameters().find( p => p.name === paramName); + + /* Check the given parameter types against the those of the requested action function. + If required, request conversion from available tool functions */ + let givenParameter = parameterMap.get(paramName); - if (givenParameter.type != actionFunctionParam.type){ - //Types don't match so try and convert - let convertedValue; + if (givenParameter.type != actionFunctionParam.type){ + //Types don't match so try and convert + let convertedValue; - const metamodelId = actionFunction.getInstanceOfParamName(paramName); - - if(metamodelId==null){ - // Convert with no metamodel to consider - convertedValue = convert( givenParameter.value, givenParameter.type, - actionFunctionParam.type, paramName ); // TODO issue #58 remove paramName + const metamodelId = actionFunction.getInstanceOfParamName(paramName); + + if(metamodelId==null){ + // Convert with no metamodel to consider + convertedValue = this.convert( givenParameter.value, givenParameter.type, + actionFunctionParam.type, paramName ); // TODO issue #58 remove paramName - } else { - // Convert considering metamodel - const givenMetamodel = parameterMap.get(metamodelId); + } else { + // Convert considering metamodel + const givenMetamodel = parameterMap.get(metamodelId); - convertedValue = convertIncludingMetamodel( givenParameter.value , givenParameter.type, - givenMetamodel.value, givenMetamodel.type, - actionFunctionParam.type, paramName ); // TODO issue #58 remove paramName - } + convertedValue = this.convertIncludingMetamodel( givenParameter.value , givenParameter.type, + givenMetamodel.value, givenMetamodel.type, + actionFunctionParam.type, paramName ); // TODO issue #58 remove paramName + } - parameterPromises.push(convertedValue); + parameterPromises.push(convertedValue); + + } else { + // Matching types add values to promise for synchronisation + let value = new Promise( function (resolve) { + let parameterData = {}; + + parameterData.name = paramName; + parameterData.data = givenParameter.value; - } else { - // Matching types add values to promise for synchronisation - let value = new Promise( function (resolve) { - let parameterData = {}; - - parameterData.name = paramName; - parameterData.data = givenParameter.value; - - resolve(parameterData); - }); + resolve(parameterData); + }); - parameterPromises.push(value); + parameterPromises.push(value); + } } - } - // Invoke the actionFunction on compeletion of any conversions - let actionFunctionPromise = new Promise(function (resolve, reject) { + // Invoke the actionFunction on compeletion of any conversions + let actionFunctionPromise = new Promise( (resolve, reject) => { - Promise.all( parameterPromises ).then( (values) => { - let actionRequestData = {}; + Promise.all( parameterPromises ).then( (values) => { + let actionRequestData = {}; - //Populate the transformed parameters - for ( const param of actionFunction.getParameters() ){ + //Populate the transformed parameters + for ( const param of actionFunction.getParameters() ){ - const panelConfig = parameterMap.get(param.name); + const panelConfig = parameterMap.get(param.name); - if (panelConfig == undefined){ - // Set unused parameters in the request to undefined as the epsilon backend function expects them all. - actionRequestData[param.name] = "undefined"; + if (panelConfig == undefined){ + // Set unused parameters in the request to undefined as the epsilon backend function expects them all. + actionRequestData[param.name] = "undefined"; - } else { - let parameterData = values.find(val => (val.name === param.name) ); + } else { + let parameterData = values.find(val => (val.name === param.name) ); - actionRequestData[param.name] = parameterData.data; + actionRequestData[param.name] = parameterData.data; + } } - } - let resultPromise = functionRegistry_call(functionId, actionRequestData); + let resultPromise = this.functionRegistry_call(functionId, actionRequestData); - resolve(resultPromise); - - }).catch( (err) => { + resolve(resultPromise); + + }).catch( (err) => { + + reject(err); + }); - reject(err); }); - }); + return actionFunctionPromise; + } - return actionFunctionPromise; -} + /** + * Converts a source value to a target type using the available conversion functions + * + * TODO: To be moved to the ToolManager issue #40 + * + * @param {string} sourceValue + * @param {string} sourceType + * @param {string} targetType + * @param {string} parameterName name of the parameter for request + * @returns {Promise} promise for the converted paramter value + */ + convert(sourceValue, sourceType, targetType, parameterName){ + + let parameterPromise; + let typesPanelValuesMap = {}; // Types have to be distinct for mapping to the conversion function's paramters + typesPanelValuesMap[sourceType]= sourceValue; -/** - * Converts a source value to a target type using the available conversion functions - * - * TODO: To be moved to the ToolManager issue #40 - * - * @param {string} sourceValue - * @param {string} sourceType - * @param {string} targetType - * @param {string} parameterName name of the parameter for request - * @returns {Promise} promise for the converted paramter value - */ -function convert(sourceValue, sourceType, targetType, parameterName){ - - let parameterPromise; - let typesPanelValuesMap = {}; // Types have to be distinct for mapping to the conversion function's paramters - typesPanelValuesMap[sourceType]= sourceValue; + let conversionFunctionId = this.functionRegistry_find( Object.keys(typesPanelValuesMap), targetType ); - let conversionFunctionId = functionRegistry_find( Object.keys(typesPanelValuesMap), targetType ); + if (conversionFunctionId != null){ + //There is a matching conversion function + parameterPromise = this.functionRegistry_callConversion(conversionFunctionId, typesPanelValuesMap, parameterName); + + } else { + parameterPromise = null; + this.errorNotification("No conversion function available for input types:" + Object.keys(typesPanelValuesMap).toString() ) + } - if (conversionFunctionId != null){ - //There is a matching conversion function - parameterPromise = functionRegistry_callConversion(conversionFunctionId, typesPanelValuesMap, parameterName); - - } else { - parameterPromise = null; - errorNotification("No conversion function available for input types:" + Object.keys(typesPanelValuesMap).toString() ) + return parameterPromise; } - return parameterPromise; -} + /** + * Converts a source value to a target type using the available conversion functions taking + * into consideration the related metamodel. + * + * TODO: To be moved to the ToolManager issue #40 + * + * @param {string} sourceValue + * @param {string} sourceType + * @param {string} metamodelValue + * @param {string} metamodelType + * @param {string} targetType + * @param {string} parameterName name of the parameter for request + * @returns {Promise} promise for the converted paramter value + */ + async convertIncludingMetamodel(sourceValue, sourceType, metamodelValue, metamodelType, targetType, parameterName){ + let parameterPromise; + let typesPanelValuesMap = {}; // Types have to be distinct for mapping to the conversion function's paramters + typesPanelValuesMap[sourceType]= sourceValue; + + let conversionFunctionId; + + let potentialConversionFunctions = this.functionRegistry_findPartial( [sourceType, ARRAY_ANY_ELEMENT], targetType); + + //check for a conversion function with the metamodel type + conversionFunctionId = await this.selectConversionFunctionConvertMetamodel( metamodelType, metamodelValue, potentialConversionFunctions, + false, parameterName, typesPanelValuesMap) + + if (conversionFunctionId==null){ + //no conversion found so check for a conversion function but consider conversions of the metamodel + conversionFunctionId = await this.selectConversionFunctionConvertMetamodel(metamodelType, metamodelValue, potentialConversionFunctions, + true, parameterName, typesPanelValuesMap); + } -/** - * Converts a source value to a target type using the available conversion functions taking - * into consideration the related metamodel. - * - * TODO: To be moved to the ToolManager issue #40 - * - * @param {string} sourceValue - * @param {string} sourceType - * @param {string} metamodelValue - * @param {string} metamodelType - * @param {string} targetType - * @param {string} parameterName name of the parameter for request - * @returns {Promise} promise for the converted paramter value - */ -async function convertIncludingMetamodel(sourceValue, sourceType, metamodelValue, metamodelType, targetType, parameterName){ - let parameterPromise; - let typesPanelValuesMap = {}; // Types have to be distinct for mapping to the conversion function's paramters - typesPanelValuesMap[sourceType]= sourceValue; - - let conversionFunctionId; - - let potentialConversionFunctions = functionRegistry_findPartial( [sourceType, ARRAY_ANY_ELEMENT], targetType); - - //check for a conversion function with the metamodel type - conversionFunctionId = await selectConversionFunctionConvertMetamodel( metamodelType, metamodelValue, potentialConversionFunctions, - false, parameterName, typesPanelValuesMap) - - if (conversionFunctionId==null){ - //no conversion found so check for a conversion function but consider conversions of the metamodel - conversionFunctionId = await selectConversionFunctionConvertMetamodel(metamodelType, metamodelValue, potentialConversionFunctions, - true, parameterName, typesPanelValuesMap); - } + if (conversionFunctionId != null){ + //There is a matching conversion function + parameterPromise = this.functionRegistry_callConversion(conversionFunctionId, typesPanelValuesMap, parameterName); + + } else { + parameterPromise = null; + this.errorNotification("No conversion function available for input types:" + Object.keys(typesPanelValuesMap).toString() ) + } - if (conversionFunctionId != null){ - //There is a matching conversion function - parameterPromise = functionRegistry_callConversion(conversionFunctionId, typesPanelValuesMap, parameterName); - - } else { - parameterPromise = null; - errorNotification("No conversion function available for input types:" + Object.keys(typesPanelValuesMap).toString() ) + return parameterPromise; } - return parameterPromise; -} - -/** - * For the given list of conversion function ids to check, finds the first conversion function with matching metamodel dependency. - * Optionally conversions of the metamodel are considered from the conversion functions available to the tools manager and - * the metamodel type. If available, the metamodel value is converted to the required type. - * - * @param {string} metamodelType the metamodel type - * @param {string} metamodelValue the metamodel value - * @param {string[]} conversionFunctions list of conversion function ids to check - * @param {boolean} convertMetamodel when true try to convert the metamodel using a remote tool service conversion function - * available to the ToolsManager. - * @param {string} parameterName the name of the parameter to use when convering the metamodel. - * @param {string[]} typeValueMap the type values map the metamodel input value is added to if a conversion function is found - * @returns {string} the id of a conversion function to use, null if none found. - */ -async function selectConversionFunctionConvertMetamodel(metamodelType, metamodelValue, conversionFunctions, convertMetamodel, parameterName, typeValueMap){ - let conversionFunctionId; - let functionsToCheck = [...conversionFunctions] - - while ( conversionFunctionId==null && functionsToCheck.length > 0){ - let functionId = functionsToCheck.pop(); - let conversionFunction = toolsManager.getActionFunction(functionId); + /** + * For the given list of conversion function ids to check, finds the first conversion function with matching metamodel dependency. + * Optionally conversions of the metamodel are considered from the conversion functions available to the tools manager and + * the metamodel type. If available, the metamodel value is converted to the required type. + * + * @param {string} metamodelType the metamodel type + * @param {string} metamodelValue the metamodel value + * @param {string[]} conversionFunctions list of conversion function ids to check + * @param {boolean} convertMetamodel when true try to convert the metamodel using a remote tool service conversion function + * available to the ToolsManager. + * @param {string} parameterName the name of the parameter to use when convering the metamodel. + * @param {string[]} typeValueMap the type values map the metamodel input value is added to if a conversion function is found + * @returns {string} the id of a conversion function to use, null if none found. + */ + async selectConversionFunctionConvertMetamodel(metamodelType, metamodelValue, conversionFunctions, convertMetamodel, parameterName, typeValueMap){ + let conversionFunctionId; + let functionsToCheck = [...conversionFunctions] + + while ( conversionFunctionId==null && functionsToCheck.length > 0){ + let functionId = functionsToCheck.pop(); + let conversionFunction = toolsManager.getActionFunction(functionId); - // Lookup the conversion function's metamodel type - let metamodelName = conversionFunction.getInstanceOfParamName( conversionFunction.getParameters()[0].name ); + // Lookup the conversion function's metamodel type + let metamodelName = conversionFunction.getInstanceOfParamName( conversionFunction.getParameters()[0].name ); - if(metamodelName==null){ - metamodelName = conversionFunction.getInstanceOfParamName( conversionFunction.getParameters()[1].name ); - } + if(metamodelName==null){ + metamodelName = conversionFunction.getInstanceOfParamName( conversionFunction.getParameters()[1].name ); + } - const targetMetamodelType = conversionFunction.getParameterType(metamodelName); + const targetMetamodelType = conversionFunction.getParameterType(metamodelName); - if (!convertMetamodel){ - // Check for conversion functions with matching metamodels only - - if (targetMetamodelType==metamodelType) { - //Conversion function found so use the panel value - - conversionFunctionId = functionId; - typeValueMap[metamodelType]= metamodelValue; - } + if (!convertMetamodel){ + // Check for conversion functions with matching metamodels only + + if (targetMetamodelType==metamodelType) { + //Conversion function found so use the panel value + + conversionFunctionId = functionId; + typeValueMap[metamodelType]= metamodelValue; + } - } else { - // Check for conversion functions converting metamodel if possible - let metamodelConversionFunctionId = toolsManager.getConversionFunction( [metamodelType], targetMetamodelType ); - - if (metamodelConversionFunctionId != null){ + } else { + // Check for conversion functions converting metamodel if possible + let metamodelConversionFunctionId = toolsManager.getConversionFunction( [metamodelType], targetMetamodelType ); + + if (metamodelConversionFunctionId != null){ - conversionFunctionId = functionId; + conversionFunctionId = functionId; - //convert metamodel - let metamodelTypeValueMap = {}; - metamodelTypeValueMap[metamodelType]=metamodelValue; // The found conversion function is expected to have one parameter + //convert metamodel + let metamodelTypeValueMap = {}; + metamodelTypeValueMap[metamodelType]=metamodelValue; // The found conversion function is expected to have one parameter - let convertedValue = await functionRegistry_callConversion(metamodelConversionFunctionId, metamodelTypeValueMap, parameterName); + let convertedValue = await this.functionRegistry_callConversion(metamodelConversionFunctionId, metamodelTypeValueMap, parameterName); - typeValueMap[targetMetamodelType]= convertedValue.data; + typeValueMap[targetMetamodelType]= convertedValue.data; + } } } + + return conversionFunctionId; } - return conversionFunctionId; -} + /** + * Prepares the input parameters and requests the type translation for the given function id + * + * TODO: To be moved to the FunctionRegistry issue #40 + * + * @param {string} functionId the id of the action function + * @param {Object} typeValuesMap an object mapping action functions paramter types as keys to input values + * @param {string} parameterName name of the parameter + * @returns Promise for the translated data + * + */ + functionRegistry_callConversion( functionId, typeValuesMap, parameterName ){ + let conversionRequestData = {}; + let conversionFunction = toolsManager.getActionFunction(functionId); -/** - * Prepares the input parameters and requests the type translation for the given function id - * - * TODO: To be moved to the FunctionRegistry issue #40 - * - * @param {string} functionId the id of the action function - * @param {Object} typeValuesMap an object mapping action functions paramter types as keys to input values - * @param {string} parameterName name of the parameter - * @returns Promise for the translated data - * - */ -function functionRegistry_callConversion( functionId, typeValuesMap, parameterName ){ - let conversionRequestData = {}; - let conversionFunction = toolsManager.getActionFunction(functionId); - - // Populate parameters for the conversion request - for( const param of conversionFunction.getParameters() ){ - conversionRequestData[param.name] = typeValuesMap[param.type]; - } + // Populate parameters for the conversion request + for( const param of conversionFunction.getParameters() ){ + conversionRequestData[param.name] = typeValuesMap[param.type]; + } - return requestTranslation(conversionRequestData, conversionFunction, parameterName); -} + return this.requestTranslation(conversionRequestData, conversionFunction, parameterName); + } -/** - * - * @param {string} functionId url of the function to call - * @param {Object} parameters object containing the parameters request data - * @returns - */ -function functionRegistry_call(functionId, parameters ){ + /** + * + * @param {string} functionId url of the function to call + * @param {Object} parameters object containing the parameters request data + * @returns + */ + functionRegistry_call(functionId, parameters ){ - let actionFunction = toolsManager.getActionFunction(functionId); - let parametersJson = JSON.stringify(parameters); + let actionFunction = toolsManager.getActionFunction(functionId); + let parametersJson = JSON.stringify(parameters); - let requestPromise = jsonRequest(actionFunction.getPath(), parametersJson) + let requestPromise = jsonRequest(actionFunction.getPath(), parametersJson) - return requestPromise; -} + return requestPromise; + } -/** - * Requests the conversion function from the remote tool service - * - * @param {Object} parameters - * @param {ActionFunction} converstionFunction - * @param {String} parameterName name of the parameter - * @returns Promise for the translated data - */ -function requestTranslation(parameters, conversionFunction, parameterName){ - - let parametersJson = JSON.stringify(parameters); + /** + * Requests the conversion function from the remote tool service + * + * @param {Object} parameters + * @param {ActionFunction} converstionFunction + * @param {String} parameterName name of the parameter + * @returns Promise for the translated data + */ + requestTranslation(parameters, conversionFunction, parameterName){ + + let parametersJson = JSON.stringify(parameters); - return jsonRequestConversion(conversionFunction.getPath(), parametersJson, parameterName); -} + return jsonRequestConversion(conversionFunction.getPath(), parametersJson, parameterName); + } -/** - * TODO: Temporary wrapper called function to be renamed and to be moved to the FunctionRegistry issue #40 - */ -function functionRegistry_find(inputsParamTypes, outputParamType){ - return toolsManager.getConversionFunction( inputsParamTypes, outputParamType ); -} + /** + * TODO: Temporary wrapper called function to be renamed and to be moved to the FunctionRegistry issue #40 + */ + functionRegistry_find(inputsParamTypes, outputParamType){ + return toolsManager.getConversionFunction( inputsParamTypes, outputParamType ); + } -/** - * TODO: Temporary wrapper called function to be renamed and to be moved to the FunctionRegistry issue #40 - */ -function functionRegistry_findPartial(inputsParamTypes, outputParamType){ - return toolsManager.getPartiallyMatchingConversionFunctions( inputsParamTypes, outputParamType ); -} + /** + * TODO: Temporary wrapper called function to be renamed and to be moved to the FunctionRegistry issue #40 + */ + functionRegistry_findPartial(inputsParamTypes, outputParamType){ + return toolsManager.getPartiallyMatchingConversionFunctions( inputsParamTypes, outputParamType ); + } -/** - * Handle the response from the remote tool service - * - * @param {Object} action - * @param {Promise} requestPromise - */ -function handleResponseActionFunction(action, requestPromise){ - - requestPromise.then( (responseText) => { + /** + * Handle the response from the remote tool service + * + * @param {Object} action + * @param {Promise} requestPromise + */ + handleResponseActionFunction(action, requestPromise){ + + requestPromise.then( (responseText) => { - var response = JSON.parse(responseText); - const outputPanel = activityManager.findPanel( action.output.id, panels); + var response = JSON.parse(responseText); + const outputPanel = activityManager.findPanel( action.output.id, panels); - var outputConsole; - if (action.outputConsole != null){ - outputConsole = activityManager.findPanel(action.outputConsole.id, panels); - } else { - outputConsole = outputPanel; - } + var outputConsole; + if (action.outputConsole != null){ + outputConsole = activityManager.findPanel(action.outputConsole.id, panels); + } else { + outputConsole = outputPanel; + } - Metro.notify.killAll(); + Metro.notify.killAll(); - if ( Object.prototype.hasOwnProperty.call(response, "error")) { - outputConsole.setError(response.error); - } else { + if ( Object.prototype.hasOwnProperty.call(response, "error")) { + outputConsole.setError(response.error); + } else { - var responseDiagram = Object.keys(response).find( key => key.toLowerCase().includes("diagram") ); + var responseDiagram = Object.keys(response).find( key => key.toLowerCase().includes("diagram") ); - if (response.output) { - // Text - outputConsole.setValue(response.output) - } - - if (response.editorUrl) { - // Language workbench - longNotification("Building editor"); - checkEditorReady( response.editorStatusUrl, response.editorUrl, action.source.editorPanel, action.source.editorActivity, outputConsole); + if (response.output) { + // Text + outputConsole.setValue(response.output) + } + if (response.editorUrl) { + // Language workbench + this.longNotification("Building editor"); + this.checkEditorReady( response.editorStatusUrl, response.editorUrl, action.source.editorPanel, action.source.editorActivity, outputConsole); + - } else if (responseDiagram != undefined) { - - outputPanel.renderDiagram( response[responseDiagram] ); + } else if (responseDiagram != undefined) { - } else if (response.generatedFiles) { - // Multiple text files - outputPanel.setGeneratedFiles(response.generatedFiles); - - } else if (response.generatedText) { - // Generated file - - switch (action.outputType){ - case "code": - // Text - outputPanel.getEditor().setValue(response.generatedText.trim(), 1); - break; - - case "html": - // Html - outputPanel.setOutput(response.output); - var iframe = document.getElementById("htmlIframe"); - if (iframe == null) { - iframe = document.createElement("iframe"); - iframe.id = "htmlIframe" - iframe.style.height = "100%"; - iframe.style.width = "100%"; - document.getElementById(outputPanel.getId() + "Diagram").appendChild(iframe); - } - - iframe.srcdoc = response.generatedText; - break; - - case "puml": - case "dot": - // UML or Graph - var krokiEndpoint = ""; - if (action.outputType == "puml") krokiEndpoint = "plantuml"; - else krokiEndpoint = "graphviz/svg" - - var krokiXhr = new XMLHttpRequest(); - krokiXhr.open("POST", "https://kroki.io/" + krokiEndpoint, true); - krokiXhr.setRequestHeader("Accept", "image/svg+xml"); - krokiXhr.setRequestHeader("Content-Type", "text/plain"); - krokiXhr.onreadystatechange = function () { - if (krokiXhr.readyState === 4) { - if (krokiXhr.status === 200) { - - outputPanel.renderDiagram(krokiXhr.responseText); - - } + outputPanel.renderDiagram( response[responseDiagram] ); + + } else if (response.generatedFiles) { + // Multiple text files + outputPanel.setGeneratedFiles(response.generatedFiles); + + } else if (response.generatedText) { + // Generated file + + switch (action.outputType){ + case "code": + // Text + outputPanel.getEditor().setValue(response.generatedText.trim(), 1); + break; + + case "html": + // Html + outputPanel.setOutput(response.output); + var iframe = document.getElementById("htmlIframe"); + if (iframe == null) { + iframe = document.createElement("iframe"); + iframe.id = "htmlIframe" + iframe.style.height = "100%"; + iframe.style.width = "100%"; + document.getElementById(outputPanel.getId() + "Diagram").appendChild(iframe); } - }; - krokiXhr.send(response.generatedText); - break; + + iframe.srcdoc = response.generatedText; + break; + + case "puml": + case "dot": + // UML or Graph + var krokiEndpoint = ""; + if (action.outputType == "puml") krokiEndpoint = "plantuml"; + else krokiEndpoint = "graphviz/svg" + + var krokiXhr = new XMLHttpRequest(); + krokiXhr.open("POST", "https://kroki.io/" + krokiEndpoint, true); + krokiXhr.setRequestHeader("Accept", "image/svg+xml"); + krokiXhr.setRequestHeader("Content-Type", "text/plain"); + krokiXhr.onreadystatechange = function () { + if (krokiXhr.readyState === 4) { + if (krokiXhr.status === 200) { + + outputPanel.renderDiagram(krokiXhr.responseText); + + } + } + }; + krokiXhr.send(response.generatedText); + break; - default: - console.log("Unknown output type: " + action.outputType); + default: + console.log("Unknown output type: " + action.outputType); + } } + } + }); - } - }); + } -} + fit() { + + var splitter = document.getElementById("splitter"); + splitter.style.minHeight = window.innerHeight + "px"; + splitter.style.maxHeight = window.innerHeight + "px"; -function fit() { - - var splitter = document.getElementById("splitter"); - splitter.style.minHeight = window.innerHeight + "px"; - splitter.style.maxHeight = window.innerHeight + "px"; + panels.forEach(panel => panel.fit()); + preloader.hide(); + } - panels.forEach(panel => panel.fit()); - preloader.hide(); -} + runAction(source, sourceButton) { -function runAction(source, sourceButton) { + // Get the action + var action = activityManager.getActionForCurrentActivity(source, 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 - // Get the action - var action = activityManager.getActionForCurrentActivity(source, 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 + let parameterMap = new Map(); - // Create map containing panel values - let parameterMap = new Map(); + for (let paramName of Object.keys(action.parameters)){ - for (let paramName of Object.keys(action.parameters)){ + let param = {}; + const panelId = action.parameters[paramName].id; + + if (panelId) { + const panel = activityManager.findPanel(panelId, panels); + param.type = panel.getType(); + param.value = panel.getValue(); - let param = {}; - const panelId = action.parameters[paramName].id; - - if (panelId) { - const panel = activityManager.findPanel(panelId, panels); - param.type = panel.getType(); - param.value = panel.getValue(); + } else { + // No panel with ID so it use as the parameter value + const parameterValue = action.parameters[paramName]; + param.type = 'text'; + param.value = parameterValue; + } - } else { - // No panel with ID so it use as the parameter value - const parameterValue = action.parameters[paramName]; - param.type = 'text'; - param.value = parameterValue; + parameterMap.set(paramName, param); } - parameterMap.set(paramName, param); - } - - // Add the platform language parameter - let languageParam = {}; - languageParam.type = toolActionFunction.getParameterType("language"); - languageParam.value = action.source.ref.language; // Source panel language - parameterMap.set("language", languageParam); + // Add the platform language parameter + let languageParam = {}; + languageParam.type = toolActionFunction.getParameterType("language"); + languageParam.value = action.source.ref.language; // Source panel language + parameterMap.set("language", languageParam); - // TODO support output and language - //actionRequestData.outputType = outputType; - //actionRequestData.outputLanguage = outputLanguage; + // TODO support output and language + //actionRequestData.outputType = outputType; + //actionRequestData.outputLanguage = outputLanguage; - // Call backend conversion and service functions - let actionResultPromise = invokeActionFunction(buttonConfig.actionfunction, parameterMap) + // Call backend conversion and service functions + let actionResultPromise = this.invokeActionFunction(buttonConfig.actionfunction, parameterMap); - actionResultPromise.catch( () => { - errorNotification("There was an error translating action function parameter types."); - } ); + actionResultPromise.catch( () => { + this.errorNotification("There was an error translating action function parameter types."); + } ); - handleResponseActionFunction(action , actionResultPromise); - - longNotification("Executing program"); -} + this.handleResponseActionFunction(action , actionResultPromise); + + this.longNotification("Executing program"); + } -function togglePanelById(elementId) { - const panelElement = document.getElementById(elementId); - if (panelElement) { - const parentElement = panelElement.parentElement; - toggle(parentElement.id); + togglePanelById(elementId) { + const panelElement = document.getElementById(elementId); + if (panelElement) { + const parentElement = panelElement.parentElement; + this.toggle(parentElement.id); + } } -} -function notification(title, message, cls="light"){ - const crossIcon = "
" - Metro.notify.create(crossIcon + "" + title + "" + "
" + message + "
", null, {keepOpen: true, cls: cls, width: 300}); -} + notification(title, message, cls="light"){ + const crossIcon = "
" + Metro.notify.create(crossIcon + "" + title + "" + "
" + message + "
", null, {keepOpen: true, cls: cls, width: 300}); + } -function longNotification(title, cls="light") { - notification(title + "...", "This may take a few seconds to complete if the back end is not warmed up.", cls); -} + longNotification(title, cls="light") { + this.notification(title + "...", "This may take a few seconds to complete if the back end is not warmed up.", cls); + } -function successNotification(message, cls="light") { - notification("Success:", message, cls); -} + successNotification(message, cls="light") { + this.notification("Success:", message, cls); + } -function errorNotification(message) { - console.log("ERROR: " + message); - notification("Error:", message, "bg-red fg-white"); -} + errorNotification(message) { + console.log("ERROR: " + message); + this.notification("Error:", message, "bg-red fg-white"); + } -function toggle(elementId, onEmpty) { - var element = document.getElementById(elementId); - if (element == null) return; + toggle(elementId, onEmpty) { + var element = document.getElementById(elementId); + if (element == null) return; - if (getComputedStyle(element).display == "none") { - element.style.display = "flex"; - if (element.innerHTML.length == 0) { - onEmpty(); + if (getComputedStyle(element).display == "none") { + element.style.display = "flex"; + if (element.innerHTML.length == 0) { + onEmpty(); + } } + else { + element.style.display = "none"; + } + this.updateGutterVisibility(); } - else { - element.style.display = "none"; - } - updateGutterVisibility(); -} -function updateGutterVisibility() { - for (const gutter of Array.prototype.slice.call(document.getElementsByClassName("gutter"))) { + updateGutterVisibility() { + for (const gutter of Array.prototype.slice.call(document.getElementsByClassName("gutter"))) { - var visibleSiblings = Array.prototype.slice.call(gutter.parentNode.children).filter( - child => child != gutter && getComputedStyle(child).display != "none"); - - if (visibleSiblings.length > 1) { - var nextVisibleSibling = getNextVisibleSibling(gutter); - var previousVisibleSibling = getPreviousVisibleSibling(gutter); - if (nextVisibleSibling != null && nextVisibleSibling.className != "gutter" && previousVisibleSibling != null) { - gutter.style.display = "flex"; + var visibleSiblings = Array.prototype.slice.call(gutter.parentNode.children).filter( + child => child != gutter && getComputedStyle(child).display != "none"); + + if (visibleSiblings.length > 1) { + var nextVisibleSibling = this.getNextVisibleSibling(gutter); + var previousVisibleSibling = this.getPreviousVisibleSibling(gutter); + if (nextVisibleSibling != null && nextVisibleSibling.className != "gutter" && previousVisibleSibling != null) { + gutter.style.display = "flex"; + } + else { + gutter.style.display = "none"; + } } else { gutter.style.display = "none"; } } - else { - gutter.style.display = "none"; - } } -} -function getNextVisibleSibling(element) { - var sibling = element.nextElementSibling; - while (sibling != null) { - if (getComputedStyle(sibling).display != "none") return sibling; - sibling = sibling.nextElementSibling; + getNextVisibleSibling(element) { + var sibling = element.nextElementSibling; + while (sibling != null) { + if (getComputedStyle(sibling).display != "none") return sibling; + sibling = sibling.nextElementSibling; + } } -} -function getPreviousVisibleSibling(element) { - var sibling = element.previousElementSibling; - while (sibling != null) { - if (getComputedStyle(sibling).display != "none") return sibling; - sibling = sibling.previousElementSibling; + getPreviousVisibleSibling(element) { + var sibling = element.previousElementSibling; + while (sibling != null) { + if (getComputedStyle(sibling).display != "none") return sibling; + sibling = sibling.previousElementSibling; + } } -} -function savePanelContents(){ - - let panelsToSave = panels.filter (p => p.canSave()); + savePanelContents(){ + + let panelsToSave = panels.filter (p => p.canSave()); - let fileStorePromises = []; + let fileStorePromises = []; - // FIXME: This currently creates separate commits for each panel. We really would want one commit for all of them together... - for(const panel of panelsToSave){ - - let storePromise = panel.save(fileHandler); - - if (storePromise!=null) { + // FIXME: This currently creates separate commits for each panel. We really would want one commit for all of them together... + for(const panel of panelsToSave){ - storePromise.then( () => { - console.log("The contents of panel '" + panel.getId() + "' were saved successfully."); - }); + let storePromise = panel.save(fileHandler); + + if (storePromise!=null) { + + storePromise.then( () => { + console.log("The contents of panel '" + panel.getId() + "' were saved successfully."); + }); - fileStorePromises.push(storePromise); + fileStorePromises.push(storePromise); + } } + + Promise.all(fileStorePromises).then( () => { + this.successNotification("The activity panel contents have been saved."); + + }).catch(() => { + this.errorNotification("An error occurred while trying to save the panel contents."); + }); } - - Promise.all(fileStorePromises).then( () => { - successNotification("The activity panel contents have been saved."); - - }).catch(() => { - errorNotification("An error occurred while trying to save the panel contents."); - }); -} -/** - * Poll for editor to become available. - * @param {String} statusUrl - the url for cheking the status of the editor panel. - * @param {String} editorInstanceUrl - the editor instance's url. - * @param {String} editorPanelId - the id of the editor panel. - * @param {String} editorActivityId - TODO remove as this can be found using editorPanelId to save having to specify in config. - * @param {Panel} logPanel - the panel to log progress to. - */ -async function checkEditorReady(statusUrl, editorInstanceUrl, editorPanelId, editorActivityId, logPanel){ + /** + * Poll for editor to become available. + * @param {String} statusUrl - the url for cheking the status of the editor panel. + * @param {String} editorInstanceUrl - the editor instance's url. + * @param {String} editorPanelId - the id of the editor panel. + * @param {String} editorActivityId - TODO remove as this can be found using editorPanelId to save having to specify in config. + * @param {Panel} logPanel - the panel to log progress to. + */ + async checkEditorReady(statusUrl, editorInstanceUrl, editorPanelId, editorActivityId, logPanel){ - let response = await fetch(statusUrl); + let response = await fetch(statusUrl); - if (response.status == 200){ - const result = await response.json(); + if (response.status == 200){ + const result = await response.json(); - if (result.output){ - logPanel.setValue(result.output); - } - - if (result.error){ - // Unsuccessful - console.log("Editor failed start."); - sessionStorage.removeItem(editorPanelId); - activityManager.setActivityVisibility(editorActivityId, false); - Metro.notify.killAll(); - notification("Build Failed", result.error, "ribbed-lightAmber"); - - } else if (!result.editorReady){ - await new Promise(resolve => setTimeout(resolve, 2000)); - await checkEditorReady(statusUrl, editorInstanceUrl, editorPanelId, editorActivityId, logPanel); + if (result.output){ + logPanel.setValue(result.output); + } + + if (result.error){ + // Unsuccessful + console.log("Editor failed start."); + sessionStorage.removeItem(editorPanelId); + activityManager.setActivityVisibility(editorActivityId, false); + Metro.notify.killAll(); + this.notification("Build Failed", result.error, "ribbed-lightAmber"); + + } else if (!result.editorReady){ + await new Promise(resolve => setTimeout(resolve, 2000)); + await this.checkEditorReady(statusUrl, editorInstanceUrl, editorPanelId, editorActivityId, logPanel); - } else { - // Successful - console.log("Editor ready."); - sessionStorage.setItem( editorPanelId , editorInstanceUrl ); - activityManager.setActivityVisibility(editorActivityId, true); - Metro.notify.killAll(); - successNotification("Building complete."); - } + } else { + // Successful + console.log("Editor ready."); + sessionStorage.setItem( editorPanelId , editorInstanceUrl ); + activityManager.setActivityVisibility(editorActivityId, true); + Metro.notify.killAll(); + this.successNotification("Building complete."); + } - } else { - console.log("ERROR: The editor response could not be checked: " + statusUrl); - errorNotification("Failed to start the editor."); - } + } else { + console.log("ERROR: The editor response could not be checked: " + statusUrl); + this.errorNotification("Failed to start the editor."); + } + } } - // Some functions and variables are accessed - // by onclick - or similer - events - // We need to use window.x = x for this to work - window.fit = fit; - window.updateGutterVisibility = updateGutterVisibility; - window.runAction = runAction; - window.panels = panels; - window.savePanelContents = savePanelContents; - window.toggle = toggle; - window.togglePanelById = togglePanelById; - //window.renderDiagram = renderDiagram; - window.longNotification = longNotification; - window.getPanelTitle = getPanelTitle; +var platform = new EducationPlatform(); +platform.initialize(); + +// Some functions and variables are accessed +// by onclick - or similer - events +// We need to use window.x = x for this to work +window.fit = platform.fit.bind(platform); +window.updateGutterVisibility = platform.updateGutterVisibility.bind(platform); +window.runAction = platform.runAction.bind(platform); +window.panels = panels; +window.savePanelContents = platform.savePanelContents.bind(platform); +window.toggle = platform.toggle.bind(platform); +window.togglePanelById = platform.togglePanelById.bind(platform); +//window.renderDiagram = renderDiagram; +window.longNotification = platform.longNotification.bind(platform); +window.getPanelTitle = platform.getPanelTitle.bind(platform); \ No newline at end of file From 9a470774ad8ffc19434563ce0218cc01f52dda0b Mon Sep 17 00:00:00 2001 From: Will Barnett Date: Wed, 13 Mar 2024 09:06:47 +0000 Subject: [PATCH 2/6] Moved platform variable to class for testability --- platform/src/Playground.js | 109 ++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/platform/src/Playground.js b/platform/src/Playground.js index 5c90a3d..d3f8cfd 100644 --- a/platform/src/Playground.js +++ b/platform/src/Playground.js @@ -37,22 +37,29 @@ import { jsonRequest, jsonRequestConversion, ARRAY_ANY_ELEMENT, urlParamPrivateR const TOKEN_HANDLER_URL = PlaygroundUtility.getTokenServerAddress(); const COMMON_UTILITY_URL = window.location.href.replace(window.location.search,"") + "common/utility.json"; -var outputType = "text"; -var outputLanguage = "text"; -var activity; -var preloader = new Preloader(); -var panels = []; - -export var fileHandler = new FileHandler(TOKEN_HANDLER_URL); -export var activityManager; -export var toolsManager; - var urlParameters = new URLSearchParams(window.location.search); - class EducationPlatform { + outputType; + outputLanguage; + activity; + preloader; + panels; + + fileHandler; + activityManager; + toolsManager; + + constructor() { + this.outputType = "text"; + this.outputLanguage = "text"; + this.preloader = new Preloader(); + this.panels = []; + } initialize(){ + this.fileHandler = new FileHandler(TOKEN_HANDLER_URL); + /* * Setup the browser environment */ @@ -146,31 +153,31 @@ class EducationPlatform { if (errors.length==0){ // An activity configuration has been provided - toolsManager = new ToolsManager(); - activityManager = new ActivityManager( (toolsManager.getPanelDefinition).bind(toolsManager), fileHandler ); - activityManager.initializeActivities(); - errors = errors.concat(activityManager.getConfigErrors()); + this.toolsManager = new ToolsManager(); + this.activityManager = new ActivityManager( (this.toolsManager.getPanelDefinition).bind(this.toolsManager), this.fileHandler ); + this.activityManager.initializeActivities(); + errors = errors.concat(this.activityManager.getConfigErrors()); } if (errors.length==0){ // The activities have been validated - toolsManager.setToolsUrls( activityManager.getToolUrls().add(COMMON_UTILITY_URL) ); - errors = errors.concat(toolsManager.getConfigErrors()); + this.toolsManager.setToolsUrls( this.activityManager.getToolUrls().add(COMMON_UTILITY_URL) ); + errors = errors.concat(this.toolsManager.getConfigErrors()); } if (errors.length==0){ // The tools have been validated - activityManager.showActivitiesNavEntries(); + this.activityManager.showActivitiesNavEntries(); // Import tool grammar highlighting - const toolImports = toolsManager.getToolsGrammarImports(); + const toolImports = this.toolsManager.getToolsGrammarImports(); for(let ipt of toolImports) { ace.config.setModuleUrl(ipt.module, ipt.url); } // Add Tool styles for icons - for (let toolUrl of activityManager.getToolUrls()){ + for (let toolUrl of this.activityManager.getToolUrls()){ let toolBaseUrl = toolUrl.substring(0, toolUrl.lastIndexOf("/")); var link = document.createElement("link"); link.setAttribute("rel", 'stylesheet'); @@ -178,10 +185,10 @@ class EducationPlatform { document.head.appendChild(link); } - activity = activityManager.getSelectedActivity(); + this.activity = this.activityManager.getSelectedActivity(); // Validate the resolved activity - errors = errors.concat( ActivityValidator.validate(activity, toolsManager.tools) ); + errors = errors.concat( ActivityValidator.validate(this.activity, this.toolsManager.tools) ); } if (errors.length==0){ @@ -198,10 +205,10 @@ class EducationPlatform { const contentPanelName = "content-panel"; - panels.push(new BlankPanel(contentPanelName)); - panels[0].setVisible(true); + this.panels.push(new BlankPanel(contentPanelName)); + this.panels[0].setVisible(true); - new Layout().createFromPanels("navview-content", panels); + new Layout().createFromPanels("navview-content", this.panels); PlaygroundUtility.showMenu(); @@ -267,21 +274,21 @@ class EducationPlatform { initializePanels() { - if (activity.outputLanguage != null) { - outputLanguage = activity.outputLanguage; + if (this.activity.outputLanguage != null) { + this.outputLanguage = this.activity.outputLanguage; } // Create panels for the given activites - for ( let apanel of activity.panels ){ + for ( let apanel of this.activity.panels ){ var newPanel = this.createPanelForDefinitionId(apanel); if (newPanel != null){ - panels.push(newPanel); + this.panels.push(newPanel); } } - new Layout().createFrom2dArray("navview-content", panels, activity.layout.area); + new Layout().createFrom2dArray("navview-content", this.panels, this.activity.layout.area); PlaygroundUtility.showMenu(); @@ -294,7 +301,7 @@ class EducationPlatform { Metro.init(); - activityManager.openActiveActivitiesSubMenu(); + this.activityManager.openActiveActivitiesSubMenu(); this.fit(); } @@ -336,7 +343,7 @@ class EducationPlatform { break; } case "OutputPanel": { - newPanel = new OutputPanel(newPanelId, panelDefinition.language, outputType, outputLanguage); + newPanel = new OutputPanel(newPanelId, panelDefinition.language, this.outputType, this.outputLanguage); newPanel.initialize(); break; } @@ -425,7 +432,7 @@ class EducationPlatform { */ invokeActionFunction(functionId, parameterMap){ - let actionFunction = toolsManager.functionRegistry_resolve(functionId); + let actionFunction = this.toolsManager.functionRegistry_resolve(functionId); let parameterPromises = []; @@ -611,7 +618,7 @@ class EducationPlatform { while ( conversionFunctionId==null && functionsToCheck.length > 0){ let functionId = functionsToCheck.pop(); - let conversionFunction = toolsManager.getActionFunction(functionId); + let conversionFunction = this.toolsManager.getActionFunction(functionId); // Lookup the conversion function's metamodel type let metamodelName = conversionFunction.getInstanceOfParamName( conversionFunction.getParameters()[0].name ); @@ -634,7 +641,7 @@ class EducationPlatform { } else { // Check for conversion functions converting metamodel if possible - let metamodelConversionFunctionId = toolsManager.getConversionFunction( [metamodelType], targetMetamodelType ); + let metamodelConversionFunctionId = this.toolsManager.getConversionFunction( [metamodelType], targetMetamodelType ); if (metamodelConversionFunctionId != null){ @@ -667,7 +674,7 @@ class EducationPlatform { */ functionRegistry_callConversion( functionId, typeValuesMap, parameterName ){ let conversionRequestData = {}; - let conversionFunction = toolsManager.getActionFunction(functionId); + let conversionFunction = this.toolsManager.getActionFunction(functionId); // Populate parameters for the conversion request for( const param of conversionFunction.getParameters() ){ @@ -685,7 +692,7 @@ class EducationPlatform { */ functionRegistry_call(functionId, parameters ){ - let actionFunction = toolsManager.getActionFunction(functionId); + let actionFunction = this.toolsManager.getActionFunction(functionId); let parametersJson = JSON.stringify(parameters); let requestPromise = jsonRequest(actionFunction.getPath(), parametersJson) @@ -713,14 +720,14 @@ class EducationPlatform { * TODO: Temporary wrapper called function to be renamed and to be moved to the FunctionRegistry issue #40 */ functionRegistry_find(inputsParamTypes, outputParamType){ - return toolsManager.getConversionFunction( inputsParamTypes, outputParamType ); + return this.toolsManager.getConversionFunction( inputsParamTypes, outputParamType ); } /** * TODO: Temporary wrapper called function to be renamed and to be moved to the FunctionRegistry issue #40 */ functionRegistry_findPartial(inputsParamTypes, outputParamType){ - return toolsManager.getPartiallyMatchingConversionFunctions( inputsParamTypes, outputParamType ); + return this.toolsManager.getPartiallyMatchingConversionFunctions( inputsParamTypes, outputParamType ); } @@ -736,11 +743,11 @@ class EducationPlatform { requestPromise.then( (responseText) => { var response = JSON.parse(responseText); - const outputPanel = activityManager.findPanel( action.output.id, panels); + const outputPanel = this.activityManager.findPanel( action.output.id, this.panels); var outputConsole; if (action.outputConsole != null){ - outputConsole = activityManager.findPanel(action.outputConsole.id, panels); + outputConsole = this.activityManager.findPanel(action.outputConsole.id, this.panels); } else { outputConsole = outputPanel; } @@ -836,15 +843,15 @@ class EducationPlatform { splitter.style.minHeight = window.innerHeight + "px"; splitter.style.maxHeight = window.innerHeight + "px"; - panels.forEach(panel => panel.fit()); - preloader.hide(); + this.panels.forEach(panel => panel.fit()); + this.preloader.hide(); } runAction(source, sourceButton) { // Get the action - var action = activityManager.getActionForCurrentActivity(source, sourceButton); + var action = this.activityManager.getActionForCurrentActivity(source, sourceButton); let buttonConfig; if(action.source.buttons){ @@ -854,7 +861,7 @@ class EducationPlatform { //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 + const toolActionFunction = this.toolsManager.getActionFunction( buttonConfig.actionfunction ); // TODO tidy up by resolving tool references // Create map containing panel values let parameterMap = new Map(); @@ -865,7 +872,7 @@ class EducationPlatform { const panelId = action.parameters[paramName].id; if (panelId) { - const panel = activityManager.findPanel(panelId, panels); + const panel = this.activityManager.findPanel(panelId, this.panels); param.type = panel.getType(); param.value = panel.getValue(); @@ -987,14 +994,14 @@ class EducationPlatform { savePanelContents(){ - let panelsToSave = panels.filter (p => p.canSave()); + let panelsToSave = this.panels.filter (p => p.canSave()); let fileStorePromises = []; // FIXME: This currently creates separate commits for each panel. We really would want one commit for all of them together... for(const panel of panelsToSave){ - let storePromise = panel.save(fileHandler); + let storePromise = panel.save(this.fileHandler); if (storePromise!=null) { @@ -1037,7 +1044,7 @@ class EducationPlatform { // Unsuccessful console.log("Editor failed start."); sessionStorage.removeItem(editorPanelId); - activityManager.setActivityVisibility(editorActivityId, false); + this.activityManager.setActivityVisibility(editorActivityId, false); Metro.notify.killAll(); this.notification("Build Failed", result.error, "ribbed-lightAmber"); @@ -1049,7 +1056,7 @@ class EducationPlatform { // Successful console.log("Editor ready."); sessionStorage.setItem( editorPanelId , editorInstanceUrl ); - activityManager.setActivityVisibility(editorActivityId, true); + this.activityManager.setActivityVisibility(editorActivityId, true); Metro.notify.killAll(); this.successNotification("Building complete."); } @@ -1070,7 +1077,7 @@ platform.initialize(); window.fit = platform.fit.bind(platform); window.updateGutterVisibility = platform.updateGutterVisibility.bind(platform); window.runAction = platform.runAction.bind(platform); -window.panels = panels; +window.panels = platform.panels; window.savePanelContents = platform.savePanelContents.bind(platform); window.toggle = platform.toggle.bind(platform); window.togglePanelById = platform.togglePanelById.bind(platform); From b6452498742e45b4987f48e556752003cfa450eb Mon Sep 17 00:00:00 2001 From: Will Barnett Date: Wed, 13 Mar 2024 10:01:45 +0000 Subject: [PATCH 3/6] Moved platform start-up code to index.js, updated playground to make calls to window.location to utility function for unit testing, and passed in parameters used for initialisation instead of using global variables. --- platform/src/Playground.js | 49 +++++++++++++------------------------- platform/src/Utility.js | 20 +++++++++++++++- platform/src/index.js | 23 ++++++++++++++++++ platform/webpack.config.js | 2 +- 4 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 platform/src/index.js diff --git a/platform/src/Playground.js b/platform/src/Playground.js index d3f8cfd..cb1e352 100644 --- a/platform/src/Playground.js +++ b/platform/src/Playground.js @@ -32,12 +32,10 @@ import { Button } from './Button.js'; import { Preloader } from './Preloader.js'; import { Layout } from './Layout.js'; import { PlaygroundUtility } from './PlaygroundUtility.js'; -import { jsonRequest, jsonRequestConversion, ARRAY_ANY_ELEMENT, urlParamPrivateRepo } from './Utility.js'; +import { jsonRequest, jsonRequestConversion, ARRAY_ANY_ELEMENT, urlParamPrivateRepo, utility } from './Utility.js'; -const TOKEN_HANDLER_URL = PlaygroundUtility.getTokenServerAddress(); -const COMMON_UTILITY_URL = window.location.href.replace(window.location.search,"") + "common/utility.json"; -var urlParameters = new URLSearchParams(window.location.search); +const COMMON_UTILITY_URL = utility.getWindowLocationHref().replace( utility.getWindowLocationSearch(), "" ) + "common/utility.json"; class EducationPlatform { outputType; @@ -57,8 +55,8 @@ class EducationPlatform { this.panels = []; } - initialize(){ - this.fileHandler = new FileHandler(TOKEN_HANDLER_URL); + initialize( urlParameters, tokenHandlerUrl ){ + this.fileHandler = new FileHandler(tokenHandlerUrl); /* * Setup the browser environment @@ -76,7 +74,7 @@ class EducationPlatform { if (!urlParamPrivateRepo()){ // Public repo so no need to authenticate - this.initializeActivity(); + this.initializeActivity(urlParameters); } else { PlaygroundUtility.showLogin(); @@ -85,8 +83,8 @@ class EducationPlatform { document.getElementById("btnlogin").onclick= async () => { // Get github url - const urlRequest = { url: window.location.href }; - let authServerDetails= await jsonRequest(TOKEN_HANDLER_URL + "/mdenet-auth/login/url", + const urlRequest = { url: utility.getWindowLocationHref() }; + let authServerDetails= await jsonRequest(tokenHandlerUrl + "/mdenet-auth/login/url", JSON.stringify(urlRequest) ); @@ -94,7 +92,7 @@ class EducationPlatform { authServerDetails = JSON.parse(authServerDetails); // Authenticate redirect - window.location.href = authServerDetails.url; + utility.setWindowLocationHref(authServerDetails.url); } if (urlParameters.has("code") && urlParameters.has("state") ){ @@ -107,12 +105,12 @@ class EducationPlatform { tokenRequest.code = urlParameters.get("code"); //TODO loading box - let authDetails = jsonRequest(TOKEN_HANDLER_URL + "/mdenet-auth/login/token", + let authDetails = jsonRequest(tokenHandlerUrl + "/mdenet-auth/login/token", JSON.stringify(tokenRequest), true ); authDetails.then( () => { document.getElementById('save')?.classList.remove('hidden'); window.sessionStorage.setItem("isAuthenticated", true); - this.initializeActivity(); + this.initializeActivity(urlParameters); } ); } @@ -142,7 +140,7 @@ class EducationPlatform { - initializeActivity(){ + initializeActivity(urlParameters){ let errors = []; @@ -1031,9 +1029,9 @@ class EducationPlatform { */ async checkEditorReady(statusUrl, editorInstanceUrl, editorPanelId, editorActivityId, logPanel){ - let response = await fetch(statusUrl); + let response = await fetch(statusUrl); - if (response.status == 200){ + if (response.status == 200){ const result = await response.json(); if (result.output){ @@ -1061,26 +1059,11 @@ class EducationPlatform { this.successNotification("Building complete."); } - } else { + } else { console.log("ERROR: The editor response could not be checked: " + statusUrl); this.errorNotification("Failed to start the editor."); - } + } } } -var platform = new EducationPlatform(); -platform.initialize(); - -// Some functions and variables are accessed -// by onclick - or similer - events -// We need to use window.x = x for this to work -window.fit = platform.fit.bind(platform); -window.updateGutterVisibility = platform.updateGutterVisibility.bind(platform); -window.runAction = platform.runAction.bind(platform); -window.panels = platform.panels; -window.savePanelContents = platform.savePanelContents.bind(platform); -window.toggle = platform.toggle.bind(platform); -window.togglePanelById = platform.togglePanelById.bind(platform); -//window.renderDiagram = renderDiagram; -window.longNotification = platform.longNotification.bind(platform); -window.getPanelTitle = platform.getPanelTitle.bind(platform); \ No newline at end of file +export {EducationPlatform} \ No newline at end of file diff --git a/platform/src/Utility.js b/platform/src/Utility.js index 8dd8105..fa6f574 100644 --- a/platform/src/Utility.js +++ b/platform/src/Utility.js @@ -217,6 +217,22 @@ export function getWindowLocationSearch(){ return window.location.search; } +/** + * Gets the current url - window.location.href. + * @returns {string} the url + */ +export function getWindowLocationHref(){ + return window.location.href; +} + +/** + * Sets the current url - window.location.href. + * @newUrl {string} the url to navigate to + */ +export function setWindowLocationHref(newUrl){ + return window.location.href = newUrl; +} + /* ES6 Direct imports cannot be stubbed during unit test. Therefore, to enable unit testing access to functions has to be made using the utility object. */ export const utility = { @@ -227,5 +243,7 @@ export const utility = { urlParamPrivateRepo, isAuthenticated, parseConfigFile, - getWindowLocationSearch + getWindowLocationSearch, + getWindowLocationHref, + setWindowLocationHref } \ No newline at end of file diff --git a/platform/src/index.js b/platform/src/index.js new file mode 100644 index 0000000..a7a1aa1 --- /dev/null +++ b/platform/src/index.js @@ -0,0 +1,23 @@ + +/*global TOKEN_SERVER_URL -- is set by environment variable*/ +import { EducationPlatform } from "./Playground"; + +const TOKEN_HANDLER_URL = TOKEN_SERVER_URL || "http://127.0.0.1:10000"; +let urlParameters = new URLSearchParams(window.location.search); + +var platform = new EducationPlatform(); +platform.initialize(urlParameters, TOKEN_HANDLER_URL); + +// Some functions and variables are accessed +// by onclick - or similer - events +// We need to use window.x = x for this to work +window.fit = platform.fit.bind(platform); +window.updateGutterVisibility = platform.updateGutterVisibility.bind(platform); +window.runAction = platform.runAction.bind(platform); +window.panels = platform.panels; +window.savePanelContents = platform.savePanelContents.bind(platform); +window.toggle = platform.toggle.bind(platform); +window.togglePanelById = platform.togglePanelById.bind(platform); +//window.renderDiagram = renderDiagram; +window.longNotification = platform.longNotification.bind(platform); +window.getPanelTitle = platform.getPanelTitle.bind(platform); \ No newline at end of file diff --git a/platform/webpack.config.js b/platform/webpack.config.js index d390119..d821b07 100644 --- a/platform/webpack.config.js +++ b/platform/webpack.config.js @@ -5,7 +5,7 @@ module.exports = (env) => { return { mode: "development", devtool: "inline-source-map", - entry: './src/Playground.js', + entry: './src/index.js', resolve: { alias: { From 487d532adb711c6876dce7d29446c1d26e97bb7d Mon Sep 17 00:00:00 2001 From: Will Barnett Date: Wed, 13 Mar 2024 10:53:28 +0000 Subject: [PATCH 4/6] Renamed Playground.js to reflect its EducationPlatform class --- platform/src/{Playground.js => EducationPlatform.js} | 0 platform/src/index.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename platform/src/{Playground.js => EducationPlatform.js} (100%) diff --git a/platform/src/Playground.js b/platform/src/EducationPlatform.js similarity index 100% rename from platform/src/Playground.js rename to platform/src/EducationPlatform.js diff --git a/platform/src/index.js b/platform/src/index.js index a7a1aa1..6eea532 100644 --- a/platform/src/index.js +++ b/platform/src/index.js @@ -1,6 +1,6 @@ /*global TOKEN_SERVER_URL -- is set by environment variable*/ -import { EducationPlatform } from "./Playground"; +import { EducationPlatform } from "./EducationPlatform.js"; const TOKEN_HANDLER_URL = TOKEN_SERVER_URL || "http://127.0.0.1:10000"; let urlParameters = new URLSearchParams(window.location.search); From 004ed28db0a511d5e5602cac83ef8ca14ff3f480 Mon Sep 17 00:00:00 2001 From: Will Barnett Date: Wed, 13 Mar 2024 10:56:14 +0000 Subject: [PATCH 5/6] Unit test configuration added required webpack alias for xtext imports and fixed broken unit test. --- platform/karma.conf.js | 8 ++++++++ platform/test/spec/testToolManagerSpec.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/platform/karma.conf.js b/platform/karma.conf.js index c18e8d4..e21d46a 100644 --- a/platform/karma.conf.js +++ b/platform/karma.conf.js @@ -1,5 +1,6 @@ // Karma configuration // Generated on Fri Jan 27 2023 10:15:56 GMT+0000 (Greenwich Mean Time) +const path = require('path'); module.exports = function(config) { config.set({ @@ -45,6 +46,13 @@ module.exports = function(config) { mode: "development", devtool: "inline-source-map", + resolve: { + alias: { + 'xtext/xtext-ace$': path.resolve(__dirname, 'src/xtext/2.31.0/xtext-ace'), + 'ace/range$': 'ace-builds/src-noconflict/ace', + } + }, + module: { rules: [ { diff --git a/platform/test/spec/testToolManagerSpec.js b/platform/test/spec/testToolManagerSpec.js index e4d75be..9142148 100644 --- a/platform/test/spec/testToolManagerSpec.js +++ b/platform/test/spec/testToolManagerSpec.js @@ -44,7 +44,7 @@ describe("ToolManager", () => { // Check the expected results expect(tm.toolsUrls).toHaveSize(TOOL_URLS.length); - for (const i in TOOL_URLS){ + for (let i = 0; i < TOOL_URLS.length; i++ ){ expect(tm.toolsUrls[i].url).toEqual(TOOL_URLS[i]); } }) From 75016a474fb4937fa4fe6d3a9c4a346c3f931213 Mon Sep 17 00:00:00 2001 From: barnettwilliam <32113205+barnettwilliam@users.noreply.github.com> Date: Thu, 14 Mar 2024 08:39:12 +0000 Subject: [PATCH 6/6] Corrected typo suggestion from code review Co-authored-by: Steffen Zschaler --- platform/src/EducationPlatform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/src/EducationPlatform.js b/platform/src/EducationPlatform.js index cb1e352..add0bbf 100644 --- a/platform/src/EducationPlatform.js +++ b/platform/src/EducationPlatform.js @@ -1,4 +1,4 @@ -/*global $ -- jquery is exterally imported*/ +/*global $ -- jquery is externally imported*/ /*global FEEDBACK_SURVEY_URL -- is set by environment variable*/ /*global Metro -- Metro is externally imported*/