Pierwsza implementacja DocumentModel.{merge|update}. Zmiana sposobu generowania URLi...
[redakcja.git] / project / static / js / models.js
1 /*globals Editor fileId SplitView PanelContainerView EditorView*/
2 var documentsUrl = '/api/documents/';
3
4
5 Editor.Model = Editor.Object.extend({
6   synced: false,
7   data: null
8 });
9
10
11 Editor.ToolbarButtonsModel = Editor.Model.extend({
12   _className: 'Editor.ToolbarButtonsModel',
13   serverURL: '/api/toolbar/buttons',
14   buttons: {},
15   
16   init: function() {
17     this._super();
18   },
19   
20   load: function() {
21     if (!this.get('buttons').length) {
22       $.ajax({
23         url: this.serverURL,
24         dataType: 'json',
25         success: this.loadSucceeded.bind(this)
26       });
27     }
28   },
29   
30   loadSucceeded: function(data) {
31     this.set('buttons', data);
32   }
33 });
34
35
36 // Stany modelu:
37 //
38 // empty -> loading -> synced -> unsynced -> loading
39 //                           \
40 //                            -> dirty -> updating -> updated -> synced
41 //
42 Editor.XMLModel = Editor.Model.extend({
43   _className: 'Editor.XMLModel',
44   serverURL: null,
45   data: '',
46   state: 'empty',
47   
48   init: function(serverURL, revision) {
49     this._super();
50     this.set('state', 'empty');
51     this.set('revision', revision);
52     this.serverURL = serverURL;
53     this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
54     this.addObserver(this, 'data', this.dataChanged.bind(this));
55   },
56   
57   load: function() {
58     if (this.get('state') == 'empty') {
59       this.set('state', 'loading');
60       $.ajax({
61         url: this.serverURL,
62         dataType: 'text',
63         data: {revision: this.get('revision')},
64         success: this.loadingSucceeded.bind(this)
65       });
66       return true;
67     }
68     return false;
69   },
70   
71   update: function(message) {
72     if (this.get('state') == 'dirty') {
73       this.set('state', 'updating');
74       
75       var payload = {
76         contents: this.get('data'),
77         revision: this.get('revision')
78       };
79       if (message) {
80         payload.message = message;
81       }
82       
83       $.ajax({
84         url: this.serverURL,
85         type: 'put',
86         dataType: 'json',
87         data: payload,
88         success: this.updatingSucceeded.bind(this),
89         error: this.updatingFailed.bind(this)
90       });
91       return true;
92     }
93     return false;
94   },
95   
96   updatingSucceeded: function() {
97     if (this.get('state') != 'updating') {
98       alert('erroneous state:', this.get('state'));
99     }
100     this.set('state', 'updated');
101   },
102   
103   updatingFailed: function() {
104     if (this.get('state') != 'updating') {
105       alert('erroneous state:', this.get('state'));
106     }
107     this.set('state', 'dirty');
108   },
109   
110   // For debbuging
111   set: function(property, value) {
112     if (property == 'state') {
113       console.log(this.description(), ':', property, '=', value);
114     }
115     return this._super(property, value);
116   },
117   
118   dataChanged: function(property, value) {
119     if (this.get('state') == 'synced') {
120       this.set('state', 'dirty');
121     }
122   },
123   
124   loadingSucceeded: function(data) {
125     if (this.get('state') != 'loading') {
126       alert('erroneous state:', this.get('state'));
127     }
128     this.set('data', data);
129     this.set('state', 'synced');
130   },
131   
132   dispose: function() {
133     this.removeObserver(this);
134     this._super();
135   }
136 });
137
138
139 Editor.HTMLModel = Editor.Model.extend({
140   _className: 'Editor.HTMLModel',
141   serverURL: null,
142   data: '',
143   state: 'empty',
144   
145   init: function(serverURL, revision) {
146     this._super();
147     this.set('state', 'empty');
148     this.set('revision', revision);
149     this.serverURL = serverURL;
150   },
151   
152   load: function() {
153     if (this.get('state') == 'empty') {
154       this.set('state', 'loading');
155       $.ajax({
156         url: this.serverURL,
157         dataType: 'text',
158         data: {revision: this.get('revision')},
159         success: this.loadingSucceeded.bind(this)
160       });
161     }
162   },
163   
164   loadingSucceeded: function(data) {
165     if (this.get('state') != 'loading') {
166       alert('erroneous state:', this.get('state'));
167     }
168     this.set('data', data);
169     this.set('state', 'synced');
170   },
171
172   // For debbuging
173   set: function(property, value) {
174     if (property == 'state') {
175       console.log(this.description(), ':', property, '=', value);
176     }
177     return this._super(property, value);
178   }
179 });
180
181
182 Editor.DocumentModel = Editor.Model.extend({
183   _className: 'Editor.DocumentModel',
184   data: null, // name, text_url, user_revision, latest_shared_rev, parts_url, dc_url, size, merge_url
185   contentModels: {},
186   state: 'empty',
187   
188   init: function() {
189     this._super();
190     this.set('state', 'empty');
191     this.load();
192   },
193   
194   load: function() {
195     if (this.get('state') == 'empty') {
196       this.set('state', 'loading');
197       $.ajax({
198         cache: false,
199         url: documentsUrl + fileId,
200         dataType: 'json',
201         success: this.successfulLoad.bind(this)
202       });
203     }
204   },
205   
206   successfulLoad: function(data) {
207     this.set('data', data);
208     this.set('state', 'synced');
209     this.contentModels = {
210       'xml': new Editor.XMLModel(data.text_url, data.user_revision),
211       'html': new Editor.HTMLModel(data.html_url, data.user_revision)
212     };
213     for (var key in this.contentModels) {
214       this.contentModels[key].addObserver(this, 'state', this.contentModelStateChanged.bind(this));
215     }
216   },
217   
218   contentModelStateChanged: function(property, value, contentModel) {
219     if (value == 'dirty') {
220       this.set('state', 'dirty');
221       for (var key in this.contentModels) {
222         if (this.contentModels[key].guid() != contentModel.guid()) {
223           this.contentModels[key].set('state', 'unsynced');
224         }
225       }
226     } else if (value == 'updated') {
227       this.set('state', 'synced');
228       for (key in this.contentModels) {
229         if (this.contentModels[key].guid() == contentModel.guid()) {
230           this.contentModels[key].set('state', 'synced');
231         } else if (this.contentModels[key].get('state') == 'unsynced') {
232           this.contentModels[key].set('state', 'empty');
233         }
234       }
235     }
236   },
237   
238   saveDirtyContentModel: function(message) {
239     for (var key in this.contentModels) {
240       if (this.contentModels[key].get('state') == 'dirty') {
241         this.contentModels[key].update(message);
242         break;
243       }
244     }
245   },
246   
247   update: function() {
248     this.set('state', 'loading');
249     $.ajax({
250       url: this.data.merge_url,
251       dataType: 'json',
252       type: 'post',
253       data: {
254         type: 'update',
255         target_revision: this.data.user_revision
256       },
257       complete: this.updateCompleted.bind(this),
258       success: function(data) { this.set('updateData', data); }.bind(this)
259     });
260   },
261   
262   updateCompleted: function(xhr, textStatus) {
263     console.log(xhr.status, textStatus);
264     if (xhr.status == 200) { // Sukces
265       this.data.user_revision = this.get('updateData').revision;
266       for (var key in this.contentModels) {
267         this.contentModels[key].set('revision', this.data.user_revision);
268         this.contentModels[key].set('state', 'empty');
269       }
270     } else if (xhr.status == 202) { // Wygenerowano PullRequest
271     } else if (xhr.status == 204) { // Nic nie zmieniono
272     } else if (xhr.status == 409) { // Konflikt podczas operacji
273     } 
274     this.set('state', 'synced');
275     this.set('updateData', null);
276   },
277   
278   merge: function(message) {
279     this.set('state', 'loading');
280     $.ajax({
281       url: this.data.merge_url,
282       type: 'post',
283       dataType: 'json',
284       data: {
285         type: 'share',
286         target_revision: this.data.user_revision,
287         message: message
288       },
289       complete: this.mergeCompleted.bind(this),
290       success: function(data) { this.set('mergeData', data); }.bind(this)
291     });
292   },
293   
294   mergeCompleted: function(xhr, textStatus) {
295     console.log(xhr.status, textStatus);
296     if (xhr.status == 200) { // Sukces
297       this.data.user_revision = this.get('mergeData').revision;
298       for (var key in this.contentModels) {
299         this.contentModels[key].set('revision', this.data.user_revision);
300         this.contentModels[key].set('state', 'empty');
301       }
302     } else if (xhr.status == 202) { // Wygenerowano PullRequest (tutaj?)
303     } else if (xhr.status == 204) { // Nic nie zmieniono
304     } else if (xhr.status == 409) { // Konflikt podczas operacji
305     }
306     this.set('state', 'synced');
307     this.set('mergeData', null);
308   },
309   
310   // For debbuging
311   set: function(property, value) {
312     if (property == 'state') {
313       console.log(this.description(), ':', property, '=', value);
314     }
315     return this._super(property, value);
316   }
317 });
318
319
320 var leftPanelView, rightPanelContainer, doc;
321
322 $(function() {
323   doc = new Editor.DocumentModel();
324   var editor = new EditorView('#body-wrap', doc);
325   editor.freeze();
326   var splitView = new SplitView('#splitview', doc);
327   leftPanelView = new PanelContainerView('#left-panel-container', doc);
328   rightPanelContainer = new PanelContainerView('#right-panel-container', doc);
329 });