feat(whiteboard) add interactive whiteboard functionality , Freehand …#141
Conversation
…drawing with various pen sizes and colors ,Shape tools (circles, squares, lines),text input and annotation
WalkthroughThis 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
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
Assessment against linked issues
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
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 addingaria-labelortitleattributes 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 adjustsglobalAlphafor 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 aFileReaderis 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
TheredrawBackground()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
ThedrawArrow()function uses trigonometric calculations to draw an arrow with a proper head. While the current approach works, the arrowhead is drawn using twostroke()calls. Consider refactoring this section—possibly by streamlining the drawing of the arrowhead or usingfill()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) usingrestoreSnapshot(). 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
📒 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
ThetakeSnapshot()andrestoreSnapshot()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 invokingdrawArrow()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.
|
@A1L13N I will fix linting after some time. |
|
@A1L13N i have fixed all tests and linting too ! |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 11225 🏁 Script executed: Length of output: 19366 🏁 Script executed: Length of output: 1231 1. Install required dependenciespip install channels[daphne] channels-redis2. 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 consumerCreate a new file 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 configurationModify 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 routingCreate a new file 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 viewdef 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 similarly9. Add state synchronizationTo 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
|
…drawing with various pen sizes and colors ,Shape tools (circles, squares, lines),text input and annotation
CLOSES #130

Summary by CodeRabbit