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