Skip to content

feat(whiteboard) add interactive whiteboard functionality , Freehand …#141

Merged
A1L13N merged 3 commits into
alphaonelabs:mainfrom
varruunnn:main
Mar 20, 2025
Merged

feat(whiteboard) add interactive whiteboard functionality , Freehand …#141
A1L13N merged 3 commits into
alphaonelabs:mainfrom
varruunnn:main

Conversation

@varruunnn
Copy link
Copy Markdown
Contributor

@varruunnn varruunnn commented Mar 20, 2025

…drawing with various pen sizes and colors ,Shape tools (circles, squares, lines),text input and annotation

CLOSES #130
image

Summary by CodeRabbit

  • New Features
    • Introduced a whiteboard feature accessible through a new navigation link.
    • Added an interactive whiteboard page offering a drawing canvas with toolbars for selecting drawing tools, adjusting colors and brush settings, uploading images, downloading results, and clearing the canvas.
    • Enhanced navigation options with a new link to the whiteboard.

…drawing with various pen sizes and colors ,Shape tools (circles, squares, lines),text input and annotation
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 20, 2025

Walkthrough

This pull request introduces a new interactive whiteboard feature. A navigation link has been added to the base template directing users to the whiteboard page. A dedicated HTML template has been created for the whiteboard, which incorporates a canvas, toolbar with various drawing tools, a color picker, and controls for image handling and canvas state preservation. In addition, new URL routing and a corresponding view function have been implemented to serve the whiteboard template.

Changes

File(s) Change Summary
web/templates/base.html
web/templates/whiteboard.html
Added a new navigation link in the base template and introduced a new whiteboard template featuring a canvas, drawing tools, and image controls.
web/urls.py
web/views.py
Added a new URL pattern for the whiteboard and a corresponding view function that renders the whiteboard template.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant B as Browser
    participant S as Server
    participant JS as Whiteboard Logic

    U->>B: Click "whiteboard" link
    B->>S: GET /whiteboard/
    S->>B: Serve whiteboard.html with embedded JS
    B->>JS: Initialize drawing tools and canvas
    JS->>B: Handle drawing events (mouse, keyboard) for freehand, shapes, and eraser actions
Loading

Assessment against linked issues

Objective Addressed Explanation
Interactive whiteboard drawing tools (freehand, shapes, eraser) ([#130])

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c017be and 735e945.

📒 Files selected for processing (1)
  • web/urls.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/urls.py

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (9)
web/views.py (1)

3236-3237: The view implementation is minimal and functional.

The whiteboard view correctly renders the whiteboard.html template. Consider adding docstrings to maintain consistency with other views in this file.

+
def whiteboard(request):
+    """Render the interactive whiteboard page."""
     return render(request, "whiteboard.html")
🧰 Tools
🪛 GitHub Actions: Tests

[error] 3236-3239: flake8: E302 expected 2 blank lines, found 1

web/templates/base.html (1)

210-212: New Whiteboard Navigation Link Addition
The new anchor tag provides a clear entry point to the whiteboard feature with an icon and descriptive text. For consistency with the other navigation links (e.g., "Home", "About"), consider capitalizing the link text to “Whiteboard”.

web/templates/whiteboard.html (7)

7-63: Whiteboard Toolbar and Canvas UI
The toolbar includes comprehensive controls—tool selection, color picker, pen width, image scaling, and buttons for loading, downloading, and clearing the board. The layout is clean and intuitive. For enhanced accessibility, consider adding aria-label or title attributes to interactive elements (e.g., the select input and buttons).


85-102: Tool and Settings Event Listeners
Event listeners for the tool selector, pen color, and pen width are implemented correctly. The logic that adjusts globalAlpha for the highlighter versus other tools is clear. To improve maintainability as more tools are added, consider refactoring these conditional statements (perhaps via a switch-case construct).


114-134: Image Upload and Loading Mechanism
The file upload process using a FileReader is well implemented. Upon a file selection, the image is loaded, drawn onto the canvas with scaling, and stored in local storage for persistence. To further improve robustness, consider adding error handling (e.g., verifying file type and handling read errors).


136-141: Redraw Background Function
The redrawBackground() function calculates new dimensions based on the current scale and redraws the uploaded image. If the design intends for the background image to be re-scaled without affecting user drawings, you might consider managing foreground and background on separate layers.


172-186: Arrow Drawing Function
The drawArrow() function uses trigonometric calculations to draw an arrow with a proper head. While the current approach works, the arrowhead is drawn using two stroke() calls. Consider refactoring this section—possibly by streamlining the drawing of the arrowhead or using fill() for a more solid visual effect—to improve clarity and performance.


187-203: Canvas Mousedown Event Handling
The mousedown event listener initializes drawing actions based on the selected tool. For freehand tools (pen, eraser, highlighter), it begins a new path; for text, it prompts the user for input; and for other shapes, it takes a snapshot to allow non-destructive previews. While the prompt for text is a straightforward solution, you might later enhance the user experience with an in-canvas text editor.


208-253: Canvas Mousemove Event Handling
The mousemove event handler updates the drawing in real time, distinguishing between freehand drawing and shape preview (line, rectangle, circle, arrow) using restoreSnapshot(). The approach is effective; however, there is some redundancy in the shape-drawing logic that could be refactored into helper functions to improve clarity and reduce duplication.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e61afb7 and c85dc62.

📒 Files selected for processing (4)
  • web/templates/base.html (1 hunks)
  • web/templates/whiteboard.html (1 hunks)
  • web/urls.py (1 hunks)
  • web/views.py (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
web/urls.py (1)
web/views.py (1) (1)
  • whiteboard (3236-3237)
🪛 GitHub Actions: Tests
web/views.py

[error] 3236-3239: flake8: E302 expected 2 blank lines, found 1

🔇 Additional comments (9)
web/urls.py (1)

233-233: URL route for whiteboard functionality looks good.

The new URL pattern for the whiteboard feature correctly maps the URL path to the corresponding view function.

web/templates/whiteboard.html (8)

1-6: Template Inheritance and Title Block
The template correctly extends "base.html" and sets the title to "Advanced Whiteboard", which clearly communicates the purpose of this page.


65-84: Canvas Initialization and Global Variables
The script initializes the canvas element, its 2D context, and various global state variables (such as drawing state, tool settings, and image-related variables) effectively. This solid setup is essential for the whiteboard’s functionality.


103-113: Image Scale Adjustment and Canvas Clearing
The image scale listener updates the scaling factor and redraws the background image appropriately, while the clear button handler effectively resets the canvas and removes the persisted image from local storage. If the intent is to remove all drawings, this is ideal; otherwise, you might later consider layering the background separately.


142-153: Persistent Background Image Restoration
Restoring the background image from local storage upon window load ensures that users can pick up where they left off. The implementation gracefully handles image loading and scaling.


154-160: Canvas Download Functionality
The download button effectively converts the canvas content to a PNG image and triggers a download. This straightforward implementation adds useful functionality for the end user.


162-170: Canvas Snapshot and Restore Functions
The takeSnapshot() and restoreSnapshot() functions play an essential role in enabling non-destructive drawing for geometric shapes. Their clear implementation supports smooth drawing interactions.


255-282: Canvas Mouseup Event Handling
On mouseup, the listener finalizes the drawing by re-rendering the precise shape based on the tool selected. In particular, the arrow tool’s separate handling by invoking drawArrow() ensures that the arrow is drawn with the intended style.


284-286: Canvas Mouseleave Event Handling
The mouseleave event listener appropriately cancels the drawing action if the cursor exits the canvas area, preventing stray strokes.

Comment thread web/views.py
@varruunnn
Copy link
Copy Markdown
Contributor Author

@A1L13N I will fix linting after some time.

@varruunnn
Copy link
Copy Markdown
Contributor Author

@A1L13N i have fixed all tests and linting too !

@A1L13N A1L13N merged commit 467d96c into alphaonelabs:main Mar 20, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 22, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's look at the whiteboard.html implementation to understand its current structure
cat web/templates/whiteboard.html

Length of output: 11225


🏁 Script executed:

#!/bin/bash
# Let's understand the project structure to determine how to implement collaborative features
# First, check what web framework is being used by looking at the requirements.txt file
cat requirements.txt 2>/dev/null || echo "requirements.txt not found"

# Check what's in the main project directory
ls -la

# Look at the Django project settings
find . -name "settings.py" -exec cat {} \;

# Examine the main URL configuration to see where the app is mounted
find . -name "urls.py" -exec cat {} \; | head -40

Length of output: 19366


🏁 Script executed:

#!/bin/bash
# Check if Django Channels is already installed
grep -i "channels" requirements.txt

# Check if there are any WebSocket related imports or code
grep -r "WebSocket" --include="*.py" --include="*.html" .

# Look at the views.py file to see the whiteboard view implementation
cat web/views.py | grep -A 15 "def whiteboard"

# Check project's asgi.py for async capabilities
find . -name "asgi.py" -exec cat {} \;

Length of output: 1231


@varruunnn To make your whiteboard live and collaborative, you'll need to implement WebSocket communication. Here's how to do it:

1. Install required dependencies

pip install channels[daphne] channels-redis

2. Update your Django settings.py

# Add to INSTALLED_APPS
INSTALLED_APPS = [
    # existing apps...
    'channels',
]

# Add Channel layers configuration
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [("127.0.0.1", 6379)],
        },
    },
}

# Update ASGI application
ASGI_APPLICATION = 'web.asgi.application'

3. Create a WebSocket consumer

Create a new file web/consumers.py:

import json
from channels.generic.websocket import AsyncWebsocketConsumer

class WhiteboardConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = f'whiteboard_{self.room_name}'

        # Join room group
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )
        await self.accept()

    async def disconnect(self, close_code):
        # Leave room group
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    async def receive(self, text_data):
        data = json.loads(text_data)
        event_type = data.get('type')
        
        # Forward the event to the group
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'broadcast_event',
                'data': data
            }
        )

    # Receive message from room group
    async def broadcast_event(self, event):
        data = event['data']
        
        # Send message to WebSocket
        await self.send(text_data=json.dumps(data))

4. Update your ASGI configuration

Modify web/asgi.py:

import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import web.routing  # we'll create this next

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'web.settings')

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": AuthMiddlewareStack(
        URLRouter(
            web.routing.websocket_urlpatterns
        )
    ),
})

5. Create WebSocket routing

Create a new file web/routing.py:

from django.urls import re_path
from . import consumers

websocket_urlpatterns = [
    re_path(r'ws/whiteboard/(?P <room_name>\w+)/$', consumers.WhiteboardConsumer.as_asgi()),
]

6. Update the whiteboard view

def whiteboard(request, room_name=None):
    if room_name is None:
        # Generate a random room name if none provided
        import uuid
        room_name = str(uuid.uuid4())[:8]
        return redirect('whiteboard', room_name=room_name)
    
    return render(request, "whiteboard.html", {'room_name': room_name})

7. Update URL patterns

# Add to urlpatterns
path("whiteboard/", views.whiteboard, name="whiteboard"),
path("whiteboard/<str:room_name>/", views.whiteboard, name="whiteboard"),

8. Update the whiteboard.html template

// Add this at the top of your script section
let roomName = '{{ room_name }}';
let socket = new WebSocket(`ws://${window.location.host}/ws/whiteboard/${roomName}/`);
let isDrawingLocally = false;

// When receiving a message from the server
socket.onmessage = function(e) {
    const data = JSON.parse(e.data);
    
    if (isDrawingLocally) {
        return; // Avoid processing our own events
    }
    
    if (data.type === 'draw_event') {
        // Handle drawing event
        const event = data.event;
        
        if (event.tool === 'pen' || event.tool === 'eraser' || event.tool === 'highlighter') {
            ctx.beginPath();
            ctx.moveTo(event.startX, event.startY);
            ctx.lineTo(event.currentX, event.currentY);
            ctx.strokeStyle = event.color;
            ctx.lineWidth = event.width;
            ctx.globalAlpha = event.alpha;
            ctx.stroke();
        } else if (event.tool === 'line') {
            drawLine(event.startX, event.startY, event.endX, event.endY, event.color, event.width);
        } else if (event.tool === 'rectangle') {
            drawRectangle(event.startX, event.startY, event.width, event.height, event.color, event.lineWidth);
        } else if (event.tool === 'circle') {
            drawCircle(event.startX, event.startY, event.radius, event.color, event.lineWidth);
        } else if (event.tool === 'text') {
            drawText(event.text, event.x, event.y, event.font, event.color);
        }
    } else if (data.type === 'clear_event') {
        // Clear canvas
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    }
};

// Modify existing mousemove handler
canvas.addEventListener('mousemove', (e) => {
    if (!drawing) return;
    const currentX = e.offsetX;
    const currentY = e.offsetY;

    isDrawingLocally = true;
    
    if (currentTool === 'pen' || currentTool === 'eraser' || currentTool === 'highlighter') {
        // Drawing code...
        
        // Send drawing event
        socket.send(JSON.stringify({
            'type': 'draw_event',
            'event': {
                'tool': currentTool,
                'startX': startX,
                'startY': startY,
                'currentX': currentX,
                'currentY': currentY,
                'color': penColor,
                'width': penWidth,
                'alpha': ctx.globalAlpha
            }
        }));
        
        startX = currentX;
        startY = currentY;
    }
    
    isDrawingLocally = false;
});

// Also update mouseup and other drawing events similarly

9. Add state synchronization

To make new users see the current board state, you'll need to save snapshots. Add this to the consumer:

# In the WhiteboardConsumer class
board_states = {}  # Class variable to store board states

async def connect(self):
    # Existing code...
    
    # Send current state if available
    if self.room_group_name in self.board_states:
        await self.send(text_data=json.dumps({
            'type': 'board_state',
            'image_data': self.board_states[self.room_group_name]
        }))

# Add method to save board state
async def receive(self, text_data):
    data = json.loads(text_data)
    
    # If it's a board state update, save it
    if data.get('type') == 'board_state_update':
        self.board_states[self.room_group_name] = data.get('image_data')
    
    # Existing code...

Then, periodically send canvas snapshots from the client:

// Add to whiteboard.html
function saveCurrentState() {
    const imageData = canvas.toDataURL("image/png");
    socket.send(JSON.stringify({
        'type': 'board_state_update',
        'image_data': imageData
    }));
}

// Call every 5 seconds or after significant changes
setInterval(saveCurrentState, 5000);

// Handle board state received from server
socket.onmessage = function(e) {
    const data = JSON.parse(e.data);
    
    if (data.type === 'board_state') {
        const img = new Image();
        img.onload = function() {
            ctx.drawImage(img, 0, 0);
        };
        img.src = data.image_data;
    }
    
    // Existing code...
};

This implementation provides the foundation for a collaborative whiteboard. You may need to fine-tune it based on your specific requirements, but it covers all the essential components needed for real-time collaboration.


✏️ Learnings added
Learnt from: varruunnn
URL: https://github.com/alphaonelabs/alphaonelabs-education-website/pull/141

Timestamp: 2025-03-22T03:31:29.468Z
Learning: To implement collaborative functionality in the whiteboard feature for the AlphaOne Labs education website, Django Channels with Redis is required for WebSocket communication between users.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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.

Feature Request: Add Interactive Whiteboard Functionality

2 participants