Lepsze wiadomości.
[redakcja.git] / project / static / js / models.js
1 /*globals Editor fileId SplitView PanelContainerView EditorView FlashView messageCenter*/
2 Editor.Model = Editor.Object.extend({
3   synced: false,
4   data: null
5 });
6
7
8 Editor.ToolbarButtonsModel = Editor.Model.extend({
9   className: 'Editor.ToolbarButtonsModel',  
10   buttons: {},
11   
12   init: function() {
13     this._super();
14   },
15   
16   load: function() {
17     if (!this.get('buttons').length) {
18       $.ajax({
19         url: toolbarUrl,
20         dataType: 'json',
21         success: this.loadSucceeded.bind(this)
22       });
23     }
24   },
25   
26   loadSucceeded: function(data) {
27     this.set('buttons', data);
28   }
29 });
30
31
32 // Stany modelu:
33 //
34 //                  -> error -> loading
35 //                 /
36 // empty -> loading -> synced -> unsynced -> loading
37 //                           \
38 //                            -> dirty -> updating -> updated -> synced
39 //
40 Editor.XMLModel = Editor.Model.extend({
41   _className: 'Editor.XMLModel',
42   serverURL: null,
43   data: '',
44   state: 'empty',
45   
46   init: function(serverURL, revision) {
47     this._super();
48     this.set('state', 'empty');
49     this.set('revision', revision);
50     this.serverURL = serverURL;
51     this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
52     this.addObserver(this, 'data', this.dataChanged.bind(this));
53   },
54   
55   load: function(force) {
56     if (force || this.get('state') == 'empty') {
57       this.set('state', 'loading');
58       messageCenter.addMessage('info', 'Wczytuję XML...');
59       $.ajax({
60         url: this.serverURL,
61         dataType: 'text',
62         data: {revision: this.get('revision')},
63         success: this.loadingSucceeded.bind(this),
64         error: this.loadingFailed.bind(this)
65       });
66       return true;
67     }
68     return false;
69   },
70   
71   loadingSucceeded: function(data) {
72     if (this.get('state') != 'loading') {
73       alert('erroneous state:', this.get('state'));
74     }
75     this.set('data', data);
76     this.set('state', 'synced');
77     messageCenter.addMessage('success', 'Wczytałem XML :-)');
78   },
79   
80   loadingFailed: function() {
81     if (this.get('state') != 'loading') {
82       alert('erroneous state:', this.get('state'));
83     }
84     this.set('error', 'Nie udało się załadować panelu');
85     this.set('state', 'error');    
86     messageCenter.addMessage('error', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
87   },
88   
89   update: function(message) {
90     if (this.get('state') == 'dirty') {
91       this.set('state', 'updating');
92       messageCenter.addMessage('info', 'Zapisuję XML...');
93       
94       var payload = {
95         contents: this.get('data'),
96         revision: this.get('revision')
97       };
98       if (message) {
99         payload.message = message;
100       }
101       
102       $.ajax({
103         url: this.serverURL,
104         type: 'post',
105         dataType: 'json',
106         data: payload,
107         success: this.updatingSucceeded.bind(this),
108         error: this.updatingFailed.bind(this)
109       });
110       return true;
111     }
112     return false;
113   },
114   
115   updatingSucceeded: function(data) {
116     if (this.get('state') != 'updating') {
117       alert('erroneous state:', this.get('state'));
118     }
119     this.set('revision', data.revision);
120     this.set('state', 'updated');
121     messageCenter.addMessage('success', 'Zapisałem XML :-)');
122   },
123   
124   updatingFailed: function() {
125     if (this.get('state') != 'updating') {
126       alert('erroneous state:', this.get('state'));
127     }
128     messageCenter.addMessage('error', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
129     this.set('state', 'dirty');
130   },
131   
132   // For debbuging
133   set: function(property, value) {
134     if (property == 'state') {
135       console.log(this.description(), ':', property, '=', value);
136     }
137     return this._super(property, value);
138   },
139   
140   dataChanged: function(property, value) {
141     if (this.get('state') == 'synced') {
142       this.set('state', 'dirty');
143     }
144   },
145   
146   dispose: function() {
147     this.removeObserver(this);
148     this._super();
149   }
150 });
151
152
153 Editor.HTMLModel = Editor.Model.extend({
154   _className: 'Editor.HTMLModel',
155   serverURL: null,
156   data: '',
157   state: 'empty',
158   
159   init: function(serverURL, revision) {
160     this._super();
161     this.set('state', 'empty');
162     this.set('revision', revision);
163     this.serverURL = serverURL;
164   },
165   
166   load: function(force) {
167     if (force || this.get('state') == 'empty') {
168       this.set('state', 'loading');
169       messageCenter.addMessage('info', 'Wczytuję HTML...');
170       $.ajax({
171         url: this.serverURL,
172         dataType: 'text',
173         data: {revision: this.get('revision')},
174         success: this.loadingSucceeded.bind(this),
175         error: this.loadingFailed.bind(this)
176       });
177     }
178   },
179   
180   loadingSucceeded: function(data) {
181     if (this.get('state') != 'loading') {
182       alert('erroneous state:', this.get('state'));
183     }
184     this.set('data', data);
185     this.set('state', 'synced');
186     messageCenter.addMessage('success', 'Wczytałem HTML :-)');
187   },
188   
189   loadingFailed: function() {
190     if (this.get('state') != 'loading') {
191       alert('erroneous state:', this.get('state'));
192     }
193     this.set('error', 'Nie udało się załadować panelu');
194     this.set('state', 'error');    
195     messageCenter.addMessage('error', 'Nie udało mi się wczytać HTML. Spróbuj ponownie :-(');
196   },
197
198   // For debbuging
199   set: function(property, value) {
200     if (property == 'state') {
201       console.log(this.description(), ':', property, '=', value);
202     }
203     return this._super(property, value);
204   }
205 });
206
207
208 Editor.ImageGalleryModel = Editor.Model.extend({
209   _className: 'Editor.ImageGalleryModel',
210   serverURL: null,
211   data: [],
212   state: 'empty',
213
214   init: function(serverURL) {
215     this._super();
216     this.set('state', 'empty');
217     this.serverURL = serverURL;
218     // olewać data    
219     this.pages = [];
220   },
221
222   load: function(force) {
223     if (force || this.get('state') == 'empty') {
224       this.set('state', 'loading');
225       $.ajax({
226         url: this.serverURL,
227         dataType: 'json',
228         success: this.loadingSucceeded.bind(this)
229       });
230     }
231   },  
232
233   loadingSucceeded: function(data) {
234     if (this.get('state') != 'loading') {
235       alert('erroneous state:', this.get('state'));
236     }
237
238     console.log('galleries:', data);
239
240     if (data.length === 0) {
241         this.set('data', []);
242     } else {
243         console.log('dupa');
244         this.set('data', data[0].pages);
245     }  
246
247     this.set('state', 'synced');
248   },
249
250   set: function(property, value) {
251     if (property == 'state') {
252       console.log(this.description(), ':', property, '=', value);
253     }
254     return this._super(property, value);
255   }
256 });
257
258
259 Editor.DocumentModel = Editor.Model.extend({
260   _className: 'Editor.DocumentModel',
261   data: null, // name, text_url, user_revision, latest_shared_rev, parts_url, dc_url, size, merge_url
262   contentModels: {},
263   state: 'empty',
264   
265   init: function() {
266     this._super();
267     this.set('state', 'empty');
268     this.load();
269   },
270   
271   load: function() {
272     if (this.get('state') == 'empty') {
273       this.set('state', 'loading');
274       messageCenter.addMessage('info', 'Ładuję dane dokumentu...');
275       $.ajax({
276         cache: false,
277         url: documentsUrl + fileId,
278         dataType: 'json',
279         success: this.successfulLoad.bind(this)
280       });
281     }
282   },
283   
284   successfulLoad: function(data) {
285     this.set('data', data);
286     this.set('state', 'synced');
287     this.contentModels = {
288       'xml': new Editor.XMLModel(data.text_url, data.user_revision),
289       'html': new Editor.HTMLModel(data.html_url, data.user_revision),
290       'gallery': new Editor.ImageGalleryModel(data.gallery_url)
291     };
292     for (var key in this.contentModels) {
293       this.contentModels[key].addObserver(this, 'state', this.contentModelStateChanged.bind(this));
294     }
295     messageCenter.addMessage('success', 'Dane dokumentu zostały załadowane :-)');
296   },
297   
298   contentModelStateChanged: function(property, value, contentModel) {
299     if (value == 'dirty') {
300       this.set('state', 'dirty');
301       for (var key in this.contentModels) {
302         if (this.contentModels[key].guid() != contentModel.guid()) {
303           this.contentModels[key].set('state', 'unsynced');
304         }
305       }
306     } else if (value == 'updated') {
307       this.set('state', 'synced');
308       for (key in this.contentModels) {
309         if (this.contentModels[key].guid() == contentModel.guid()) {
310           this.contentModels[key].set('state', 'synced');
311           this.data.user_revision = this.contentModels[key].get('revision');
312         }
313       }
314       for (key in this.contentModels) {
315         if (this.contentModels[key].guid() != contentModel.guid()) {
316           this.contentModels[key].set('revision', this.data.user_revision);
317           this.contentModels[key].set('state', 'empty');
318         }
319       }
320     }
321   },
322   
323   saveDirtyContentModel: function(message) {
324     for (var key in this.contentModels) {
325       if (this.contentModels[key].get('state') == 'dirty') {
326         this.contentModels[key].update(message);
327         break;
328       }
329     }
330   },
331   
332   update: function() {
333     this.set('state', 'loading');
334     messageCenter.addMessage('info', 'Uaktualniam dokument...');
335     $.ajax({
336       url: this.data.merge_url,
337       dataType: 'json',
338       type: 'post',
339       data: {
340         type: 'update',
341         target_revision: this.data.user_revision
342       },
343       complete: this.updateCompleted.bind(this),
344       success: function(data) { this.set('updateData', data); }.bind(this)
345     });
346   },
347   
348   updateCompleted: function(xhr, textStatus) {
349     console.log(xhr.status, textStatus);
350     if (xhr.status == 200) { // Sukces
351       this.data.user_revision = this.get('updateData').revision;
352       messageCenter.addMessage('info', 'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision,
353         'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision);
354       for (var key in this.contentModels) {
355         this.contentModels[key].set('revision', this.data.user_revision);
356         this.contentModels[key].set('state', 'empty');
357       }
358       messageCenter.addMessage('success', 'Uaktualniłem dokument do najnowszej wersji :-)');
359     } else if (xhr.status == 202) { // Wygenerowano PullRequest (tutaj?)
360     } else if (xhr.status == 204) { // Nic nie zmieniono
361       messageCenter.addMessage('info', 'Nic się nie zmieniło od ostatniej aktualizacji. Po co mam uaktualniać?');
362     } else if (xhr.status == 409) { // Konflikt podczas operacji
363       messageCenter.addMessage('error', 'Wystąpił konflikt podczas aktualizacji. Pędź po programistów! :-(');
364     } else if (xhr.status == 500) {
365       messageCenter.addMessage('critical', 'Błąd serwera. Pędź po programistów! :-(');
366     } 
367     this.set('state', 'synced');
368     this.set('updateData', null);
369   },
370   
371   merge: function(message) {
372     this.set('state', 'loading');
373     messageCenter.addMessage('info', 'Scalam dokument z głównym repozytorium...');
374     $.ajax({
375       url: this.data.merge_url,
376       type: 'post',
377       dataType: 'json',
378       data: {
379         type: 'share',
380         target_revision: this.data.user_revision,
381         message: message
382       },
383       complete: this.mergeCompleted.bind(this),
384       success: function(data) { this.set('mergeData', data); }.bind(this)
385     });
386   },
387   
388   mergeCompleted: function(xhr, textStatus) {
389     console.log(xhr.status, textStatus);
390     if (xhr.status == 200) { // Sukces
391       this.data.user_revision = this.get('mergeData').revision;
392       for (var key in this.contentModels) {
393         this.contentModels[key].set('revision', this.data.user_revision);
394         this.contentModels[key].set('state', 'empty');
395       }
396       messageCenter.addMessage('success', 'Scaliłem dokument z głównym repozytorium :-)');
397     } else if (xhr.status == 202) { // Wygenerowano PullRequest
398       messageCenter.addMessage('success', 'Wysłałem prośbę o scalenie dokumentu z głównym repozytorium.');
399     } else if (xhr.status == 204) { // Nic nie zmieniono
400       messageCenter.addMessage('info', 'Nic się nie zmieniło od ostatniego scalenia. Po co mam scalać?');
401     } else if (xhr.status == 409) { // Konflikt podczas operacji
402       messageCenter.addMessage('error', 'Wystąpił konflikt podczas scalania. Pędź po programistów! :-(');
403     } else if (xhr.status == 500) {
404       messageCenter.addMessage('critical', 'Błąd serwera. Pędź po programistów! :-(');
405     }
406     this.set('state', 'synced');
407     this.set('mergeData', null);
408   },
409   
410   // For debbuging
411   set: function(property, value) {
412     if (property == 'state') {
413       console.log(this.description(), ':', property, '=', value);
414     }
415     return this._super(property, value);
416   }
417 });
418
419
420 var leftPanelView, rightPanelContainer, doc;
421
422 $(function()
423 {
424   documentsUrl = $('#api-base-url').text() + '/';
425   toolbarUrl = $('#api-toolbar-url').text();
426
427   doc = new Editor.DocumentModel();
428   var editor = new EditorView('#body-wrap', doc);  
429   editor.freeze();
430
431   var flashView = new FlashView('#flashview', messageCenter);
432   var splitView = new SplitView('#splitview', doc);
433
434   leftPanelView = new PanelContainerView('#left-panel-container', doc);
435   rightPanelContainer = new PanelContainerView('#right-panel-container', doc); 
436 });