More UTF-8 vs. mercurial vs. os.listdir problems.
[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       $.ajax({
59         url: this.serverURL,
60         dataType: 'text',
61         data: {revision: this.get('revision')},
62         success: this.loadingSucceeded.bind(this),
63         error: this.loadingFailed.bind(this)
64       });
65       return true;
66     }
67     return false;
68   },
69   
70   loadingSucceeded: function(data) {
71     if (this.get('state') != 'loading') {
72       alert('erroneous state:', this.get('state'));
73     }
74     this.set('data', data);
75     this.set('state', 'synced');
76   },
77   
78   loadingFailed: function() {
79     if (this.get('state') != 'loading') {
80       alert('erroneous state:', this.get('state'));
81     }
82     this.set('error', 'Nie udało się załadować panelu');
83     this.set('state', 'error');    
84   },
85   
86   update: function(message) {
87     if (this.get('state') == 'dirty') {
88       this.set('state', 'updating');
89       
90       var payload = {
91         contents: this.get('data'),
92         revision: this.get('revision')
93       };
94       if (message) {
95         payload.message = message;
96       }
97       
98       $.ajax({
99         url: this.serverURL,
100         type: 'post',
101         dataType: 'json',
102         data: payload,
103         success: this.updatingSucceeded.bind(this),
104         error: this.updatingFailed.bind(this)
105       });
106       return true;
107     }
108     return false;
109   },
110   
111   updatingSucceeded: function(data) {
112     if (this.get('state') != 'updating') {
113       alert('erroneous state:', this.get('state'));
114     }
115     this.set('revision', data.revision);
116     this.set('state', 'updated');
117   },
118   
119   updatingFailed: function() {
120     if (this.get('state') != 'updating') {
121       alert('erroneous state:', this.get('state'));
122     }
123     messageCenter.addMessage('error', 'Uaktualnienie nie powiodło się', 'Uaktualnienie nie powiodło się');
124     this.set('state', 'dirty');
125   },
126   
127   // For debbuging
128   set: function(property, value) {
129     if (property == 'state') {
130       console.log(this.description(), ':', property, '=', value);
131     }
132     return this._super(property, value);
133   },
134   
135   dataChanged: function(property, value) {
136     if (this.get('state') == 'synced') {
137       this.set('state', 'dirty');
138     }
139   },
140   
141   dispose: function() {
142     this.removeObserver(this);
143     this._super();
144   }
145 });
146
147
148 Editor.HTMLModel = Editor.Model.extend({
149   _className: 'Editor.HTMLModel',
150   serverURL: null,
151   data: '',
152   state: 'empty',
153   
154   init: function(serverURL, revision) {
155     this._super();
156     this.set('state', 'empty');
157     this.set('revision', revision);
158     this.serverURL = serverURL;
159   },
160   
161   load: function(force) {
162     if (force || this.get('state') == 'empty') {
163       this.set('state', 'loading');
164       $.ajax({
165         url: this.serverURL,
166         dataType: 'text',
167         data: {revision: this.get('revision')},
168         success: this.loadingSucceeded.bind(this),
169         error: this.loadingFailed.bind(this)
170       });
171     }
172   },
173   
174   loadingSucceeded: function(data) {
175     if (this.get('state') != 'loading') {
176       alert('erroneous state:', this.get('state'));
177     }
178     this.set('data', data);
179     this.set('state', 'synced');
180   },
181   
182   loadingFailed: function() {
183     if (this.get('state') != 'loading') {
184       alert('erroneous state:', this.get('state'));
185     }
186     this.set('error', 'Nie udało się załadować panelu');
187     this.set('state', 'error');    
188   },
189
190   // For debbuging
191   set: function(property, value) {
192     if (property == 'state') {
193       console.log(this.description(), ':', property, '=', value);
194     }
195     return this._super(property, value);
196   }
197 });
198
199
200 Editor.ImageGalleryModel = Editor.Model.extend({
201   _className: 'Editor.ImageGalleryModel',
202   serverURL: null,
203   data: [],
204   state: 'empty',
205
206   init: function(serverURL) {
207     this._super();
208     this.set('state', 'empty');
209     this.serverURL = serverURL;
210     // olewać data    
211     this.pages = [];
212   },
213
214   load: function(force) {
215     if (force || this.get('state') == 'empty') {
216       this.set('state', 'loading');
217       $.ajax({
218         url: this.serverURL,
219         dataType: 'json',
220         success: this.loadingSucceeded.bind(this)
221       });
222     }
223   },  
224
225   loadingSucceeded: function(data) {
226     if (this.get('state') != 'loading') {
227       alert('erroneous state:', this.get('state'));
228     }
229
230     console.log('galleries:', data);
231
232     if (data.length === 0) {
233         this.set('data', []);
234     } else {
235         console.log('dupa');
236         this.set('data', data[0].pages);
237     }  
238
239     this.set('state', 'synced');
240   },
241
242   set: function(property, value) {
243     if (property == 'state') {
244       console.log(this.description(), ':', property, '=', value);
245     }
246     return this._super(property, value);
247   }
248 });
249
250
251 Editor.DocumentModel = Editor.Model.extend({
252   _className: 'Editor.DocumentModel',
253   data: null, // name, text_url, user_revision, latest_shared_rev, parts_url, dc_url, size, merge_url
254   contentModels: {},
255   state: 'empty',
256   
257   init: function() {
258     this._super();
259     this.set('state', 'empty');
260     this.load();
261   },
262   
263   load: function() {
264     if (this.get('state') == 'empty') {
265       this.set('state', 'loading');
266       $.ajax({
267         cache: false,
268         url: documentsUrl + fileId,
269         dataType: 'json',
270         success: this.successfulLoad.bind(this)
271       });
272     }
273   },
274   
275   successfulLoad: function(data) {
276     this.set('data', data);
277     this.set('state', 'synced');
278     this.contentModels = {
279       'xml': new Editor.XMLModel(data.text_url, data.user_revision),
280       'html': new Editor.HTMLModel(data.html_url, data.user_revision),
281       'gallery': new Editor.ImageGalleryModel(data.gallery_url)
282     };
283     for (var key in this.contentModels) {
284       this.contentModels[key].addObserver(this, 'state', this.contentModelStateChanged.bind(this));
285     }
286   },
287   
288   contentModelStateChanged: function(property, value, contentModel) {
289     if (value == 'dirty') {
290       this.set('state', 'dirty');
291       for (var key in this.contentModels) {
292         if (this.contentModels[key].guid() != contentModel.guid()) {
293           this.contentModels[key].set('state', 'unsynced');
294         }
295       }
296     } else if (value == 'updated') {
297       this.set('state', 'synced');
298       for (key in this.contentModels) {
299         if (this.contentModels[key].guid() == contentModel.guid()) {
300           this.contentModels[key].set('state', 'synced');
301           this.data.user_revision = this.contentModels[key].get('revision');
302           messageCenter.addMessage('info', 'Uaktualnienie dokumentu do wersji ' + this.data.user_revision,
303             'Uaktualnienie dokumentu do wersji ' + this.data.user_revision);
304         }
305       }
306       for (key in this.contentModels) {
307         if (this.contentModels[key].guid() != contentModel.guid()) {
308           this.contentModels[key].set('revision', this.data.user_revision);
309           this.contentModels[key].set('state', 'empty');
310         }
311       }
312     }
313   },
314   
315   saveDirtyContentModel: function(message) {
316     for (var key in this.contentModels) {
317       if (this.contentModels[key].get('state') == 'dirty') {
318         this.contentModels[key].update(message);
319         break;
320       }
321     }
322   },
323   
324   update: function() {
325     this.set('state', 'loading');
326     $.ajax({
327       url: this.data.merge_url,
328       dataType: 'json',
329       type: 'post',
330       data: {
331         type: 'update',
332         target_revision: this.data.user_revision
333       },
334       complete: this.updateCompleted.bind(this),
335       success: function(data) { this.set('updateData', data); }.bind(this)
336     });
337   },
338   
339   updateCompleted: function(xhr, textStatus) {
340     console.log(xhr.status, textStatus);
341     if (xhr.status == 200) { // Sukces
342       this.data.user_revision = this.get('updateData').revision;
343       messageCenter.addMessage('info', 'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision,
344         'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision);
345       for (var key in this.contentModels) {
346         this.contentModels[key].set('revision', this.data.user_revision);
347         this.contentModels[key].set('state', 'empty');
348       }
349     } else if (xhr.status == 202) { // Wygenerowano PullRequest (tutaj?)
350     } else if (xhr.status == 204) { // Nic nie zmieniono
351     } else if (xhr.status == 409) { // Konflikt podczas operacji
352     } 
353     this.set('state', 'synced');
354     this.set('updateData', null);
355   },
356   
357   merge: function(message) {
358     this.set('state', 'loading');
359     $.ajax({
360       url: this.data.merge_url,
361       type: 'post',
362       dataType: 'json',
363       data: {
364         type: 'share',
365         target_revision: this.data.user_revision,
366         message: message
367       },
368       complete: this.mergeCompleted.bind(this),
369       success: function(data) { this.set('mergeData', data); }.bind(this)
370     });
371   },
372   
373   mergeCompleted: function(xhr, textStatus) {
374     console.log(xhr.status, textStatus);
375     if (xhr.status == 200) { // Sukces
376       this.data.user_revision = this.get('mergeData').revision;
377       for (var key in this.contentModels) {
378         this.contentModels[key].set('revision', this.data.user_revision);
379         this.contentModels[key].set('state', 'empty');
380       }
381       messageCenter.addMessage('info', 'Uaktualnienie dokumentu do wersji ' + this.get('mergeData').revision,
382         'Uaktualnienie dokumentu do wersji ' + this.get('mergeData').revision);
383     } else if (xhr.status == 202) { // Wygenerowano PullRequest
384     } else if (xhr.status == 204) { // Nic nie zmieniono
385     } else if (xhr.status == 409) { // Konflikt podczas operacji
386     }
387     this.set('state', 'synced');
388     this.set('mergeData', null);
389   },
390   
391   // For debbuging
392   set: function(property, value) {
393     if (property == 'state') {
394       console.log(this.description(), ':', property, '=', value);
395     }
396     return this._super(property, value);
397   }
398 });
399
400
401 var leftPanelView, rightPanelContainer, doc;
402
403 $(function()
404 {
405   documentsUrl = $('#api-base-url').text() + '/';
406   toolbarUrl = $('#api-toolbar-url').text();
407
408   doc = new Editor.DocumentModel();
409   var editor = new EditorView('#body-wrap', doc);
410   editor.freeze();
411   var flashView = new FlashView('#flashview', messageCenter);
412   var splitView = new SplitView('#splitview', doc);
413   leftPanelView = new PanelContainerView('#left-panel-container', doc);
414   rightPanelContainer = new PanelContainerView('#right-panel-container', doc);
415 });