HTML defines template-based UI for flow chart nodes (start, end, output, decision, datum). CSS provides grid-based canvas and node styling. JS implements custom elements with drag-and-drop, linkable connections, and C code generation.
204 lines
5.0 KiB
JavaScript
204 lines
5.0 KiB
JavaScript
let $ = document.querySelector.bind(document);
|
|
let $$ = document.querySelectorAll.bind(document);
|
|
const svgNamespace = 'http://www.w3.org/2000/svg';
|
|
|
|
let linkageFirst = null
|
|
|
|
class WCBlock extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.addEventListener("mousedown", this.startDragging);
|
|
this.querySelector(".hook").addEventListener("click", this.linkEvent.bind(this));
|
|
/* List of connections to and from this */
|
|
this.wcForeFlow = Array.of();
|
|
this.wcNextFlow = Array.of();
|
|
}
|
|
|
|
startDragging(event) {
|
|
this.dragStartX = event.clientX;
|
|
this.dragStartY = event.clientY;
|
|
document.onmousemove = this.dragAround.bind(this);
|
|
document.onmouseup = this.stopDragging.bind(this);
|
|
}
|
|
|
|
dragAround(event) {
|
|
const {clientX, clientY} = event;
|
|
const gridSize = parseInt(window.getComputedStyle(this).getPropertyValue('--grid-size'));
|
|
let newTop = (this.offsetTop + event.clientY - this.dragStartY);
|
|
let newLeft = (this.offsetLeft + event.clientX - this.dragStartX);
|
|
newTop = Math.round(newTop / gridSize) * gridSize;
|
|
newLeft = Math.round(newLeft / gridSize) * gridSize;
|
|
this.dragStartX = Math.round(event.clientX/gridSize) * gridSize;
|
|
this.style.top = newTop +"px";
|
|
this.dragStartY = Math.round(event.clientY/gridSize) * gridSize;
|
|
this.style.left = newLeft +"px";
|
|
this.wcNextFlow.forEach(element => {
|
|
drawpath(element);
|
|
});
|
|
this.wcForeFlow.forEach(element => {
|
|
drawpath(element);
|
|
});
|
|
|
|
}
|
|
|
|
linkEvent() {
|
|
if (! linkageFirst) {
|
|
linkageFirst = this;
|
|
} else if (this != linkageFirst) {
|
|
var pathElement = document.createElementNS(svgNamespace, 'path');
|
|
pathElement.id = "flow-"+this.id+"2"+linkageFirst.id;
|
|
pathElement.toElement = this;
|
|
pathElement.fromElement = linkageFirst;
|
|
pathElement.classList.add("flow");
|
|
this.wcForeFlow.push(pathElement);
|
|
linkageFirst.wcNextFlow.push(pathElement);
|
|
$("#connections").appendChild(pathElement);
|
|
drawpath(pathElement);
|
|
linkageFirst = null;
|
|
}
|
|
}
|
|
|
|
generateNextCode() {
|
|
var code = ""
|
|
this.wcNextFlow.forEach((path) => {
|
|
code += path.toElement.generateCode();
|
|
});
|
|
return code;
|
|
}
|
|
|
|
stopDragging(event) {
|
|
document.onmousemove = null;
|
|
document.onmouseup = null;
|
|
}
|
|
}
|
|
|
|
class WCOutput extends WCBlock {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
generateCode() {
|
|
return `
|
|
printf("${this.querySelector("textarea").value}");
|
|
` + this.generateNextCode();
|
|
}
|
|
}
|
|
|
|
class WCStart extends WCBlock {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
generateCode() {
|
|
var code = `
|
|
// --- WiseCode® ---
|
|
int main() {
|
|
|
|
`;
|
|
return code + this.generateNextCode() + `
|
|
}
|
|
// --- END ---
|
|
`;
|
|
}
|
|
}
|
|
|
|
class WCEnd extends WCBlock {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
generateCode() {
|
|
return `
|
|
// WCEnd
|
|
exit(0);
|
|
`;
|
|
}
|
|
}
|
|
|
|
class WCProgram extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.counter = 0;
|
|
}
|
|
build() {
|
|
this.startNode = document.importNode($("#wc-start").content, true)
|
|
this.endNode = document.importNode($("#wc-end").content, true)
|
|
this.startNode.firstElementChild.id = "main-start";
|
|
this.endNode.firstElementChild.id = "main-end";
|
|
this.appendChild(this.startNode)
|
|
this.appendChild(this.endNode)
|
|
}
|
|
|
|
newDecisionNode() {
|
|
this.counter ++;
|
|
const decisionNode = document.importNode($("#wc-decision").content, true)
|
|
decisionNode.firstElementChild.id = "decision"+this.counter;
|
|
this.appendChild(decisionNode);
|
|
}
|
|
|
|
newOutputNode() {
|
|
const outputNode = document.importNode($("#wc-output").content, true)
|
|
outputNode.firstElementChild.id = "output"+this.counter;
|
|
this.appendChild(outputNode);
|
|
}
|
|
|
|
newDataNode() {
|
|
const dataNode = document.importNode($("#wc-datum").content, true)
|
|
dataNode.firstElementChild.id = "data"+this.counter;
|
|
this.appendChild(dataNode);
|
|
}
|
|
|
|
generateCode() {
|
|
$("#c-code").value = this.firstElementChild.generateCode();
|
|
}
|
|
}
|
|
|
|
class WCDecision extends WCBlock {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
generateCode() {
|
|
return `
|
|
if (condition) goto next; else goto alt;
|
|
`;
|
|
}
|
|
}
|
|
|
|
class WCDatum extends WCBlock {
|
|
constructor() {
|
|
super();
|
|
}
|
|
}
|
|
|
|
function drawpath(path) {
|
|
const rectFrom = path.fromElement.querySelector(".hook").getBoundingClientRect();
|
|
const rectTo = path.toElement.querySelector(".hook").getBoundingClientRect();
|
|
const [fromX, fromY] = [(rectFrom.left + rectFrom.right) / 2, (rectFrom.top + rectFrom.bottom) / 2];
|
|
const [toX, toY] = [(rectTo.left + rectTo.right) / 2, (rectTo.top + rectTo.bottom) / 2];
|
|
path.setAttribute("d",
|
|
`M ${fromX} ${fromY} \
|
|
C\
|
|
${toX} ${fromY} \
|
|
${fromX} ${toY} \
|
|
${toX} ${toY} \
|
|
`
|
|
);
|
|
}
|
|
|
|
customElements.define("wc-start", WCStart);
|
|
customElements.define("wc-end", WCEnd);
|
|
customElements.define("wc-program", WCProgram);
|
|
customElements.define("wc-output", WCOutput);
|
|
customElements.define("wc-decision", WCDecision);
|
|
customElements.define("wc-datum", WCDatum);
|
|
|
|
function generateCode() {
|
|
const text = $("#main-start").generateCode()
|
|
$("#c-code").innerHTML = text;
|
|
}
|
|
|
|
function main() {
|
|
$("#main-program").build();
|
|
}
|