From 8bfd0cba983c2a6b3d5103551415626e6cdb93be Mon Sep 17 00:00:00 2001 From: v0 Date: Mon, 6 Apr 2026 20:06:58 +0000 Subject: [PATCH 1/9] feat: add strategy examples for AI assistant Implement "Move faster" option with two example approaches Co-authored-by: Jeff Ginger <5846359+Geph@users.noreply.github.com> --- .gitignore | 2 +- README.md | 12 ++++++------ app/page.tsx | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index f650315..37c2b6f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ yarn-error.log* # typescript *.tsbuildinfo -next-env.d.ts \ No newline at end of file +next-env.d.ts diff --git a/README.md b/README.md index 7b7addb..d993db5 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ An integrated help system with multiple support options: ### Installation -```bash +\`\`\`bash # Clone the repository git clone https://github.com/your-username/ocean-cleanup-blocks.git @@ -106,16 +106,16 @@ npm install # Start development server npm run dev -``` +\`\`\` Open [http://localhost:3000](http://localhost:3000) in your browser. ### Building for Production -```bash +\`\`\`bash npm run build npm start -``` +\`\`\` ## Usage Guide @@ -150,7 +150,7 @@ npm start ## Project Structure -``` +\`\`\` ├── app/ │ ├── page.tsx # Main application component │ ├── layout.tsx # Root layout @@ -160,7 +160,7 @@ npm start ├── lib/ │ └── utils.ts # Utility functions └── public/ # Static assets -``` +\`\`\` ## Contributing diff --git a/app/page.tsx b/app/page.tsx index ac26ce8..618a2b6 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -93,7 +93,7 @@ interface AIAssistantState { isVisible: boolean isMinimized: boolean isMaximized: boolean - surveyStep: "main" | "strategy" | "predict" | "fix" | "compare" | "feel" | "partner" + surveyStep: "main" | "strategy" | "predict" | "fix" | "compare" | "feel" | "partner" | "strategy-examples" } interface CategoryState { @@ -2747,7 +2747,10 @@ function BlocklyEditor() {

What strategy would you like help with?

-
+ ) : aiStep === "strategy-examples" ? ( +
+ +

Two approaches to move efficiently:

+ + {/* Approach 1 */} +
+

Approach 1: Increase Velocity

+

Set drive velocity to 100 at the start, then drive forward.

+
+
when started
+
set drive_velocity to 100
+
drive forward 500 mm
+
+

Why: Higher velocity = faster movement. This approach is simple and direct.

+
+ + {/* Approach 2 */} +
+

Approach 2: Add Loop for Continuous Movement

+

Use a forever loop to keep collecting trash continuously without stopping.

+
+
when started
+
forever
+
drive forward 300 mm
+
turn right 90 degrees
+
+

Why: Loops allow the robot to patrol continuously, covering more area and collecting more trash automatically.

+
+ + {/* Comparison */} +
+

Comparison:

+
+
Approach 1: Best for collecting one area quickly. Limited trash collection.
+
Approach 2: Best for collecting more trash over time. Continuously patrols the area.
+
Try both approaches and see which gets you more trash!
+
+
+
) : aiStep === "predict" ? (
)} + + {aiStep === "strategy-examples" && ( +
+
+
+

Strategies to Collect More Trash

+

Try these approaches and compare the results

+
+
+ {/* Approach 1 */} +
+

Approach 1: Increase Velocity

+
+ when started
+   set drive velocity to 100
+   drive forward 500 mm +
+

Result: Fast collection in one area. Good for quick bursts.

+
+ + {/* Approach 2 */} +
+

Approach 2: Continuous Patrol Loop

+
+ when started
+   forever
+     drive forward 300 mm
+     turn right 90 degrees +
+

Result: Covers more area over time. Maximum trash collection.

+
+ +
+

💡 Challenge: Try both approaches and see which collects more trash!

+
+
+
+
+ )} )} From 1dabe9d4b923223d9aea45a4ecf9eb7d53f295ef Mon Sep 17 00:00:00 2001 From: v0 Date: Mon, 6 Apr 2026 20:14:45 +0000 Subject: [PATCH 3/9] feat: enhance strategy overlay with close button and SVG paths Add close button and SVG visualizations for robot movement strategies. Co-authored-by: Jeff Ginger <5846359+Geph@users.noreply.github.com> --- app/page.tsx | 79 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 09b6e41..39afc08 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2620,38 +2620,67 @@ function BlocklyEditor() { )} {aiStep === "strategy-examples" && ( -
-
-
-

Strategies to Collect More Trash

-

Try these approaches and compare the results

+
+
+
+
+

Strategies to Collect More Trash

+

Try these approaches and compare the results

+
+
-
- {/* Approach 1 */} -
-

Approach 1: Increase Velocity

-
- when started
-   set drive velocity to 100
-   drive forward 500 mm +
+
+ {/* Approach 1 */} +
+

Approach 1: Increase Velocity

+ + {/* Movement visualization */} + + + + + + Straight line forward + + +
+ when started
+ set drive velocity to 100
+ drive forward 500 mm +
+

Result: Fast collection in one line. Good for quick focused movement.

-

Result: Fast collection in one area. Good for quick bursts.

-
- {/* Approach 2 */} -
-

Approach 2: Continuous Patrol Loop

-
- when started
-   forever
-     drive forward 300 mm
-     turn right 90 degrees + {/* Approach 2 */} +
+

Approach 2: Continuous Patrol Loop

+ + {/* Movement visualization */} + + + + + Square patrol pattern + + +
+ when started
+ forever
+   drive forward 300 mm
+   turn right 90 degrees +
+

Result: Covers large area continuously. Maximum trash collection.

-

Result: Covers more area over time. Maximum trash collection.

-

💡 Challenge: Try both approaches and see which collects more trash!

+

Challenge: Try both approaches and see which collects more trash!

From 4d892a209f431570847bf173fbd8bb9019c85e3c Mon Sep 17 00:00:00 2001 From: v0 Date: Tue, 14 Apr 2026 21:06:44 +0000 Subject: [PATCH 4/9] feat: add code view toggle between blocks and Python Introduce state and button to switch between block and Python code views. Co-authored-by: Jeff Ginger <5846359+Geph@users.noreply.github.com> --- app/page.tsx | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 39afc08..ba11b3c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -694,11 +694,10 @@ function BlocklyEditor() { const [trashItems, setTrashItems] = useState([]) const [gameState, setGameState] = useState({ trashCollected: 0, - gameLost: false, - isGameOver: false, // Added initialization for isGameOver - isSpawningTrash: false, - showCelebration: false, // Initialize celebration state + isGameOver: false, + trashItems: [], }) + const [codeView, setCodeView] = useState<"blocks" | "python">("blocks") const trashSpawnIntervalRef = useRef(null) const floatAnimationRef = useRef(null) @@ -1488,6 +1487,11 @@ function BlocklyEditor() { }) } + const getPythonCode = useCallback(() => { + if (!workspace) return "# No code yet" + return window.Blockly.Python.workspaceToCode(workspace) + }, [workspace]) + const handleRun = async () => { if (!workspace || !window.Blockly || isRunning) return @@ -2340,6 +2344,12 @@ function BlocklyEditor() {
VEXcode Project + Not Saving
@@ -2505,12 +2515,22 @@ function BlocklyEditor() { {/* Blockly Workspace */}
- {!blocklyLoaded && ( -
-

Loading Blockly...

+ {codeView === "blocks" ? ( + <> + {!blocklyLoaded && ( +
+

Loading Blockly...

+
+ )} +
+ + ) : ( +
+
+                {getPythonCode()}
+              
)} -
From 5f2ef2d7a818c77662fc94445305030ae9980fbe Mon Sep 17 00:00:00 2001 From: v0 Date: Tue, 14 Apr 2026 21:09:58 +0000 Subject: [PATCH 5/9] feat: implement custom Python code generator for Blockly Replace 'getPythonCode' with custom function to convert blocks to Python. Co-authored-by: Jeff Ginger <5846359+Geph@users.noreply.github.com> --- app/page.tsx | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/app/page.tsx b/app/page.tsx index ba11b3c..720db42 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1489,7 +1489,147 @@ function BlocklyEditor() { const getPythonCode = useCallback(() => { if (!workspace) return "# No code yet" - return window.Blockly.Python.workspaceToCode(workspace) + + // Custom Python code generator - traverse blocks and generate Python syntax + const generatePythonFromBlock = (block: any, indent: string = ""): string => { + if (!block) return "" + + const type = block.type + let code = "" + + switch (type) { + case "when_started": + code = "# When Started\ndef main():\n" + const nextBlock = block.getNextBlock() + if (nextBlock) { + code += generatePythonFromBlock(nextBlock, " ") + } else { + code += " pass\n" + } + code += "\nmain()" + break + + case "drive_forward": + case "drive_distance": + const direction = block.getFieldValue("DIRECTION") || "forward" + const distance = block.getFieldValue("DISTANCE") || "200" + const unit = block.getFieldValue("UNIT") || "mm" + const unitPython = unit === "inches" ? "INCHES" : "MM" + code = `${indent}drivetrain.drive_for(${direction.toUpperCase()}, ${distance}, ${unitPython})\n` + break + + case "turn_right": + case "turn_angle": + const turnDir = block.getFieldValue("DIRECTION") || "right" + const degrees = block.getFieldValue("DEGREES") || "90" + code = `${indent}drivetrain.turn_for(${turnDir.toUpperCase()}, ${degrees}, DEGREES)\n` + break + + case "set_drive_velocity": + const velocity = block.getFieldValue("VELOCITY") || "50" + code = `${indent}drivetrain.set_drive_velocity(${velocity}, PERCENT)\n` + break + + case "set_turn_velocity": + const turnVel = block.getFieldValue("VELOCITY") || "50" + code = `${indent}drivetrain.set_turn_velocity(${turnVel}, PERCENT)\n` + break + + case "set_heading": + const heading = block.getFieldValue("HEADING") || "0" + code = `${indent}drivetrain.set_heading(${heading}, DEGREES)\n` + break + + case "stop_driving": + code = `${indent}drivetrain.stop()\n` + break + + case "forever": + code = `${indent}while True:\n` + const foreverStatement = block.getInputTargetBlock("DO") + if (foreverStatement) { + code += generatePythonFromBlock(foreverStatement, indent + " ") + } else { + code += `${indent} pass\n` + } + break + + case "repeat": + const times = block.getFieldValue("TIMES") || "10" + code = `${indent}for i in range(${times}):\n` + const repeatStatement = block.getInputTargetBlock("DO") + if (repeatStatement) { + code += generatePythonFromBlock(repeatStatement, indent + " ") + } else { + code += `${indent} pass\n` + } + break + + case "wait": + const waitTime = block.getFieldValue("TIME") || "1" + code = `${indent}wait(${waitTime}, SECONDS)\n` + break + + case "if_then": + code = `${indent}if condition: # Add your condition\n` + const ifStatement = block.getInputTargetBlock("DO") + if (ifStatement) { + code += generatePythonFromBlock(ifStatement, indent + " ") + } else { + code += `${indent} pass\n` + } + break + + case "magnet_on": + code = `${indent}electromagnet.pickup()\n` + break + + case "magnet_off": + code = `${indent}electromagnet.drop()\n` + break + + case "pen_down": + code = `${indent}pen.move(DOWN)\n` + break + + case "pen_up": + code = `${indent}pen.move(UP)\n` + break + + case "set_pen_color": + const color = block.getFieldValue("COLOR") || "red" + code = `${indent}pen.set_pen_color("${color}")\n` + break + + case "print_console": + const text = block.getFieldValue("TEXT") || "" + code = `${indent}print("${text}")\n` + break + + default: + code = `${indent}# ${type}()\n` + } + + // Process next block in sequence + const next = block.getNextBlock() + if (next) { + code += generatePythonFromBlock(next, indent) + } + + return code + } + + // Get top-level blocks and generate code + const topBlocks = workspace.getTopBlocks(true) + if (topBlocks.length === 0) return "# No code yet\n# Add blocks to see Python code" + + let pythonCode = "# VEXcode VR Python\nfrom vexcode import *\n\n" + + for (const block of topBlocks) { + pythonCode += generatePythonFromBlock(block) + } + + return pythonCode }, [workspace]) const handleRun = async () => { From 2d4b20814eaed33d690aa361ea20f0441ce924fa Mon Sep 17 00:00:00 2001 From: v0 Date: Tue, 14 Apr 2026 21:11:09 +0000 Subject: [PATCH 6/9] fix: preserve Blockly workspace with CSS visibility toggle Keep Blockly div mounted and use CSS for visibility toggling. Co-authored-by: Jeff Ginger <5846359+Geph@users.noreply.github.com> --- app/page.tsx | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 720db42..81120b4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2655,16 +2655,19 @@ function BlocklyEditor() { {/* Blockly Workspace */}
- {codeView === "blocks" ? ( - <> - {!blocklyLoaded && ( -
-

Loading Blockly...

-
- )} -
- - ) : ( + {!blocklyLoaded && ( +
+

Loading Blockly...

+
+ )} + {/* Always keep blocklyDiv mounted, just hide with CSS */} +
+ {/* Python code view */} + {codeView === "python" && (
                 {getPythonCode()}

From 047257dcc41d5da404f244849fe5261da8ff88c7 Mon Sep 17 00:00:00 2001
From: v0 
Date: Tue, 14 Apr 2026 21:35:17 +0000
Subject: [PATCH 7/9] refactor: update getPythonCode function for correct block
 types

Rewrite function to match actual block types and scope blocks to avoid conflicts.

Co-authored-by: Jeff Ginger <5846359+Geph@users.noreply.github.com>
---
 app/page.tsx | 209 ++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 180 insertions(+), 29 deletions(-)

diff --git a/app/page.tsx b/app/page.tsx
index 81120b4..d8aaf1d 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1509,42 +1509,81 @@ function BlocklyEditor() {
           code += "\nmain()"
           break
           
-        case "drive_forward":
-        case "drive_distance":
+        case "drive_simple": {
+          const dir = block.getFieldValue("DIRECTION") || "forward"
+          code = `${indent}drivetrain.drive(${dir.toUpperCase()})\n`
+          break
+        }
+          
+        case "drive_distance": {
           const direction = block.getFieldValue("DIRECTION") || "forward"
           const distance = block.getFieldValue("DISTANCE") || "200"
           const unit = block.getFieldValue("UNIT") || "mm"
           const unitPython = unit === "inches" ? "INCHES" : "MM"
           code = `${indent}drivetrain.drive_for(${direction.toUpperCase()}, ${distance}, ${unitPython})\n`
           break
+        }
           
-        case "turn_right":
-        case "turn_angle":
+        case "turn_simple": {
+          const dir = block.getFieldValue("DIRECTION") || "right"
+          code = `${indent}drivetrain.turn(${dir.toUpperCase()})\n`
+          break
+        }
+          
+        case "turn_degrees": {
           const turnDir = block.getFieldValue("DIRECTION") || "right"
           const degrees = block.getFieldValue("DEGREES") || "90"
           code = `${indent}drivetrain.turn_for(${turnDir.toUpperCase()}, ${degrees}, DEGREES)\n`
           break
+        }
+        
+        case "turn_to_heading": {
+          const heading = block.getFieldValue("HEADING") || "0"
+          code = `${indent}drivetrain.turn_to_heading(${heading}, DEGREES)\n`
+          break
+        }
+        
+        case "turn_to_rotation": {
+          const rotation = block.getFieldValue("ROTATION") || "0"
+          code = `${indent}drivetrain.turn_to_rotation(${rotation}, DEGREES)\n`
+          break
+        }
           
-        case "set_drive_velocity":
+        case "set_drive_velocity": {
           const velocity = block.getFieldValue("VELOCITY") || "50"
           code = `${indent}drivetrain.set_drive_velocity(${velocity}, PERCENT)\n`
           break
+        }
           
-        case "set_turn_velocity":
+        case "set_turn_velocity": {
           const turnVel = block.getFieldValue("VELOCITY") || "50"
           code = `${indent}drivetrain.set_turn_velocity(${turnVel}, PERCENT)\n`
           break
+        }
           
-        case "set_heading":
+        case "set_drive_heading": {
           const heading = block.getFieldValue("HEADING") || "0"
           code = `${indent}drivetrain.set_heading(${heading}, DEGREES)\n`
           break
+        }
+        
+        case "set_drive_rotation": {
+          const rotation = block.getFieldValue("ROTATION") || "0"
+          code = `${indent}drivetrain.set_rotation(${rotation}, DEGREES)\n`
+          break
+        }
+        
+        case "set_drive_timeout": {
+          const timeout = block.getFieldValue("TIMEOUT") || "1"
+          code = `${indent}drivetrain.set_timeout(${timeout}, SECONDS)\n`
+          break
+        }
           
         case "stop_driving":
           code = `${indent}drivetrain.stop()\n`
           break
           
-        case "forever":
+        case "forever_loop": {
           code = `${indent}while True:\n`
           const foreverStatement = block.getInputTargetBlock("DO")
           if (foreverStatement) {
@@ -1553,8 +1592,9 @@ function BlocklyEditor() {
             code += `${indent}    pass\n`
           }
           break
+        }
           
-        case "repeat":
+        case "repeat_times": {
           const times = block.getFieldValue("TIMES") || "10"
           code = `${indent}for i in range(${times}):\n`
           const repeatStatement = block.getInputTargetBlock("DO")
@@ -1564,13 +1604,41 @@ function BlocklyEditor() {
             code += `${indent}    pass\n`
           }
           break
+        }
+        
+        case "repeat_until": {
+          code = `${indent}while not condition:  # Add your condition\n`
+          const repeatUntilStatement = block.getInputTargetBlock("DO")
+          if (repeatUntilStatement) {
+            code += generatePythonFromBlock(repeatUntilStatement, indent + "    ")
+          } else {
+            code += `${indent}    pass\n`
+          }
+          break
+        }
+        
+        case "while_loop": {
+          code = `${indent}while condition:  # Add your condition\n`
+          const whileStatement = block.getInputTargetBlock("DO")
+          if (whileStatement) {
+            code += generatePythonFromBlock(whileStatement, indent + "    ")
+          } else {
+            code += `${indent}    pass\n`
+          }
+          break
+        }
           
-        case "wait":
+        case "wait_seconds": {
           const waitTime = block.getFieldValue("TIME") || "1"
           code = `${indent}wait(${waitTime}, SECONDS)\n`
           break
+        }
+        
+        case "wait_until":
+          code = `${indent}wait_until(condition)  # Add your condition\n`
+          break
           
-        case "if_then":
+        case "if_then": {
           code = `${indent}if condition:  # Add your condition\n`
           const ifStatement = block.getInputTargetBlock("DO")
           if (ifStatement) {
@@ -1579,41 +1647,124 @@ function BlocklyEditor() {
             code += `${indent}    pass\n`
           }
           break
-          
-        case "magnet_on":
-          code = `${indent}electromagnet.pickup()\n`
+        }
+        
+        case "if_then_else": {
+          code = `${indent}if condition:  # Add your condition\n`
+          const ifDoStatement = block.getInputTargetBlock("DO")
+          if (ifDoStatement) {
+            code += generatePythonFromBlock(ifDoStatement, indent + "    ")
+          } else {
+            code += `${indent}    pass\n`
+          }
+          code += `${indent}else:\n`
+          const elseStatement = block.getInputTargetBlock("ELSE")
+          if (elseStatement) {
+            code += generatePythonFromBlock(elseStatement, indent + "    ")
+          } else {
+            code += `${indent}    pass\n`
+          }
           break
-          
-        case "magnet_off":
-          code = `${indent}electromagnet.drop()\n`
+        }
+        
+        case "break_block":
+          code = `${indent}break\n`
+          break
+        
+        case "stop_project":
+          code = `${indent}stop()\n`
           break
+        
+        case "comment_block": {
+          const comment = block.getFieldValue("COMMENT") || ""
+          code = `${indent}# ${comment}\n`
+          break
+        }
           
-        case "pen_down":
-          code = `${indent}pen.move(DOWN)\n`
+        case "energize_magnet": {
+          const action = block.getFieldValue("ACTION") || "pick up"
+          code = action === "pick up" 
+            ? `${indent}electromagnet.pickup()\n`
+            : `${indent}electromagnet.drop()\n`
           break
+        }
           
-        case "pen_up":
-          code = `${indent}pen.move(UP)\n`
+        case "move_pen": {
+          const penAction = block.getFieldValue("POSITION") || "down"
+          code = `${indent}pen.move(${penAction.toUpperCase()})\n`
           break
+        }
+        
+        case "set_pen_width": {
+          const width = block.getFieldValue("WIDTH") || "1"
+          code = `${indent}pen.set_pen_width(${width})\n`
+          break
+        }
           
-        case "set_pen_color":
+        case "set_pen_color": {
           const color = block.getFieldValue("COLOR") || "red"
           code = `${indent}pen.set_pen_color("${color}")\n`
           break
+        }
           
-        case "print_console":
+        case "print_text": {
           const text = block.getFieldValue("TEXT") || ""
-          code = `${indent}print("${text}")\n`
+          code = `${indent}brain.print("${text}")\n`
+          break
+        }
+        
+        case "clear_all_rows":
+          code = `${indent}brain.clear()\n`
+          break
+        
+        case "set_cursor_next_row":
+          code = `${indent}brain.next_row()\n`
+          break
+        
+        // Sensing blocks
+        case "bumper_pressed":
+          code = `bumper.pressed()`
+          break
+        
+        case "eye_is_near": {
+          const nearObj = block.getFieldValue("OBJECT") || "any"
+          code = `eye.is_near_object()`
           break
+        }
+        
+        case "eye_detects_color": {
+          const detColor = block.getFieldValue("COLOR") || "red"
+          code = `eye.detect("${detColor}")`
+          break
+        }
+        
+        // Operators
+        case "math_arithmetic": {
+          const op = block.getFieldValue("OP") || "ADD"
+          const opMap: { [key: string]: string } = { ADD: "+", MINUS: "-", MULTIPLY: "*", DIVIDE: "/" }
+          const a = block.getFieldValue("A") || "0"
+          const b = block.getFieldValue("B") || "0"
+          code = `(${a} ${opMap[op] || "+"} ${b})`
+          break
+        }
+        
+        case "random_int": {
+          const from = block.getFieldValue("FROM") || "1"
+          const to = block.getFieldValue("TO") || "10"
+          code = `random.randint(${from}, ${to})`
+          break
+        }
           
         default:
           code = `${indent}# ${type}()\n`
       }
       
-      // Process next block in sequence
-      const next = block.getNextBlock()
-      if (next) {
-        code += generatePythonFromBlock(next, indent)
+      // Process next block in sequence (skip for value blocks)
+      if (!["math_arithmetic", "random_int", "bumper_pressed", "eye_is_near", "eye_detects_color"].includes(type)) {
+        const next = block.getNextBlock()
+        if (next) {
+          code += generatePythonFromBlock(next, indent)
+        }
       }
       
       return code
@@ -1623,7 +1774,7 @@ function BlocklyEditor() {
     const topBlocks = workspace.getTopBlocks(true)
     if (topBlocks.length === 0) return "# No code yet\n# Add blocks to see Python code"
     
-    let pythonCode = "# VEXcode VR Python\nfrom vexcode import *\n\n"
+    let pythonCode = "# VEXcode VR Python\nfrom vexcode import *\nimport random\n\n"
     
     for (const block of topBlocks) {
       pythonCode += generatePythonFromBlock(block)

From 61af3b00a605538be1ee50381418ca89bdcac9ee Mon Sep 17 00:00:00 2001
From: v0 
Date: Tue, 14 Apr 2026 21:39:52 +0000
Subject: [PATCH 8/9] refactor: fix block traversal in getPythonCode function

Use getInputTargetBlock for logic blocks and generate helpers for traversal.

Co-authored-by: Jeff Ginger <5846359+Geph@users.noreply.github.com>
---
 app/page.tsx                      |  99 ++--
 components/ui/accordion.tsx       |  66 +++
 components/ui/alert-dialog.tsx    | 157 +++++++
 components/ui/alert.tsx           |  66 +++
 components/ui/aspect-ratio.tsx    |  11 +
 components/ui/avatar.tsx          |  53 +++
 components/ui/badge.tsx           |  46 ++
 components/ui/breadcrumb.tsx      | 109 +++++
 components/ui/button-group.tsx    |  83 ++++
 components/ui/calendar.tsx        | 213 +++++++++
 components/ui/card.tsx            |  92 ++++
 components/ui/carousel.tsx        | 241 ++++++++++
 components/ui/chart.tsx           | 351 +++++++++++++++
 components/ui/checkbox.tsx        |  32 ++
 components/ui/collapsible.tsx     |  33 ++
 components/ui/command.tsx         | 184 ++++++++
 components/ui/context-menu.tsx    | 252 +++++++++++
 components/ui/dialog.tsx          | 143 ++++++
 components/ui/drawer.tsx          | 135 ++++++
 components/ui/dropdown-menu.tsx   | 257 +++++++++++
 components/ui/empty.tsx           | 104 +++++
 components/ui/field.tsx           | 244 ++++++++++
 components/ui/form.tsx            | 167 +++++++
 components/ui/hover-card.tsx      |  44 ++
 components/ui/input-group.tsx     | 169 +++++++
 components/ui/input-otp.tsx       |  77 ++++
 components/ui/input.tsx           |  21 +
 components/ui/item.tsx            | 193 ++++++++
 components/ui/kbd.tsx             |  28 ++
 components/ui/label.tsx           |  24 +
 components/ui/menubar.tsx         | 276 ++++++++++++
 components/ui/navigation-menu.tsx | 166 +++++++
 components/ui/pagination.tsx      | 127 ++++++
 components/ui/popover.tsx         |  48 ++
 components/ui/progress.tsx        |  31 ++
 components/ui/radio-group.tsx     |  45 ++
 components/ui/resizable.tsx       |  56 +++
 components/ui/scroll-area.tsx     |  58 +++
 components/ui/select.tsx          | 185 ++++++++
 components/ui/separator.tsx       |  28 ++
 components/ui/sheet.tsx           | 139 ++++++
 components/ui/sidebar.tsx         | 726 ++++++++++++++++++++++++++++++
 components/ui/skeleton.tsx        |  13 +
 components/ui/slider.tsx          |  59 +++
 components/ui/sonner.tsx          |  25 +
 components/ui/spinner.tsx         |  16 +
 components/ui/switch.tsx          |  29 ++
 components/ui/table.tsx           | 116 +++++
 components/ui/tabs.tsx            |  66 +++
 components/ui/textarea.tsx        |  18 +
 components/ui/toast.tsx           | 129 ++++++
 components/ui/toaster.tsx         |  35 ++
 components/ui/toggle-group.tsx    |  73 +++
 components/ui/toggle.tsx          |  47 ++
 components/ui/tooltip.tsx         |  61 +++
 components/ui/use-mobile.tsx      |  19 +
 components/ui/use-toast.ts        | 191 ++++++++
 hooks/use-mobile.ts               |  19 +
 hooks/use-toast.ts                | 191 ++++++++
 59 files changed, 6623 insertions(+), 63 deletions(-)
 create mode 100644 components/ui/accordion.tsx
 create mode 100644 components/ui/alert-dialog.tsx
 create mode 100644 components/ui/alert.tsx
 create mode 100644 components/ui/aspect-ratio.tsx
 create mode 100644 components/ui/avatar.tsx
 create mode 100644 components/ui/badge.tsx
 create mode 100644 components/ui/breadcrumb.tsx
 create mode 100644 components/ui/button-group.tsx
 create mode 100644 components/ui/calendar.tsx
 create mode 100644 components/ui/card.tsx
 create mode 100644 components/ui/carousel.tsx
 create mode 100644 components/ui/chart.tsx
 create mode 100644 components/ui/checkbox.tsx
 create mode 100644 components/ui/collapsible.tsx
 create mode 100644 components/ui/command.tsx
 create mode 100644 components/ui/context-menu.tsx
 create mode 100644 components/ui/dialog.tsx
 create mode 100644 components/ui/drawer.tsx
 create mode 100644 components/ui/dropdown-menu.tsx
 create mode 100644 components/ui/empty.tsx
 create mode 100644 components/ui/field.tsx
 create mode 100644 components/ui/form.tsx
 create mode 100644 components/ui/hover-card.tsx
 create mode 100644 components/ui/input-group.tsx
 create mode 100644 components/ui/input-otp.tsx
 create mode 100644 components/ui/input.tsx
 create mode 100644 components/ui/item.tsx
 create mode 100644 components/ui/kbd.tsx
 create mode 100644 components/ui/label.tsx
 create mode 100644 components/ui/menubar.tsx
 create mode 100644 components/ui/navigation-menu.tsx
 create mode 100644 components/ui/pagination.tsx
 create mode 100644 components/ui/popover.tsx
 create mode 100644 components/ui/progress.tsx
 create mode 100644 components/ui/radio-group.tsx
 create mode 100644 components/ui/resizable.tsx
 create mode 100644 components/ui/scroll-area.tsx
 create mode 100644 components/ui/select.tsx
 create mode 100644 components/ui/separator.tsx
 create mode 100644 components/ui/sheet.tsx
 create mode 100644 components/ui/sidebar.tsx
 create mode 100644 components/ui/skeleton.tsx
 create mode 100644 components/ui/slider.tsx
 create mode 100644 components/ui/sonner.tsx
 create mode 100644 components/ui/spinner.tsx
 create mode 100644 components/ui/switch.tsx
 create mode 100644 components/ui/table.tsx
 create mode 100644 components/ui/tabs.tsx
 create mode 100644 components/ui/textarea.tsx
 create mode 100644 components/ui/toast.tsx
 create mode 100644 components/ui/toaster.tsx
 create mode 100644 components/ui/toggle-group.tsx
 create mode 100644 components/ui/toggle.tsx
 create mode 100644 components/ui/tooltip.tsx
 create mode 100644 components/ui/use-mobile.tsx
 create mode 100644 components/ui/use-toast.ts
 create mode 100644 hooks/use-mobile.ts
 create mode 100644 hooks/use-toast.ts

diff --git a/app/page.tsx b/app/page.tsx
index d8aaf1d..e3c71e3 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1490,6 +1490,19 @@ function BlocklyEditor() {
   const getPythonCode = useCallback(() => {
     if (!workspace) return "# No code yet"
     
+    // Recursively generate code for a statement input (DO, ELSE, etc.)
+    const generateStatements = (block: any, inputName: string, indent: string): string => {
+      const child = block.getInputTargetBlock(inputName)
+      if (!child) return `${indent}pass\n`
+      return generateSequence(child, indent)
+    }
+
+    // Walk a chain of next-connected blocks
+    const generateSequence = (block: any, indent: string): string => {
+      if (!block) return ""
+      return generatePythonFromBlock(block, indent) + generateSequence(block.getNextBlock(), indent)
+    }
+
     // Custom Python code generator - traverse blocks and generate Python syntax
     const generatePythonFromBlock = (block: any, indent: string = ""): string => {
       if (!block) return ""
@@ -1500,12 +1513,7 @@ function BlocklyEditor() {
       switch (type) {
         case "when_started":
           code = "# When Started\ndef main():\n"
-          const nextBlock = block.getNextBlock()
-          if (nextBlock) {
-            code += generatePythonFromBlock(nextBlock, "    ")
-          } else {
-            code += "    pass\n"
-          }
+          code += generateStatements(block, "DO", "    ")
           code += "\nmain()"
           break
           
@@ -1583,87 +1591,60 @@ function BlocklyEditor() {
           code = `${indent}drivetrain.stop()\n`
           break
           
+        case "forever":
         case "forever_loop": {
           code = `${indent}while True:\n`
-          const foreverStatement = block.getInputTargetBlock("DO")
-          if (foreverStatement) {
-            code += generatePythonFromBlock(foreverStatement, indent + "    ")
-          } else {
-            code += `${indent}    pass\n`
-          }
+          code += generateStatements(block, "DO", indent + "    ")
           break
         }
-          
+
+        case "repeat":
         case "repeat_times": {
           const times = block.getFieldValue("TIMES") || "10"
           code = `${indent}for i in range(${times}):\n`
-          const repeatStatement = block.getInputTargetBlock("DO")
-          if (repeatStatement) {
-            code += generatePythonFromBlock(repeatStatement, indent + "    ")
-          } else {
-            code += `${indent}    pass\n`
-          }
+          code += generateStatements(block, "DO", indent + "    ")
           break
         }
         
         case "repeat_until": {
-          code = `${indent}while not condition:  # Add your condition\n`
-          const repeatUntilStatement = block.getInputTargetBlock("DO")
-          if (repeatUntilStatement) {
-            code += generatePythonFromBlock(repeatUntilStatement, indent + "    ")
-          } else {
-            code += `${indent}    pass\n`
-          }
+          const cond = block.getInputTargetBlock("CONDITION")
+          const condStr = cond ? `not ${cond.type}()` : "not condition"
+          code = `${indent}while ${condStr}:\n`
+          code += generateStatements(block, "DO", indent + "    ")
           break
         }
         
         case "while_loop": {
-          code = `${indent}while condition:  # Add your condition\n`
-          const whileStatement = block.getInputTargetBlock("DO")
-          if (whileStatement) {
-            code += generatePythonFromBlock(whileStatement, indent + "    ")
-          } else {
-            code += `${indent}    pass\n`
-          }
+          code = `${indent}while condition:\n`
+          code += generateStatements(block, "DO", indent + "    ")
           break
         }
           
         case "wait_seconds": {
-          const waitTime = block.getFieldValue("TIME") || "1"
+          const waitTime = block.getFieldValue("SECONDS") || "1"
           code = `${indent}wait(${waitTime}, SECONDS)\n`
           break
         }
         
         case "wait_until":
-          code = `${indent}wait_until(condition)  # Add your condition\n`
+          code = `${indent}wait_until(condition)\n`
           break
           
         case "if_then": {
-          code = `${indent}if condition:  # Add your condition\n`
-          const ifStatement = block.getInputTargetBlock("DO")
-          if (ifStatement) {
-            code += generatePythonFromBlock(ifStatement, indent + "    ")
-          } else {
-            code += `${indent}    pass\n`
-          }
+          const ifCond = block.getInputTargetBlock("CONDITION")
+          const ifCondStr = ifCond ? generatePythonFromBlock(ifCond, "").trim() : "condition"
+          code = `${indent}if ${ifCondStr}:\n`
+          code += generateStatements(block, "DO", indent + "    ")
           break
         }
         
         case "if_then_else": {
-          code = `${indent}if condition:  # Add your condition\n`
-          const ifDoStatement = block.getInputTargetBlock("DO")
-          if (ifDoStatement) {
-            code += generatePythonFromBlock(ifDoStatement, indent + "    ")
-          } else {
-            code += `${indent}    pass\n`
-          }
+          const ifelseCond = block.getInputTargetBlock("CONDITION")
+          const ifelseCondStr = ifelseCond ? generatePythonFromBlock(ifelseCond, "").trim() : "condition"
+          code = `${indent}if ${ifelseCondStr}:\n`
+          code += generateStatements(block, "DO", indent + "    ")
           code += `${indent}else:\n`
-          const elseStatement = block.getInputTargetBlock("ELSE")
-          if (elseStatement) {
-            code += generatePythonFromBlock(elseStatement, indent + "    ")
-          } else {
-            code += `${indent}    pass\n`
-          }
+          code += generateStatements(block, "ELSE", indent + "    ")
           break
         }
         
@@ -1759,14 +1740,6 @@ function BlocklyEditor() {
           code = `${indent}# ${type}()\n`
       }
       
-      // Process next block in sequence (skip for value blocks)
-      if (!["math_arithmetic", "random_int", "bumper_pressed", "eye_is_near", "eye_detects_color"].includes(type)) {
-        const next = block.getNextBlock()
-        if (next) {
-          code += generatePythonFromBlock(next, indent)
-        }
-      }
-      
       return code
     }
     
diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx
new file mode 100644
index 0000000..e538a33
--- /dev/null
+++ b/components/ui/accordion.tsx
@@ -0,0 +1,66 @@
+'use client'
+
+import * as React from 'react'
+import * as AccordionPrimitive from '@radix-ui/react-accordion'
+import { ChevronDownIcon } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+
+function Accordion({
+  ...props
+}: React.ComponentProps) {
+  return 
+}
+
+function AccordionItem({
+  className,
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+  )
+}
+
+function AccordionTrigger({
+  className,
+  children,
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+      svg]:rotate-180',
+          className,
+        )}
+        {...props}
+      >
+        {children}
+        
+      
+    
+  )
+}
+
+function AccordionContent({
+  className,
+  children,
+  ...props
+}: React.ComponentProps) {
+  return (
+    
+      
{children}
+
+ ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..9704452 --- /dev/null +++ b/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +'use client' + +import * as React from 'react' +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' + +import { cn } from '@/lib/utils' +import { buttonVariants } from '@/components/ui/button' + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx new file mode 100644 index 0000000..e6751ab --- /dev/null +++ b/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const alertVariants = cva( + 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', + { + variants: { + variant: { + default: 'bg-card text-card-foreground', + destructive: + 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/components/ui/aspect-ratio.tsx b/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..40bb120 --- /dev/null +++ b/components/ui/aspect-ratio.tsx @@ -0,0 +1,11 @@ +'use client' + +import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio' + +function AspectRatio({ + ...props +}: React.ComponentProps) { + return +} + +export { AspectRatio } diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..aa98465 --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +'use client' + +import * as React from 'react' +import * as AvatarPrimitive from '@radix-ui/react-avatar' + +import { cn } from '@/lib/utils' + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..fc4126b --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<'span'> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'span' + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..1750ff2 --- /dev/null +++ b/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { ChevronRight, MoreHorizontal } from 'lucide-react' + +import { cn } from '@/lib/utils' + +function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) { + return