Skip to content

[WIP] Add UX improvements to expression tree generator#3

Merged
psaegert merged 2 commits into
masterfrom
copilot/add-ux-improvements-export-enter-animation
Apr 21, 2026
Merged

[WIP] Add UX improvements to expression tree generator#3
psaegert merged 2 commits into
masterfrom
copilot/add-ux-improvements-export-enter-animation

Conversation

Copilot AI commented Apr 21, 2026

Copy link
Copy Markdown
  • Add Export PNG UI controls (button + background mode selector) in /home/runner/work/expression-tree-gen/expression-tree-gen/index.html
  • Add Animate toggle control in /home/runner/work/expression-tree-gen/expression-tree-gen/index.html
  • Wire canvas interactions in /home/runner/work/expression-tree-gen/expression-tree-gen/canvas.js (export logic, Enter key triggers generate, animate flag passed to draw)
  • Update /home/runner/work/expression-tree-gen/expression-tree-gen/tree.js to honor animate flag for edge drawing and recursive rendering
  • Add minimal related styling in /home/runner/work/expression-tree-gen/expression-tree-gen/app.css (button row wrapping + label styles)
  • Manually verify behavior and capture a UI screenshot
Original prompt

Goal

Add three UX improvements to the expression tree generator:

  1. Export button — export the current canvas as a PNG with either a white or transparent background. Tree nodes must always be filled white regardless of the chosen background.
  2. Enter key — pressing Enter in the #expression-input field triggers the Generate button.
  3. Animation toggle — a checkbox/switch to enable or disable the edge-drawing animation. When disabled, the tree renders instantly.

Files involved

  • index.html — add UI controls (export button, bg-mode selector, animation toggle)
  • canvas.js — wire up event handlers, implement export, handle Enter key, respect animation setting
  • tree.js — make drawEdgeAnimated / drawTree honor an "animate" flag
  • app.css — minor styling for the new controls if needed

Required changes

1. Export button (index.html, canvas.js)

Add an Export PNG button to the .buttons container in index.html. Next to it, add a control for choosing the background mode — a small <select> or a pair of radio buttons with options White (default) and Transparent.

Example markup:

<button id="export-tree">Export PNG</button>
<label class="bg-mode-label" style="color:#e0e0e0;">
  Background:
  <select id="export-bg-mode">
    <option value="white" selected>White</option>
    <option value="transparent">Transparent</option>
  </select>
</label>

Implementation in canvas.js:

  • On click, read the selected mode from #export-bg-mode.
  • If white: create an offscreen canvas the same size as the visible canvas, fill it with #ffffff, then drawImage(canvas, 0, 0) onto it, and export that via toDataURL('image/png').
  • If transparent: export the visible canvas directly with toDataURL('image/png'). The current clearCanvas() uses clearRect, which leaves the canvas transparent, so this will work as long as the CSS page background (the #canvas-container grid pattern) does not bleed in — it won't, because toDataURL only captures the canvas pixel data.
  • Trigger a download by creating an <a> element with href = dataURL and download = 'expression-tree.png', then .click() it.

Important: Nodes already fill with white (tree.js line 101: context.fillStyle = 'white'; context.fill()). No change needed to node fill — it will remain white even when the page background is transparent. This satisfies the "nodes always filled white" requirement automatically.

Edge case: if no tree has been generated yet (canvas empty), the export will produce an empty/transparent PNG. That's acceptable, but consider disabling the Export button until a tree exists, or just letting it produce an empty image. Simpler: always allow and don't worry about it.

2. Enter key to generate (canvas.js)

Add a keydown listener on #expression-input:

document.getElementById('expression-input').addEventListener('keydown', (e) => {
    if (e.key === 'Enter') {
        e.preventDefault()
        document.getElementById('generate-tree').click()
    }
})

3. Animation toggle (index.html, canvas.js, tree.js)

Add a checkbox next to the buttons:

<label class="animate-toggle-label" style="color:#e0e0e0;">
  <input type="checkbox" id="animate-toggle" checked />
  Animate
</label>

Wire it through:

  • In canvas.js, before calling drawTree(root, c), read document.getElementById('animate-toggle').checked and pass it into drawTree as a new parameter (default true for backward compatibility).
  • In tree.js, update drawTree(root, context, animate = true) to forward animate to child calls and into drawEdge.
  • Update Node.drawEdge(context, childNode, resolve, animate) to branch:
    • If animate is true, call drawEdgeAnimated(...) as today.
    • If animate is false, draw the edge synchronously with a single beginPath/moveTo/lineTo/stroke, then call resolve() immediately (still keep the async contract so the rest of drawTree works unchanged).

Alternatively, bypass the promise entirely when not animating: in drawTree, if !animate, use a synchronous traversal that draws edges + nodes without await. Either approach is fine — prefer the simpler one (keeping the promise flow but resolving immediately).

Styling notes (app.css)

The .buttons flex container already lays items in a row with 15px margin-right per child. The new controls should inherit that layout. You may need to tweak .buttons to flex-wrap: wrap so the row doesn't overflow on narrow screens, and add a bit of vertical margin for wrapped rows:

.buttons { flex-wrap: wrap; row-gap: 10px; }

Label text color should match the existing #e0e0e0 used elsewhere in the contact section for readability on the dark background.

Acceptance criteria

  • Clicking Export PNG with mode White downloads a PNG whose background is solid white and whose nodes are filled white with visible black borders and text.
  • Clickin...

This pull request was created from Copilot chat.

@psaegert psaegert marked this pull request as ready for review April 21, 2026 09:49
Copilot AI review requested due to automatic review settings April 21, 2026 09:49
@psaegert psaegert merged commit 6e08d0f into master Apr 21, 2026
Copilot stopped work on behalf of psaegert due to an error April 21, 2026 09:49
Copilot AI requested a review from psaegert April 21, 2026 09:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds UX controls to the expression tree generator for exporting the canvas, keyboard-driven generation, and enabling/disabling render animations.

Changes:

  • Added Export PNG button with background mode selector (white vs transparent).
  • Added Enter-key handler to trigger tree generation from the expression input.
  • Added animation toggle and propagated an animate flag through rendering; updated styling for new controls.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
index.html Adds Export PNG controls and Animate toggle to the UI.
canvas.js Wires UI events (export, Enter-to-generate) and passes animate flag into rendering.
tree.js Updates edge drawing and recursive rendering to honor an animate flag.
app.css Adds wrapping/gap styling for the expanded button/control row and label styling.
Comments suppressed due to low confidence (1)

tree.js:83

  • Node.drawEdge now takes an animate flag but has no default, and the implementation treats a missing value as falsy (non-animated). To preserve the previous default behavior and make the method safer for any future direct callers, consider defaulting the parameter to true (or explicitly coercing undefined to true).
    this.drawEdge = function (context, childNode, resolve, animate) {
        const radius = this.getRadius(context)
        const childRadius = childNode.getRadius(context)
        context.strokeStyle = 'gray';
        const dx = childNode.x - this.x

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread canvas.js
Comment on lines +23 to +30
var exportCanvas = document.createElement('canvas')
var exportContext = exportCanvas.getContext('2d')
exportCanvas.width = canvas.width
exportCanvas.height = canvas.height
exportContext.fillStyle = '#ffffff'
exportContext.fillRect(0, 0, exportCanvas.width, exportCanvas.height)
exportContext.drawImage(canvas, 0, 0)
dataURL = exportCanvas.toDataURL('image/png')

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In exportCanvas(), the local var exportCanvas = document.createElement('canvas') shadows the function name. This makes stack traces/debugging harder and prevents any future self-reference/recursion. Rename the local variable (e.g., offscreenCanvas) to avoid identifier shadowing.

Suggested change
var exportCanvas = document.createElement('canvas')
var exportContext = exportCanvas.getContext('2d')
exportCanvas.width = canvas.width
exportCanvas.height = canvas.height
exportContext.fillStyle = '#ffffff'
exportContext.fillRect(0, 0, exportCanvas.width, exportCanvas.height)
exportContext.drawImage(canvas, 0, 0)
dataURL = exportCanvas.toDataURL('image/png')
var offscreenCanvas = document.createElement('canvas')
var exportContext = offscreenCanvas.getContext('2d')
offscreenCanvas.width = canvas.width
offscreenCanvas.height = canvas.height
exportContext.fillStyle = '#ffffff'
exportContext.fillRect(0, 0, offscreenCanvas.width, offscreenCanvas.height)
exportContext.drawImage(canvas, 0, 0)
dataURL = offscreenCanvas.toDataURL('image/png')

Copilot uses AI. Check for mistakes.
Comment thread canvas.js
Comment on lines +36 to +37
link.download = 'expression-tree.png'
link.click()

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The download trigger uses link.click() on a detached <a> element. This is unreliable in some browsers (notably Safari) unless the link is temporarily appended to document.body and then removed. Consider appending/removing to improve cross-browser export behavior.

Suggested change
link.download = 'expression-tree.png'
link.click()
link.download = 'expression-tree.png'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants