Merge branch 'master' of stigma.nowoczesnapolska.org.pl:platforma
[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.ImageGalleryModel = Editor.Model.extend({
183   _className: 'Editor.ImageGalleryModel',
184   serverURL: null,  
185   state: 'empty',
186
187   init: function(serverURL) {
188     this._super();
189     this.set('state', 'empty');
190     this.serverURL = serverURL;
191     // olewać data    
192     this.pages = [];
193   },
194
195   load: function() {
196     if (this.get('state') == 'empty') {
197       this.set('state', 'loading');
198       $.ajax({
199         url: this.serverURL,
200         dataType: 'json',
201         success: this.loadingSucceeded.bind(this)
202       });
203     }
204   },  
205
206   loadingSucceeded: function(data) {
207     if (this.get('state') != 'loading') {
208       alert('erroneous state:', this.get('state'));
209     }
210
211     this.set('pages', data[0].pages);
212     this.set('state', 'synced');
213   },
214
215   set: function(property, value) {
216     if (property == 'state') {
217       console.log(this.description(), ':', property, '=', value);
218     }
219     return this._super(property, value);
220   }
221 });
222
223
224 Editor.DocumentModel = Editor.Model.extend({
225   _className: 'Editor.DocumentModel',
226   data: null, // name, text_url, user_revision, latest_shared_rev, parts_url, dc_url, size, merge_url
227   contentModels: {},
228   state: 'empty',
229   
230   init: function() {
231     this._super();
232     this.set('state', 'empty');
233     this.load();
234   },
235   
236   load: function() {
237     if (this.get('state') == 'empty') {
238       this.set('state', 'loading');
239       $.ajax({
240         cache: false,
241         url: documentsUrl + fileId,
242         dataType: 'json',
243         success: this.successfulLoad.bind(this)
244       });
245     }
246   },
247   
248   successfulLoad: function(data) {
249     this.set('data', data);
250     this.set('state', 'synced');
251     this.contentModels = {
252       'xml': new Editor.XMLModel(data.text_url, data.user_revision),
253       'html': new Editor.HTMLModel(data.html_url, data.user_revision),
254       'gallery': new Editor.ImageGalleryModel(data.gallery_url)
255     };
256     for (var key in this.contentModels) {
257       this.contentModels[key].addObserver(this, 'state', this.contentModelStateChanged.bind(this));
258     }
259   },
260   
261   contentModelStateChanged: function(property, value, contentModel) {
262     if (value == 'dirty') {
263       this.set('state', 'dirty');
264       for (var key in this.contentModels) {
265         if (this.contentModels[key].guid() != contentModel.guid()) {
266           this.contentModels[key].set('state', 'unsynced');
267         }
268       }
269     } else if (value == 'updated') {
270       this.set('state', 'synced');
271       for (key in this.contentModels) {
272         if (this.contentModels[key].guid() == contentModel.guid()) {
273           this.contentModels[key].set('state', 'synced');
274         } else if (this.contentModels[key].get('state') == 'unsynced') {
275           this.contentModels[key].set('state', 'empty');
276         }
277       }
278     }
279   },
280   
281   saveDirtyContentModel: function(message) {
282     for (var key in this.contentModels) {
283       if (this.contentModels[key].get('state') == 'dirty') {
284         this.contentModels[key].update(message);
285         break;
286       }
287     }
288   },
289   
290   update: function() {
291     this.set('state', 'loading');
292     $.ajax({
293       url: this.data.merge_url,
294       dataType: 'json',
295       type: 'post',
296       data: {
297         type: 'update',
298         target_revision: this.data.user_revision
299       },
300       complete: this.updateCompleted.bind(this),
301       success: function(data) { this.set('updateData', data); }.bind(this)
302     });
303   },
304   
305   updateCompleted: function(xhr, textStatus) {
306     console.log(xhr.status, textStatus);
307     if (xhr.status == 200) { // Sukces
308       this.data.user_revision = this.get('updateData').revision;
309       for (var key in this.contentModels) {
310         this.contentModels[key].set('revision', this.data.user_revision);
311         this.contentModels[key].set('state', 'empty');
312       }
313     } else if (xhr.status == 202) { // Wygenerowano PullRequest
314     } else if (xhr.status == 204) { // Nic nie zmieniono
315     } else if (xhr.status == 409) { // Konflikt podczas operacji
316     } 
317     this.set('state', 'synced');
318     this.set('updateData', null);
319   },
320   
321   merge: function(message) {
322     this.set('state', 'loading');
323     $.ajax({
324       url: this.data.merge_url,
325       type: 'post',
326       dataType: 'json',
327       data: {
328         type: 'share',
329         target_revision: this.data.user_revision,
330         message: message
331       },
332       complete: this.mergeCompleted.bind(this),
333       success: function(data) { this.set('mergeData', data); }.bind(this)
334     });
335   },
336   
337   mergeCompleted: function(xhr, textStatus) {
338     console.log(xhr.status, textStatus);
339     if (xhr.status == 200) { // Sukces
340       this.data.user_revision = this.get('mergeData').revision;
341       for (var key in this.contentModels) {
342         this.contentModels[key].set('revision', this.data.user_revision);
343         this.contentModels[key].set('state', 'empty');
344       }
345     } else if (xhr.status == 202) { // Wygenerowano PullRequest (tutaj?)
346     } else if (xhr.status == 204) { // Nic nie zmieniono
347     } else if (xhr.status == 409) { // Konflikt podczas operacji
348     }
349     this.set('state', 'synced');
350     this.set('mergeData', null);
351   },
352   
353   // For debbuging
354   set: function(property, value) {
355     if (property == 'state') {
356       console.log(this.description(), ':', property, '=', value);
357     }
358     return this._super(property, value);
359   }
360 });
361
362
363 var leftPanelView, rightPanelContainer, doc;
364
365 $(function() {
366   doc = new Editor.DocumentModel();
367   var editor = new EditorView('#body-wrap', doc);
368   editor.freeze();
369   var splitView = new SplitView('#splitview', doc);
370   leftPanelView = new PanelContainerView('#left-panel-container', doc);
371   rightPanelContainer = new PanelContainerView('#right-panel-container', doc);
372 });