support reverting individual entities - makes conflict resolution work
authorRichard Fairhurst <richard@systemed.net>
Sun, 16 Jan 2011 16:55:35 +0000 (16:55 +0000)
committerRichard Fairhurst <richard@systemed.net>
Sun, 16 Jan 2011 16:55:35 +0000 (16:55 +0000)
net/systemeD/halcyon/MapPaint.as
net/systemeD/halcyon/connection/Connection.as
net/systemeD/halcyon/connection/XMLBaseConnection.as
net/systemeD/halcyon/connection/XMLConnection.as
net/systemeD/potlatch2/controller/ControllerState.as

index 412d4ea..f68725d 100644 (file)
@@ -204,6 +204,8 @@ package net.systemeD.halcyon {
                        if (!wayuis[way.id]) {
                                wayuis[way.id]=new WayUI(way,this);
                                way.addEventListener(Connection.WAY_DELETED, wayDeleted);
+                       } else {
+                               wayuis[way.id].redraw();
                        }
                        return wayuis[way.id];
                }
index ec8de9f..9930614 100644 (file)
@@ -6,6 +6,7 @@ package net.systemeD.halcyon.connection {
     import flash.events.Event;
        import net.systemeD.halcyon.Globals;
        import net.systemeD.halcyon.connection.actions.*;
+       import net.systemeD.halcyon.AttentionEvent;
        import net.systemeD.halcyon.MapEvent;
 
        public class Connection extends EventDispatcher {
@@ -484,16 +485,22 @@ package net.systemeD.halcyon.connection {
                                no: cancelUpload }));
                }
 
-               public function retryUpload():void { uploadChanges(); }
-               public function cancelUpload():void { return; }
+               public function retryUpload(e:Event=null):void { 
+                       removeEventListener(LOAD_COMPLETED,retryUpload);
+                       uploadChanges(); 
+               }
+               public function cancelUpload():void {
+                       return;
+               }
                public function retryUploadWithNewChangeset():void { 
                        // ** FIXME: we need to move the create-changeset-then-upload logic out of SaveDialog
                }
                public function goToEntity(entity:Entity):void { 
-                       dispatchEvent(new MapEvent(MapEvent.ATTENTION, { entity: entity }));
+                       dispatchEvent(new AttentionEvent(AttentionEvent.ATTENTION, entity));
                }
                public function revertBeforeUpload(entity:Entity):void { 
-                       // ** FIXME: implement a 'revert entity' method, then retry upload on successful download
+                       addEventListener(LOAD_COMPLETED,retryUpload);
+                       loadEntity(entity);
                }
                public function deleteBeforeUpload(entity:Entity):void {
             var a:CompositeUndoableAction = new CompositeUndoableAction("Delete refs");            
@@ -508,7 +515,7 @@ package net.systemeD.halcyon.connection {
                public function loadBbox(left:Number, right:Number,
                                                                top:Number, bottom:Number):void {
            }
-           
+           public function loadEntity(entity:Entity):void {}
            public function setAuthToken(id:Object):void {}
         public function setAccessToken(key:String, secret:String):void {}
            public function createChangeset(tags:Object):void {}
index ad6a391..16749f2 100644 (file)
@@ -19,8 +19,6 @@ package net.systemeD.halcyon.connection {
                }
                
         protected function loadedMap(event:Event):void {
-            dispatchEvent(new Event(LOAD_COMPLETED));
-
             var map:XML = new XML(URLLoader(event.target).data);
             var id:Number;
             var version:uint;
@@ -30,10 +28,15 @@ package net.systemeD.halcyon.connection {
             var node:Node, newNode:Node;
             var unusedNodes:Object={};
 
-                       var minlon:Number=map.bounds.@minlon;
-                       var maxlon:Number=map.bounds.@maxlon;
-                       var minlat:Number=map.bounds.@minlat;
-                       var maxlat:Number=map.bounds.@maxlat;
+                       var minlon:Number, maxlon:Number, minlat:Number, maxlat:Number;
+                       var singleEntityRequest:Boolean=true;
+                       if (map.bounds.@minlon.length()) {
+                               minlon=map.bounds.@minlon;
+                               maxlon=map.bounds.@maxlon;
+                               minlat=map.bounds.@minlat;
+                               maxlat=map.bounds.@maxlat;
+                               singleEntityRequest=false;
+                       }
 
             for each(var relData:XML in map.relation) {
                 id = Number(relData.@id);
@@ -97,7 +100,11 @@ package net.systemeD.halcyon.connection {
                                                   Number(nodeData.@uid),
                                                   nodeData.@timestamp);
                                
-                               if ( node == null || !node.loaded) {
+                               if ( singleEntityRequest ) {
+                                       // it's a revert request, so create/update the node
+                                       setOrUpdateNode(newNode, true);
+                               } else if ( node == null || !node.loaded) {
+                                       // the node didn't exist before, so create/update it
                                        newNode.parentsLoaded=newNode.within(minlon,maxlon,minlat,maxlat);
                                        setOrUpdateNode(newNode, true);
                                } else {
@@ -114,7 +121,7 @@ package net.systemeD.halcyon.connection {
                 timestamp = data.@timestamp;
 
                 var way:Way = getWay(id);
-                if ( way == null || !way.loaded ) {
+                if ( way == null || !way.loaded || singleEntityRequest) {
                     var nodes:Array = [];
                     for each(var nd:XML in data.nd) {
                                                var nodeid:Number=Number(nd.@ref)
@@ -135,6 +142,7 @@ package net.systemeD.halcyon.connection {
             }
             
             registerPOINodes();
+            dispatchEvent(new Event(LOAD_COMPLETED));
         }
         
         protected function registerPOINodes():void {
index 2a55629..e0fc6a1 100644 (file)
@@ -6,6 +6,7 @@ package net.systemeD.halcyon.connection {
        import flash.net.*;
     import org.iotashan.oauth.*;
 
+       import net.systemeD.halcyon.AttentionEvent;
        import net.systemeD.halcyon.MapEvent;
 
     /**
@@ -34,12 +35,22 @@ package net.systemeD.halcyon.connection {
             var mapRequest:URLRequest = new URLRequest(Connection.apiBaseURL+"map");
             mapRequest.data = mapVars;
 
-            var mapLoader:URLLoader = new URLLoader();
-            mapLoader.addEventListener(Event.COMPLETE, loadedMap);
-            mapLoader.addEventListener(IOErrorEvent.IO_ERROR, errorOnMapLoad);
-            mapLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, mapLoadStatus);
-            mapLoader.load(mapRequest);
-            dispatchEvent(new Event(LOAD_STARTED));
+            sendLoadRequest(mapRequest);
+               }
+
+               override public function loadEntity(entity:Entity):void {
+                       var url:String=Connection.apiBaseURL + entity.getType() + "/" + entity.id;
+                       if (entity is Relation || entity is Way) url+="/full";
+                       sendLoadRequest(new URLRequest(url));
+               }
+
+               private function sendLoadRequest(request:URLRequest):void {
+                       var mapLoader:URLLoader = new URLLoader();
+                       mapLoader.addEventListener(Event.COMPLETE, loadedMap);
+                       mapLoader.addEventListener(IOErrorEvent.IO_ERROR, errorOnMapLoad);
+                       mapLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, mapLoadStatus);
+                       mapLoader.load(request);
+                       dispatchEvent(new Event(LOAD_STARTED));
                }
 
         private function errorOnMapLoad(event:Event):void {
@@ -138,9 +149,12 @@ package net.systemeD.halcyon.connection {
                        closeActiveChangeset();
                }
                
-               private function changesetCloseComplete(event:Event):void { }
-               private function changesetCloseError(event:Event):void { }
-               // ** TODO: when we get little floating warnings, we can send a happy or sad one up
+               private function changesetCloseComplete(event:Event):void { 
+                       dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Changeset closed"));
+               }
+               private function changesetCloseError(event:Event):void { 
+                       dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Couldn't close changeset", 1));
+               }
 
         private function signedOAuthURL(url:String, method:String):String {
             // method should be PUT, GET, POST or DELETE
@@ -261,7 +275,7 @@ package net.systemeD.halcyon.connection {
                                case '409 Conflict':
                                        if (message.match(/changeset/i)) { throwChangesetError(message); return; }
                                        matches=message.match(/mismatch.+had: (\d+) of (\w+) (\d+)/i);
-                                       if (matches) { throwConflictError(findEntity(matches[3],matches[2]), Number(matches[1]), message); return; }
+                                       if (matches) { throwConflictError(findEntity(matches[2],matches[3]), Number(matches[1]), message); return; }
                                        break;
                                
                                case '410 Gone':
index 4d4e15a..dec3388 100644 (file)
@@ -9,6 +9,9 @@ package net.systemeD.potlatch2.controller {
        import net.systemeD.halcyon.Globals;
        import net.systemeD.potlatch2.save.SaveManager;
        import flash.ui.Keyboard;
+       import mx.controls.Alert;
+       import mx.events.CloseEvent;
+       
     /** Represents a particular state of the controller, such as "dragging a way" or "nothing selected". Key methods are 
     * processKeyboardEvent and processMouseEvent which take some action, and return a new state for the controller. 
     * 
@@ -66,6 +69,7 @@ package net.systemeD.potlatch2.controller {
                                case 83:        SaveManager.saveChanges(); break;                                                                               // S - save
                                case 84:        controller.tagViewer.togglePanel(); return null;                                                // T - toggle tags panel
                                case 90:        MainUndoStack.getGlobalStack().undo(); return null;                                             // Z - undo
+                               case Keyboard.ESCAPE:   revertSelection(); break;                                                                       // ESC - revert to server version
                                case Keyboard.NUMPAD_ADD:                                                                                                                       // + - add tag
                                case 187:       controller.tagViewer.selectAdvancedPanel();                                                             //   |
                                                        controller.tagViewer.addNewTag(); return null;                                                  //   |
@@ -189,6 +193,18 @@ package net.systemeD.potlatch2.controller {
                        controller.updateSelectionUI();
                }
 
+               /** Revert all selected items to previously saved state, via a dialog box. */
+               protected function revertSelection():void {
+                       if (selectCount==0) return;
+                       Alert.show("Revert selected items to the last saved version, discarding your changes?","Are you sure?",Alert.YES | Alert.CANCEL,null,revertHandler);
+               }
+               protected function revertHandler(event:CloseEvent):void {
+                       if (event.detail==Alert.CANCEL) return;
+                       for each (var item:Entity in _selection) {
+                               controller.connection.loadEntity(item);
+                       }
+               }
+
                // Selection getters
 
                public function get selectCount():uint {