commit 91f583c51ce42bb7e66726f7e6b191a242e0a0bc Author: Adrien Jaguenet Date: Tue Jun 16 12:52:53 2026 +0200 Initial commit: visual programming framework with web components 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. diff --git a/index.html b/index.html new file mode 100644 index 0000000..cbf59d8 --- /dev/null +++ b/index.html @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + +

The anti-vibecoding framework

+ + + + + + + +
+ + + + +
+ + diff --git a/main.js b/main.js new file mode 100644 index 0000000..636112d --- /dev/null +++ b/main.js @@ -0,0 +1,203 @@ +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(); +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..e87f169 --- /dev/null +++ b/style.css @@ -0,0 +1,118 @@ +:root { + --grid-size: 25px; +} + +wc-start, wc-end, wc-output, wc-decision, wc-datum { + border: 0.1rem solid black; + padding: .5rem; + /* WARNING: margin adds up every event handle when dragging the element, any margin set should be set outside*/ + margin: 0; + position: absolute; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + user-select: none; +} + +wc-start, wc-end { + border-radius: 1rem; +} + +wc-output { + border-radius: 0; + background-color: #a0a0f0; + transform: skewX(-20deg); +} + +wc-output * { + transform: skewX(20deg); +} + +wc-decision { + background-color: #f0f0a0; + border-radius: 0; + aspect-ratio: 1; + transform: rotate(45deg); + display: block; +} + +wc-decision * { + transform: rotate(-45deg); +} + +wc-decision #hook-fore { + position: absolute; + top: 0; + left: 0; +} + +wc-decision #hook-out1 { + position: absolute; + top:100%; + left:100%; +} + +wc-decision #hook-out0 { + position: absolute; + top: 0%; + left: 100%; +} + +wc-decision #hook-test { + align-self: center; + position: absolute; + top: 100%; + left: 0%; +} + +wc-datum { + background-color: #2020f0; + color: white; +} + +div#draw-area { + height: 100vh; + width: 100vw; + background-size: var(--grid-size) var(--grid-size); + background-image: + linear-gradient(to right, grey 1px, transparent 1px), + linear-gradient(to bottom, grey 1px, transparent 1px); +} + +svg#connections { + z-index: -1; + background: transparent; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +path.flow { + fill: none; + stroke: red; + stroke-width: 5; +} + +button.hook { + display: block; + width: 2em; + height: 2em; + border-radius: 1em; + contain: layout; +} + +wc-end { + background-color: #a0ffa0; +} + +wc-start { + background-color: #ffa0a0; +} + +textarea { + white-space: pre-wrap; +} +