@@ -25,9 +25,9 @@ import 'package:uuid/uuid.dart';
2525class WorkspaceProvider extends StateHandler {
2626 late SupabaseService _supabaseService;
2727 late DashboardProvider _dashboardProvider;
28-
28+
2929 // Initialize _currentWorkspace as null, it will be set by setWorkspace
30- WorkspaceModel ? _currentWorkspace;
30+ WorkspaceModel ? _currentWorkspace;
3131
3232 WorkspaceProvider (this ._supabaseService, this ._dashboardProvider) : super () {
3333 _myId = _supabaseService.supabase.auth.currentUser? .id ?? const Uuid ().v4 ();
@@ -48,11 +48,11 @@ class WorkspaceProvider extends StateHandler {
4848 String ? _currentlySelectedObjectId;
4949 InteractionMode _interactionMode = InteractionMode .none;
5050 Offset ? _panStartPoint;
51- Offset _cursorPosition = const Offset (0 , 0 );
52- static const double _defaultShapeSize = 100.0 ;
53- static const double _handleRadius = 8.0 ;
51+ Offset _cursorPosition = const Offset (0 , 0 ); // Now stores canvas coordinates
52+ static const double _defaultShapeSize = 100.0 ; // In canvas units
53+ static const double _handleRadius = 8.0 ; // In canvas units
5454 Color _currentWorkspaceColor = scaffoldColor;
55-
55+
5656 TextEditingController _workspaceNameController = TextEditingController (
5757 text: 'Workspace Name' ,
5858 );
@@ -92,15 +92,15 @@ class WorkspaceProvider extends StateHandler {
9292 // Handle case where workspace isn't found (e.g., navigate back, show error)
9393 return ;
9494 }
95-
95+
9696 _workspaceNameController.text = _currentWorkspace! .name;
9797 _canvasObjects.clear (); // Clear existing objects when changing workspace
9898 _userCursors.clear (); // Clear cursors too
9999 _currentlySelectedObjectId = null ; // Clear selected object
100100
101101 await _fetchCanvasObjects (); // Fetch objects for the new workspace
102102 _setupRealtimeChannel (_currentWorkspace! .id); // Set up realtime for new workspace
103-
103+
104104 notifyListeners ();
105105 }
106106
@@ -118,6 +118,8 @@ class WorkspaceProvider extends StateHandler {
118118 .order ('created_at' , ascending: true );
119119
120120 for (final canvasObjectData in initialData) {
121+ // Ensure that `canvasObjectData['object']` is directly the JSONB content
122+ // and not wrapped in another object if not intended.
121123 final canvasObject = CanvasObject .fromJson (canvasObjectData['object' ]);
122124 _canvasObjects[canvasObject.id] = canvasObject;
123125 }
@@ -155,6 +157,7 @@ class WorkspaceProvider extends StateHandler {
155157 }
156158
157159 // --- Realtime Sync ---
160+ // Now expects canvas coordinates for cursorPosition
158161 Future <void > syncCanvasObject (Offset cursorPosition) {
159162 final myCursor = UserCursor (position: cursorPosition, id: _myId);
160163 if (_currentWorkspace == null || _canvasChannel == null ) return Future .value ();
@@ -163,8 +166,8 @@ class WorkspaceProvider extends StateHandler {
163166 event: Constants .broadcastEventName,
164167 payload: {
165168 'cursor' : myCursor.toJson (),
166- if (_currentlySelectedObjectId != null )
167- 'object' : _canvasObjects[_currentlySelectedObjectId] ? .toJson (),
169+ if (_currentlySelectedObjectId != null && _canvasObjects. containsKey (_currentlySelectedObjectId ! )) // Ensure object exists
170+ 'object' : _canvasObjects[_currentlySelectedObjectId! ] ! .toJson (),
168171 'workspace_id' : _currentWorkspace! .id, // Include workspace_id in broadcast
169172 },
170173 );
@@ -196,25 +199,26 @@ class WorkspaceProvider extends StateHandler {
196199 }
197200 }
198201
202+ // Modified onPanEnd to ensure object is saved when interaction stops
199203 void onPanEnd (DragEndDetails details) async {
200204 if (_currentlySelectedObjectId != null ) {
201- syncCanvasObject (_cursorPosition);
205+ syncCanvasObject (_cursorPosition); // Sync final position
206+
207+ final drawnObjectId = _currentlySelectedObjectId;
208+ if (drawnObjectId != null && _currentWorkspace != null ) {
209+ // Save the current object state to the database
210+ await _supabaseService.supabase.from ('canvas_objects' ).upsert ({
211+ 'id' : drawnObjectId,
212+ 'object' : _canvasObjects[drawnObjectId]! .toJson (), // Ensure this toJson() matches your DB schema (jsonb)
213+ 'workspace_id' : _currentWorkspace! .id,
214+ });
215+ print ('Canvas object ${drawnObjectId } upserted to DB.' );
216+ }
202217 }
203218
204- final drawnObjectId = _currentlySelectedObjectId;
205-
206219 _panStartPoint = null ;
207220 _interactionMode = InteractionMode .none;
208221 notifyListeners ();
209-
210- if (drawnObjectId == null || _currentWorkspace == null ) {
211- return ;
212- }
213- await _supabaseService.supabase.from ('canvas_objects' ).upsert ({
214- 'id' : drawnObjectId,
215- 'object' : _canvasObjects[drawnObjectId]! .toJson (),
216- 'workspace_id' : _currentWorkspace! .id,
217- });
218222 }
219223
220224 // --- UI/Interaction Logic ---
@@ -285,7 +289,8 @@ class WorkspaceProvider extends StateHandler {
285289 notifyListeners ();
286290 }
287291
288- void addNewNode (DragDownDetails details) {
292+ // Expects details.globalPosition to be in canvas coordinates
293+ void addNewNode (DragDownDetails details) async { // Make async to save immediately
289294 if (_currentWorkspace == null ) {
290295 print ("Cannot add node: No workspace selected." );
291296 return ;
@@ -329,60 +334,86 @@ class WorkspaceProvider extends StateHandler {
329334 if (newObject != null ) {
330335 _canvasObjects[newObject.id] = newObject;
331336 _currentlySelectedObjectId = newObject.id;
332- _interactionMode = InteractionMode .moving;
337+ _interactionMode = InteractionMode .moving; // Set to moving immediately after creation
333338 notifyListeners ();
339+
340+ // Immediately save the newly created object to the database
341+ try {
342+ await _supabaseService.supabase.from ('canvas_objects' ).upsert ({
343+ 'id' : newObject.id,
344+ 'object' : newObject.toJson (),
345+ 'workspace_id' : _currentWorkspace! .id,
346+ });
347+ print ('New canvas object ${newObject .id } inserted into DB.' );
348+ } catch (e) {
349+ print ('Error inserting new canvas object: $e ' );
350+ }
334351 }
335352 }
336353
354+ // Expects details.globalPosition and details.delta to be in canvas coordinates
337355 void onPanDown (DragDownDetails details) {
338- _cursorPosition = details.globalPosition;
339- _panStartPoint = details.globalPosition;
356+ _cursorPosition = details.globalPosition; // This is now in canvas coordinates
357+ _panStartPoint = details.globalPosition; // This is now in canvas coordinates
340358
341359 _currentlySelectedObjectId = null ;
342360 _interactionMode = InteractionMode .none;
343361 notifyListeners ();
344362
345363 if (_currentMode == DrawMode .pointer) {
346- if (_currentlySelectedObjectId != null ) {
347- final selectedObject = _canvasObjects[_currentlySelectedObjectId ! ] ! ;
348- final rect = selectedObject .getBounds ();
364+ // Prioritize handle interaction check
365+ for ( final canvasObject in _canvasObjects.values. toList ().reversed) {
366+ final rect = canvasObject .getBounds ();
349367
368+ // Check for handle interaction. Handle radius also in canvas units.
350369 if ((details.globalPosition - rect.topLeft).distance < _handleRadius) {
370+ _currentlySelectedObjectId = canvasObject.id;
351371 _interactionMode = InteractionMode .resizingTopLeft;
372+ notifyListeners ();
373+ return ; // Exit after finding a handle
352374 } else if ((details.globalPosition - rect.topRight).distance < _handleRadius) {
375+ _currentlySelectedObjectId = canvasObject.id;
353376 _interactionMode = InteractionMode .resizingTopRight;
377+ notifyListeners ();
378+ return ;
354379 } else if ((details.globalPosition - rect.bottomLeft).distance < _handleRadius) {
380+ _currentlySelectedObjectId = canvasObject.id;
355381 _interactionMode = InteractionMode .resizingBottomLeft;
382+ notifyListeners ();
383+ return ;
356384 } else if ((details.globalPosition - rect.bottomRight).distance < _handleRadius) {
385+ _currentlySelectedObjectId = canvasObject.id;
357386 _interactionMode = InteractionMode .resizingBottomRight;
387+ notifyListeners ();
388+ return ;
358389 }
359390 }
360391
361- if (_interactionMode == InteractionMode .none) {
362- for (final canvasObject in _canvasObjects.values.toList ().reversed) {
363- if (canvasObject.intersectsWith (details.globalPosition)) {
364- _currentlySelectedObjectId = canvasObject.id;
365- _interactionMode = InteractionMode .moving;
366- notifyListeners ();
367- break ;
368- }
392+ // If no handle interaction, check if we're clicking on an object to move it
393+ for (final canvasObject in _canvasObjects.values.toList ().reversed) {
394+ if (canvasObject.intersectsWith (details.globalPosition)) { // details.globalPosition is now canvas coordinate
395+ _currentlySelectedObjectId = canvasObject.id;
396+ _interactionMode = InteractionMode .moving;
397+ notifyListeners ();
398+ break ; // Exit after selecting an object
369399 }
370400 }
371401 } else {
372- addNewNode (details);
402+ addNewNode (details); // Call addNewNode if not in pointer mode
373403 }
374404 }
375405
406+ // Expects details.globalPosition and details.delta to be in canvas coordinates
376407 void onPanUpdate (DragUpdateDetails details) {
377- _cursorPosition = details.globalPosition;
408+ _cursorPosition = details.globalPosition; // This is now in canvas coordinates
378409 if (_currentlySelectedObjectId == null ) return ;
379410
380411 final currentObject = _canvasObjects[_currentlySelectedObjectId! ];
381412 if (currentObject == null ) return ;
382413
383414 switch (_interactionMode) {
384415 case InteractionMode .moving:
385- _canvasObjects[_currentlySelectedObjectId! ] = currentObject.move (details.delta);
416+ _canvasObjects[_currentlySelectedObjectId! ] = currentObject.move (details.delta); // delta is already in canvas units
386417 break ;
387418 case InteractionMode .resizingTopLeft:
388419 final newTopLeft = currentObject.getBounds ().topLeft + details.delta;
0 commit comments