Expand the title, and center and pad the loading information
[potlatch2.git] / net / systemeD / potlatch2 / history / HistoryDialog.mxml
1 <?xml version="1.0" encoding="utf-8"?>
2 <s:TitleWindow
3         xmlns:fx="http://ns.adobe.com/mxml/2009"
4         xmlns:mx="library://ns.adobe.com/flex/mx"
5         xmlns:s="library://ns.adobe.com/flex/spark"
6         xmlns:help="net.systemeD.potlatch2.help.*"
7         title="History for {entity.getType()} {entity.id}"
8         width="600" height="400">
9
10   <s:layout>
11     <s:VerticalLayout />
12   </s:layout>
13
14   <s:VGroup width="100%" height="100%">
15     <s:HGroup width="100%" horizontalAlign="center" verticalAlign="middle" paddingTop="5">
16       <s:RichText text="Loading data..." visible="{ entityStates.length == 0 }" />
17     </s:HGroup>
18       <s:DataGrid dataProvider="{entityStates}" width="100%" height="100%" enabled="{ entityStates != null }">
19         <s:columns>
20           <s:ArrayList>
21             <s:GridColumn editable="false" dataField="version" headerText="version" width="50">
22               <s:itemRenderer>
23                 <fx:Component>
24                   <s:DefaultGridItemRenderer textAlign="center" />
25                 </fx:Component>
26               </s:itemRenderer>
27             </s:GridColumn>
28             <s:GridColumn editable="false" dataField="timestamp" headerText="timestamp" />
29             <s:GridColumn editable="false" dataField="user" headerText="username" />
30             <s:GridColumn editable="false">
31               <s:itemRenderer>
32                 <fx:Component>
33                   <s:GridItemRenderer>
34                     <s:VGroup horizontalAlign="center" verticalAlign="middle" width="100%" height="100%">
35                       <s:Button label="Contact User"
36                           click="parentDocument.message(data)"/>
37                     </s:VGroup>
38                   </s:GridItemRenderer>
39                 </fx:Component>
40               </s:itemRenderer>
41             </s:GridColumn>
42           </s:ArrayList>
43         </s:columns>
44       </s:DataGrid>
45   </s:VGroup>
46
47
48   <s:controlBarContent>
49     <s:Button label="Revert" enabled="false" styleName="titleWindowButton" />
50     <s:Spacer width="100%"/>
51     <s:Button label="More Details..." enabled="{entity.id >= 0}" click="openEntityPage()" styleName="titleWindowButton" />
52     <s:Button label="Cancel" click="PopUpManager.removePopUp(this);" styleName="titleWindowButton" />
53   </s:controlBarContent>
54
55   <fx:Script><![CDATA[
56
57     import mx.managers.PopUpManager;
58     import mx.core.Application;
59     import mx.core.FlexGlobals;
60     import mx.events.CloseEvent;
61     import flash.events.Event;
62     import com.adobe.utils.ArrayUtil;
63
64     import net.systemeD.halcyon.connection.*
65
66     // This is the entity that we're requesting the history for.
67     [Bindable]
68     private var entity:Entity;
69
70     // These are the various states that the entity as been in - so is a list
71     // of Nodes (all with the same id) or Ways etc
72     [Bindable]
73     private var entityStates:ArrayList = new ArrayList();
74
75     // store intermediate states for ways
76     private var wayStates:Array; // an array of ways
77     private var wayNodeStates:Array; // an array of arrays of nodes
78
79     // the number of outstanding asynchronous node history requests,
80     // so we know when all have been fetched
81     private var pendingNodeFetches:uint;
82
83     public function init(e:Entity):void {
84         if (e == null) {return;}
85
86         PopUpManager.addPopUp(this, Application(FlexGlobals.topLevelApplication), true);
87         PopUpManager.centerPopUp(this);
88         this.addEventListener(CloseEvent.CLOSE, historyDialog_close);
89
90         entity = e;
91         fetchHistory();
92     }
93
94     private function historyDialog_close(evt:CloseEvent):void {
95         PopUpManager.removePopUp(this);
96     }
97
98     /** Open up a new browser page showing OSM's view of the current entity. */
99     private function openEntityPage():void {
100         if (entity != null && entity.id >= 0) {
101             // This is slightly hard-coded, but not drastically. The ../s could be changed for string manipulation of the apiBase
102             var urlBase:String = entity.connection.apiBase + '../../browse/';
103             navigateToURL(new URLRequest(urlBase+entity.getType()+'/'+entity.id), "potlatch_browse");
104         }
105     }
106
107     private function fetchHistory():void {
108         if (entity is Node) {
109             entity.connection.fetchHistory(entity, processNode);
110         } else if (entity is Way) {
111             entity.connection.fetchHistory(entity, processWay);
112         } else {
113             // not implemented
114         }
115     }
116
117     private function processNode(results:Array):void {
118         // Simply copy the nodes into the states array
119         // todo sorting or somesuch
120         entityStates.source = results.reverse();
121     }
122
123     private function processWay(results:Array):void {
124         // This is much more complicated that nodes.
125         // In potlatch(2) we show the user the number of different states, bearing in mind
126         // node movements (and tag changes?).
127
128         wayStates = results;
129
130         // figure out the list of nodes that have ever been involved in the way, and fetch them.
131         // pendingNode will store each one, and trigger an event when they are all downloaded.
132         wayNodeStates = [];
133         addEventListener("pendingNodesAllFetched", processWayStates);
134
135         var nodes:Object = {};
136         var count:uint = 0;
137
138         for each(var oldWay:Way in results) {
139             for (var i:uint = 0; i< oldWay.length; i++) {
140                 var node:Node = oldWay.getNode(i);
141                 if(!nodes[node.id]) {
142                     nodes[node.id] = node;
143                     count++;
144                 }
145             }
146         }
147
148         pendingNodeFetches = count;
149
150         for each (var n:Node in nodes) {
151             entity.connection.fetchHistory(n, pendingNode);
152         }
153     }
154
155     // Callback for fetching a node history as part of a way; when there's no outstanding
156     // nodes remaining this will trigger an event.
157     private function pendingNode(results:Array):void {
158         wayNodeStates.push(results)
159         pendingNodeFetches--;
160         trace("fetched node "+results[0].id+" , "+pendingNodeFetches+" more to go");
161         if (pendingNodeFetches == 0) {
162             dispatchEvent(new Event("pendingNodesAllFetched"));
163         }
164     }
165
166     private function processWayStates(e:Event):void {
167         // we now have all the node histories for
168         // for each node that has ever been part of the way.
169
170         // Build a list of every timestamp of interest, along with who
171         // the person was that triggered that timestamp (either from a way version
172         // change, or from changing a node.
173         var revdates:Array = [];
174         var revusers:Object = {};
175
176         for each (var way:Way in wayStates) {
177             revdates.push(way.timestamp);
178             revusers[way.timestamp] = way.user;
179         }
180
181         for each (var nodeStates:Array in wayNodeStates) {
182             for each (var node:Node in nodeStates) {
183                 revdates.push(node.timestamp);
184                 revusers[node.timestamp] = node.user;
185             }
186         }
187
188         // sort the dates and remove duplicates and those before the first version of the way
189
190         revdates = revdates.sort();
191         revdates = ArrayUtil.createUniqueCopy(revdates); // (corelib) de-duplicates
192         revdates = revdates.filter(function(e:*, i:int, arr:Array):Boolean { return e >= wayStates[0].timestamp});
193
194         var version:int = 1;
195         var subversion:int = 0;
196         var es:Array = []; // entityStates
197
198         for each (var revdate:String in revdates) {
199           var entitystate:Object = {};
200
201           var w:Way = getEntityAtDate(wayStates, revdate) as Way;
202
203           if (w.version == version) {
204               subversion++;
205           } else {
206               version = w.version;
207               subversion = 1;
208           }
209
210           //for (i = 0, i < w.length; i++) {
211           //    var generalNode:Node = w.getNode(i);
212           //    var specificNode:Node = getEntityAtDate(wayNodeStates[generalNode.id], revdate);
213           //    where was I going with this? Oh, yes, it'll be needed for building the object to revert to.
214           //}
215
216           entitystate.version = String(version) + "." + String(subversion);
217           entitystate.timestamp = revdate;
218           entitystate.user = revusers[revdate];
219           es.push(entitystate)
220         }
221
222         entityStates.source = es.reverse();
223     }
224
225     // given a list of entities sorted with oldest first, find the last version before that date.
226     private function getEntityAtDate(list:Array, date:String):Entity {
227         trace("find for date : "+date);
228         for(var i:int = list.length-1; i >= 0; i--) {
229             var entity:Entity = list[i];
230             trace (entity.timestamp + " : " + date);
231             if (entity.timestamp <= date) {
232                 trace("returning version " + entity.version);
233                 return entity;
234             }
235         }
236         trace("ERROR");
237         return null;
238     }
239
240     public function message(entity:Entity):void {
241         if (entity.user != null) {
242             var urlBase:String = entity.connection.apiBase + '../../message/new/';
243             navigateToURL(new URLRequest(urlBase+entity.user), "potlatch_message");
244         }
245     }
246     ]]>
247   </fx:Script>
248
249
250 </s:TitleWindow>
251
252