Commit 3cd32e94 authored by Klaus Ramm's avatar Klaus Ramm

Second refactor step, transform to code works

parent f5890e33
......@@ -22,15 +22,22 @@
</div>
</section>
<section class="navbar-section">
<p>
<span>
&#169; 2019 Klaus Ramm (
</span>
<a href="license.html">MIT License</a>
<span>
)
</span>
</p>
<div class="columns">
<div class="column col-12 text-right">
<span>
&#169; 2019 Klaus Ramm
</span>
</div>
<div class="column col-12 text-right">
<span>
(
</span>
<a href="license.html">MIT License</a>
<span>
)
</span>
</div>
</div>
</section>
</div>
</div>
......@@ -41,10 +48,6 @@
<div id="optionButtons" class="columns elementButtonColumns">
<div class="column col-6 col-md-12 pt-2">
</div>
<!-- Option button row -->
<div class="column col-auto">
<button id="ToggleSourcecode" class="btn" onclick="alterSourcecodeDisplay(this.id);" value="Quellcode" />
</div>
<div class="column col-auto elementButtonColumns">
<button class="btn" onclick="document.getElementById('IEModal').classList.add('active');">
......@@ -53,23 +56,7 @@
</div>
</div>
<div id="editorDisplay" class="columns">
<!-- Transform to code -->
<div class="column col-3 col-md-12 col-ml-auto">
<div id='SourcecodeDisplay' class="columns d-hide">
<div class="column col-10 col-mx-auto">
<div>
<span>Übersetzen in:</span>
</div>
<div class="form-group">
<select id="SourcecodeSelect" class="form-select" onchange="startTransforming(this.value);">
<option value="--">--</option>
</select>
</div>
</div>
<div id="Sourcecode" class="column col-12 lowerPadding">
</div>
</div>
</div>
<!-- Container for views -->
</div>
</div>
<!-- Popup modal for import and export -->
......
......@@ -12,15 +12,7 @@
import { model } from './model/main';
import { Presenter } from './presenter/main';
import { Structogram } from './views/structogram';
// check the web storage for old data
if (typeof(Storage) !== "undefined") {
if ('tree' in localStorage) {
model.setTree(JSON.parse(localStorage.tree));
// TODO: model.displaySourcecode = JSON.parse(localStorage.displaySourcecode);
}
}
import { CodeView } from './views/code';
window.onload = function() {
......@@ -32,5 +24,19 @@ window.onload = function() {
// create our view objects
const structogram = new Structogram(presenter, document.getElementById("editorDisplay"));
presenter.addView(structogram);
const code = new CodeView(presenter, document.getElementById("editorDisplay"));
presenter.addView(code);
// reset button must be last defined
let resetButtonDiv = document.createElement('div');
resetButtonDiv.classList.add('column', 'col-mr-auto');
let resetButton = document.createElement('button');
resetButton.classList.add('btn', 'float-right');
resetButton.addEventListener('click', () => presenter.resetModel());
resetButton.appendChild(document.createTextNode('Reset'));
resetButtonDiv.appendChild(resetButton);
document.getElementById('optionButtons').appendChild(resetButtonDiv);
presenter.init();
}
......@@ -7,6 +7,13 @@ class Model {
}) {
this.tree = tree;
this.presenter = null;
// check the web storage for old data
if (typeof(Storage) !== "undefined") {
if ('tree' in localStorage) {
this.setTree(JSON.parse(localStorage.tree));
}
}
}
......
......@@ -7,6 +7,7 @@ export class Presenter {
this.views = [];
this.moveId = null;
this.nextInsertElement = null;
this.displaySourcecode = false;
}
......@@ -35,6 +36,16 @@ export class Presenter {
}
setSourcecodeDisplay(state) {
this.displaySourcecode = state;
}
getSourcecodeDisplay() {
return this.displaySourcecode
}
/**
* Update the model stored in the browser store
*/
......@@ -43,7 +54,7 @@ export class Presenter {
if (typeof(Storage) !== "undefined") {
// update the model as stringified JSON data
localStorage.tree = JSON.stringify(this.model.getTree());
//localStorage.displaySourcecode = JSON.stringify(model.displaySourcecode);
localStorage.displaySourcecode = this.displaySourcecode;
}
}
......@@ -70,6 +81,37 @@ export class Presenter {
}
/**
* Start the tranformation of the model tree to sourcecode
*
* @param lang programming language to which the translation happens
*/
startTransforming(event) {
for (const view of this.views) {
view.setLang(event.target.value);
}
this.renderAllViews();
}
/**
* Toggle the rendering of sourcecode
*
* @param buttonId id of the sourcecode display button
*/
alterSourcecodeDisplay(buttonId) {
if (this.displaySourcecode) {
this.displaySourcecode = false;
} else {
this.displaySourcecode = true;
}
this.updateBrowserStore();
for (const view of this.views) {
view.displaySourcecode(buttonId.target.id);
}
}
/**
* Prepare for inserting an element
*
......
export class CodeView {
constructor(presenter, domRoot) {
this.presenter = presenter;
this.domRoot = domRoot;
this.lang = '--';
this.translationMap = {
'Python': {'untranslatable': ['FootLoopNode', 'CaseNode'],
'InputNode': {'pre': '',
'post': " = input(\"Eingabe\")\n"
},
'OutputNode': {'pre': "print(",
'post': ")\n"
},
'TaskNode': {'pre': '',
'post': '\n'
},
'BranchNode': {'pre': "if ",
'post': ":\n",
'between': "else:\n"
},
'CountLoopNode': {'pre': "for ",
'post': ":\n"
},
'HeadLoopNode': {'pre': "while ",
'post': ":\n"
},
'leftBracket': '',
'rightBracket': ''
},
'PHP': {'untranslatable': [],
'InputNode': {'pre': '',
'post': " = readline(\"Eingabe\");\n"
},
'OutputNode': {'pre': "echo ",
'post': ";\n"
},
'TaskNode': {'pre': '',
'post': ';\n'
},
'BranchNode': {'pre': "if (",
'post': ")\n",
'between': "} else {\n"
},
'CountLoopNode': {'pre': "for (",
'post': ")\n"
},
'HeadLoopNode': {'pre': "while (",
'post': ")\n"
},
'FootLoopNode': {'prepre': "do\n",
'pre': "while (",
'post': ");\n"
},
'CaseNode': {'pre': "switch(",
'post': ")\n",
},
'InsertCase': {'preNormal': "case ",
'preDefault': "default",
'post': ":\n",
'postpost': "break;\n"
},
'leftBracket': '{',
'rightBracket': '}'
},
'Java': {'untranslatable': [],
'InputNode': {'pre': '',
'post': " = System.console().readLine();\n"
},
'OutputNode': {'pre': "System.out.println(",
'post': ");\n"
},
'TaskNode': {'pre': '',
'post': ';\n'
},
'BranchNode': {'pre': "if (",
'post': ")\n",
'between': "} else {\n"
},
'CountLoopNode': {'pre': "for (",
'post': ")\n"
},
'HeadLoopNode': {'pre': "while (",
'post': ")\n"
},
'FootLoopNode': {'prepre': "do\n",
'pre': "while (",
'post': ");\n"
},
'CaseNode': {'pre': "switch(",
'post': ")\n",
},
'InsertCase': {'preNormal': "case ",
'preDefault': "default",
'post': ":\n",
'postpost': "break;\n"
},
'leftBracket': '{',
'rightBracket': '}'
}
}
this.preRender();
}
preRender() {
let sourcecode = document.createElement('div');
sourcecode.classList.add('column', 'col-3', 'col-md-12', 'col-ml-auto');
let sourcecodeDisplay = document.createElement('div');
sourcecodeDisplay.classList.add('columns', 'd-hide');
sourcecodeDisplay.id = 'SourcecodeDisplay';
let sourcecodeHeader = document.createElement('div');
sourcecodeHeader.classList.add('column', 'col-10', 'col-mx-auto');
let sourcecodeTitle = document.createElement('span');
sourcecodeTitle.appendChild(document.createTextNode('Übersetzen in:'));
let sourcecodeForm = document.createElement('div');
sourcecodeForm.classList.add('form-group');
let sourcecodeSelect = document.createElement('select');
sourcecodeSelect.classList.add('form-select');
sourcecodeSelect.id = 'SourcecodeSelect';
sourcecodeSelect.addEventListener('change', (event) => this.presenter.startTransforming(event));
let sourcecodeOption = document.createElement('option');
sourcecodeOption.value = '--';
sourcecodeOption.appendChild(document.createTextNode('--'));
sourcecodeSelect.appendChild(sourcecodeOption);
for (const lang in this.translationMap) {
const langDiv = document.createElement('option');
langDiv.value = lang;
langDiv.appendChild(document.createTextNode(lang));
sourcecodeSelect.appendChild(langDiv);
}
sourcecodeForm.appendChild(sourcecodeSelect);
sourcecodeHeader.appendChild(sourcecodeTitle);
sourcecodeHeader.appendChild(sourcecodeForm);
let sourcecodeWorkingArea = document.createElement('div');
sourcecodeWorkingArea.classList.add('column', 'col-12', 'lowerPadding');
sourcecodeWorkingArea.id = 'Sourcecode';
sourcecodeDisplay.appendChild(sourcecodeHeader);
sourcecodeDisplay.appendChild(sourcecodeWorkingArea);
sourcecode.appendChild(sourcecodeDisplay);
this.domRoot.appendChild(sourcecode);
this.domRoot = document.getElementById('Sourcecode');
let options = document.createElement('div');
options.classList.add('column', 'col-auto');
let optionButton = document.createElement('button');
optionButton.classList.add('btn');
optionButton.id = 'ToggleSourcecode';
optionButton.addEventListener('click', (event) => this.presenter.alterSourcecodeDisplay(event));
optionButton.appendChild(document.createTextNode('Quellcode einblenden'));
options.appendChild(optionButton);
document.getElementById('optionButtons').appendChild(options);
if (typeof(Storage) !== "undefined") {
if ('displaySourcecode' in localStorage && 'lang' in localStorage) {
const possibleLang = localStorage.lang;
if (possibleLang in this.translationMap) {
this.lang = possibleLang;
sourcecodeSelect.value = this.lang;
this.presenter.setSourcecodeDisplay(JSON.parse(localStorage.displaySourcecode));
this.displaySourcecode('ToggleSourcecode');
}
}
}
}
render(model) {
// remove content
while (this.domRoot.hasChildNodes()) {
this.domRoot.removeChild(this.domRoot.lastChild);
}
// only translate, if some language is selected
if (this.lang != "--") {
// check if translation is possible with current tree
let isTranslatable = false;
for (const nodeType of this.translationMap[this.lang].untranslatable) {
isTranslatable = isTranslatable || this.checkForUntranslatable(model, nodeType);
}
// create container for the spans
let preBlock = document.createElement('pre');
preBlock.classList.add('code');
// set the language attribute
preBlock.setAttribute('data-lang', this.lang);
let codeBlock = document.createElement('code');
// start appending the translated elements
if (!isTranslatable) {
let content = this.transformToCode(model, 0, this.lang);
content.forEach(function(i) {
codeBlock.appendChild(i);
});
} else {
codeBlock.appendChild(document.createTextNode("Das Struktogramm enthält Elemente, \nwelche in der Programmiersprache nicht \nzur Verfügung stehen."));
}
preBlock.appendChild(codeBlock);
this.domRoot.appendChild(preBlock);
}
}
setLang(lang) {
if (typeof(Storage) !== "undefined") {
localStorage.lang = lang;
}
this.lang = lang;
}
resetButtons() {}
/**
* Add indentations to a text element
*
* @param indentLevel number of how many levels deep the node is
* @return string multiple indentations, times the level
*/
addIndentations(indentLevel) {
let text = "";
let defaultIndent = " ";
for (let i = 0; i < indentLevel; i++) {
text = text + defaultIndent;
}
return text
}
/**
* Create a span with text and a highlight class
*
* @param text text to be displayed
* @return span complete HTML structure with text and class
*/
createHighlightedSpan(text) {
let span = document.createElement('span');
span.classList.add('text-primary');
span.appendChild(document.createTextNode(text));
return span
}
/**
* Check recursively elements if they match a given type
*
* @param subTree part of the tree with all children of current element
* @param nodeType type of the translation map element to be checked against
* @return boolean true, if the current element type is the given type
*/
checkForUntranslatable(subTree, nodeType) {
// end recursion
if (subTree.type == 'Placeholder' || subTree.type == 'InsertNode' && subTree.followElement === null) {
return false
} else {
// compare the types
if (subTree.type == nodeType) {
return true
} else {
// different recursive steps, depending on child structure
switch (subTree.type) {
case 'InsertNode':
case 'InputNode':
case 'OutputNode':
case 'TaskNode':
return false || this.checkForUntranslatable(subTree.followElement, nodeType);
break;
case 'BranchNode':
return false || this.checkForUntranslatable(subTree.trueChild, nodeType) || this.checkForUntranslatable(subTree.falseChild, nodeType) || this.checkForUntranslatable(subTree.followElement, nodeType)
break;
case 'CountLoopNode':
case 'HeadLoopNode':
case 'FootLoopNode':
return false || this.checkForUntranslatable(subTree.child, nodeType) || this.checkForUntranslatable(subTree.followElement, nodeType)
break;
}
}
}
}
/**
* Tranform an element to sourcecode with a translation mapping
*
* @param subTree part of the tree with all children of current element
* @param indentLevel number of indentation levels
* @param lang current sourcecode language
* @return [] array of span elements with the tranformed element
*/
transformToCode(subTree, indentLevel, lang) {
// end recursion
if (subTree.type == 'Placeholder' || subTree.type == 'InsertNode' && subTree.followElement === null) {
return []
} else {
// create the span
let elemSpan = document.createElement('span');
// add eventlisteners for mouseover and click events
// highlight equivalent element in struktogramm on mouseover
elemSpan.addEventListener('mouseover', function() {
let node = document.getElementById(subTree.id);
node.parentElement.parentElement.classList.add('bg-primary');
});
elemSpan.addEventListener('mouseout', function() {
let node = document.getElementById(subTree.id);
node.parentElement.parentElement.classList.remove('bg-primary');
});
// switch to edit mode of equivalent element in the struktogramm
let text = this.createHighlightedSpan(subTree.text);
text.classList.add('c-hand');
text.addEventListener('click', () => this.presenter.switchEditState(subTree.id));
switch (subTree.type) {
case 'InsertNode':
return this.transformToCode(subTree.followElement, indentLevel, lang)
break;
case 'InputNode':
elemSpan.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].InputNode.pre));
elemSpan.appendChild(text);
elemSpan.appendChild(document.createTextNode(this.translationMap[lang].InputNode.post));
return [elemSpan].concat(this.transformToCode(subTree.followElement, indentLevel, lang));
break;
case 'OutputNode':
elemSpan.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].OutputNode.pre));
elemSpan.appendChild(text);
elemSpan.appendChild(document.createTextNode(this.translationMap[lang].OutputNode.post));
return [elemSpan].concat(this.transformToCode(subTree.followElement, indentLevel, lang));
break;
case 'TaskNode':
elemSpan.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].TaskNode.pre));
elemSpan.appendChild(text);
elemSpan.appendChild(document.createTextNode(this.translationMap[lang].TaskNode.post));
return [elemSpan].concat(this.transformToCode(subTree.followElement, indentLevel, lang));
break;
case 'BranchNode':
{
let branchHeaderPre = this.addIndentations(indentLevel) + this.translationMap[lang].BranchNode.pre;
elemSpan.appendChild(document.createTextNode(branchHeaderPre));
elemSpan.appendChild(text);
elemSpan.appendChild(document.createTextNode(this.translationMap[lang].BranchNode.post));
let branch = [elemSpan];
if (this.translationMap[lang].leftBracket != '') {
let leftBracket = document.createElement('span');
leftBracket.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].leftBracket + "\n"));
branch.push(leftBracket);
}
let trueContent = this.transformToCode(subTree.trueChild, indentLevel + 1, lang);
let falseContent = this.transformToCode(subTree.falseChild, indentLevel + 1, lang);
branch = branch.concat(trueContent);
if (falseContent.length > 0) {
let between = document.createElement('span');
between.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].BranchNode.between));
branch.push(between);
}
branch = branch.concat(falseContent);
if (this.translationMap[lang].rightBracket != '') {
let rightBracket = document.createElement('span');
rightBracket.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].rightBracket + "\n"));
branch.push(rightBracket);
}
return branch.concat(this.transformToCode(subTree.followElement, indentLevel, lang));
}
break;
case 'CountLoopNode':
{
let loopHeaderPre = this.addIndentations(indentLevel) + this.translationMap[lang].CountLoopNode.pre;
elemSpan.appendChild(document.createTextNode(loopHeaderPre));
elemSpan.appendChild(text);
elemSpan.appendChild(document.createTextNode(this.translationMap[lang].CountLoopNode.post));
let loop = [elemSpan];
if (this.translationMap[lang].leftBracket != '') {
let leftBracket = document.createElement('span');
leftBracket.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].leftBracket + "\n"));
loop.push(leftBracket);
}
loop = loop.concat(this.transformToCode(subTree.child, indentLevel + 1, lang));
if (this.translationMap[lang].rightBracket != '') {
let rightBracket = document.createElement('span');
rightBracket.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].rightBracket + "\n"));
loop.push(rightBracket);
}
return loop.concat(this.transformToCode(subTree.followElement, indentLevel, lang));
}
break;
case 'HeadLoopNode':
{
let loopHeaderPre = this.addIndentations(indentLevel) + this.translationMap[lang].HeadLoopNode.pre;
elemSpan.appendChild(document.createTextNode(loopHeaderPre));
elemSpan.appendChild(text);
elemSpan.appendChild(document.createTextNode(this.translationMap[lang].HeadLoopNode.post));
let loop = [elemSpan];
if (this.translationMap[lang].leftBracket != '') {
let leftBracket = document.createElement('span');
leftBracket.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].leftBracket + "\n"));
loop.push(leftBracket);
}
loop = loop.concat(this.transformToCode(subTree.child, indentLevel + 1, lang));
if (this.translationMap[lang].rightBracket != '') {
let rightBracket = document.createElement('span');
rightBracket.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].rightBracket + "\n"));
loop.push(rightBracket);
}
return loop.concat(this.transformToCode(subTree.followElement, indentLevel, lang));
}
break;
case 'FootLoopNode':
{
let loopContent = this.addIndentations(indentLevel) + this.translationMap[lang].FootLoopNode.prepre;
elemSpan.appendChild(document.createTextNode(loopContent));
let loop = [elemSpan];
if (this.translationMap[lang].leftBracket != '') {
let leftBracket = document.createElement('span');
leftBracket.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].leftBracket + "\n"));
loop.push(leftBracket);
}
let child = this.transformToCode(subTree.child, indentLevel + 1, lang);
loop = loop.concat(child);
if (this.translationMap[lang].rightBracket != '') {
let rightBracket = document.createElement('span');
rightBracket.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].rightBracket + "\n"));
loop.push(rightBracket);
}
let subContent = document.createElement('span');
subContent.appendChild(document.createTextNode(this.addIndentations(indentLevel) + this.translationMap[lang].FootLoopNode.pre));
subContent.appendChild(text);
subContent.appendChild(document.createTextNode(this.translationMap[lang].FootLoopNode.post));
subContent.addEventListener('mouseover', function() {
let node = document.getElementById(subTree.id);
node.parentElement.parentElement.classList.add('bg-primary');