Skip to content

Commit f71bbea

Browse files
JacobCoffeeclaude
andcommitted
feat(layer): add WebSocket handlers for layer actions
Add real-time layer management via WebSocket: - _handle_get_elements: fetch all elements for layer list - _handle_layer_action: route layer operations - toggle_visibility, toggle_lock - bring_to_front, send_to_back - move_forward, move_backward - delete - Update _element_to_dict to include z_index, visible, locked Broadcasts updates to all connected clients for real-time sync. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 58ff32b commit f71bbea

File tree

1 file changed

+122
-0
lines changed

1 file changed

+122
-0
lines changed

src/scribbl_py/realtime/handler.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ async def _handle_message(
184184
MessageType.STROKE_START.value: self._handle_stroke_start,
185185
MessageType.STROKE_CONTINUE.value: self._handle_stroke_continue,
186186
MessageType.STROKE_END.value: self._handle_stroke_end,
187+
"get_elements": self._handle_get_elements,
188+
"layer_action": self._handle_layer_action,
187189
}
188190

189191
handler = handlers.get(msg_type)
@@ -274,6 +276,9 @@ def _element_to_dict(self, element: Any) -> dict[str, Any]:
274276
"stroke_width": response.style.stroke_width,
275277
"opacity": response.style.opacity,
276278
},
279+
"z_index": element.z_index,
280+
"visible": element.visible,
281+
"locked": element.locked,
277282
"created_at": response.created_at.isoformat(),
278283
}
279284

@@ -852,6 +857,123 @@ async def _handle_stroke_end(
852857
)
853858
await self._send_error(socket, "stroke_creation_failed", "Failed to create stroke")
854859

860+
async def _handle_get_elements(
861+
self,
862+
socket: WebSocket,
863+
user_data: dict[str, Any],
864+
message: dict[str, Any],
865+
) -> None:
866+
"""Handle request for elements list.
867+
868+
Args:
869+
socket: The WebSocket connection.
870+
user_data: The user's connection data.
871+
message: The message (unused).
872+
"""
873+
canvas_id = user_data["canvas_id"]
874+
875+
elements = await self._service.list_elements(canvas_id)
876+
877+
await socket.send_json({
878+
"type": "elements_list",
879+
"elements": [self._element_to_dict(e) for e in elements],
880+
})
881+
882+
async def _handle_layer_action(
883+
self,
884+
socket: WebSocket,
885+
user_data: dict[str, Any],
886+
message: dict[str, Any],
887+
) -> None:
888+
"""Handle layer management actions.
889+
890+
Args:
891+
socket: The WebSocket connection.
892+
user_data: The user's connection data.
893+
message: The layer action message.
894+
"""
895+
canvas_id = user_data["canvas_id"]
896+
user_id = user_data.get("user_id", "system")
897+
898+
action = message.get("action")
899+
element_id_str = message.get("element_id")
900+
901+
if not element_id_str:
902+
await self._send_error(socket, "missing_element_id", "Element ID required")
903+
return
904+
905+
try:
906+
element_id = UUID(element_id_str)
907+
except ValueError:
908+
await self._send_error(socket, "invalid_element_id", "Invalid element ID format")
909+
return
910+
911+
try:
912+
if action == "toggle_visibility":
913+
element = await self._service.toggle_visibility(canvas_id, element_id, user_id)
914+
updates = {"visible": element.visible}
915+
916+
elif action == "toggle_lock":
917+
element = await self._service.toggle_lock(canvas_id, element_id, user_id)
918+
updates = {"locked": element.locked}
919+
920+
elif action == "bring_to_front":
921+
element = await self._service.bring_to_front(canvas_id, element_id, user_id)
922+
updates = {"z_index": element.z_index}
923+
924+
elif action == "send_to_back":
925+
element = await self._service.send_to_back(canvas_id, element_id, user_id)
926+
updates = {"z_index": element.z_index}
927+
928+
elif action == "move_forward":
929+
element = await self._service.move_forward(canvas_id, element_id, user_id)
930+
updates = {"z_index": element.z_index}
931+
932+
elif action == "move_backward":
933+
element = await self._service.move_backward(canvas_id, element_id, user_id)
934+
updates = {"z_index": element.z_index}
935+
936+
elif action == "delete":
937+
deleted = await self._service.delete_element(canvas_id, element_id, user_id)
938+
if deleted:
939+
await self._manager.broadcast(canvas_id, {
940+
"type": "element_deleted",
941+
"element_id": str(element_id),
942+
})
943+
logger.debug(
944+
"Layer deleted",
945+
element_id=str(element_id),
946+
canvas_id=str(canvas_id),
947+
)
948+
return
949+
950+
else:
951+
await self._send_error(socket, "unknown_action", f"Unknown layer action: {action}")
952+
return
953+
954+
# Broadcast the update to all clients
955+
await self._manager.broadcast(canvas_id, {
956+
"type": "element_updated",
957+
"element_id": str(element_id),
958+
"updates": updates,
959+
})
960+
961+
logger.debug(
962+
"Layer action completed",
963+
action=action,
964+
element_id=str(element_id),
965+
canvas_id=str(canvas_id),
966+
)
967+
968+
except Exception:
969+
logger.exception(
970+
"Error handling layer action",
971+
action=action,
972+
element_id=str(element_id),
973+
canvas_id=str(canvas_id),
974+
)
975+
await self._send_error(socket, "layer_action_failed", f"Failed to execute layer action: {action}")
976+
855977
async def _send_error(
856978
self,
857979
socket: WebSocket,

0 commit comments

Comments
 (0)