Fix: merge conflict
[myslice.git] / to-be-integrated / plugins / code_editor / static / js / LayoutCM.js
1 /*
2  Layout using CodeMirror
3  */
4
5 Element.implement({
6   getInnerWidth: function() {
7     return this.getSize().x -
8       this.getStyle('padding-left').toInt() -
9       this.getStyle('padding-right').toInt() -
10       this.getStyle('border-left-width').toInt() -
11       this.getStyle('border-right-width').toInt();
12   },
13   getInnerHeight: function() {
14     return this.getSize().y -
15       this.getStyle('padding-top').toInt() -
16       this.getStyle('padding-bottom').toInt() -
17       this.getStyle('border-top-width').toInt() -
18       this.getStyle('border-bottom-width').toInt();
19   }
20 });
21
22 var keyMods = {
23   'shift': false,
24   'shiftKey': false,
25   'control': false,
26   'ctrlKey': false
27 }; // these mods will be checked
28
29 var Layout = {
30   editors: $H({}),
31   editors_order: ['html', 'css', 'js'],
32   reservedKeys: [ // list of [modifier,keycode,callbackname]
33     ['ctrlKey', 13, 'run'], ['control', 13, 'run'],   // c+ret+run
34     ['ctrlKey', 38, 'switchPrev'],                    // c+upArrow
35     ['ctrlKey', 40, 'switchNext'],                     // c+dnArrow
36     ['ctrlKey+shiftKey', 13, 'loadDraft'], ['control+shift', 13, 'loadDraft'],
37     ['ctrlKey+shiftKey', 38, 'toggleSidebar'], ['control+shift', 38, 'toggleSidebar'],
38     ['ctrlKey+shiftKey', 76, 'showShortcutDialog'], ['control+shift', 76, 'showShortcutDialog'],
39     ['ctrlKey', 83, 'saveAndReload'], ['control', 83, 'saveAndReload'],
40     ['ctrlKey', 75, 'launchTowtruck'], ['control', 75, 'launchTowtruck']
41     // future
42     // ['ctrlKey', f, 'searchBox'], ['control', f, 'searchBox']
43   ],
44   isMobile: (navigator.userAgent.match(/(iPhone|iPod|iPad)/) ||
45              navigator.userAgent.match(/BlackBerry/) ||
46              navigator.userAgent.match(/Android/)),
47   render: function () {
48     // instantiate sidebar
49 //    this.sidebar = new Sidebar({
50 //      DOM: 'sidebar'
51 //    });
52     window.addEvents({
53       'resize': this.resize.bind(this),
54       'keydown': function(keyEvent) {
55         // console.log(keyEvent)
56         if (this.isReservedKey(false, keyEvent)) {
57           this.routeReservedKey(keyEvent);
58         }
59       }.bind(this)
60     });
61 //    this.sidebar.addEvents({
62 //      'accordion_resized': this.resize.bind(this)
63 //    });
64     // set editor labels
65     var result = document.id('result');
66     $$('.window_label').setStyle('opacity', 0.8);
67     if (result) {
68       result.getElement('.window_label').setStyle('opacity', 0.3);
69       this.result = result.getElement('iframe');
70     }
71     // resize
72     this.resize();
73     this.resize.bind(this).delay(20);
74     // change behaviour for IE
75     if (!Browser.Engine.trident4) {
76       this.createDragInstances();
77     }
78     this.createErrorTooltips();
79     // send an event
80     this.fireEvent('ready');
81     // jordan:
82     this.refreshEditors();
83   },
84
85   createErrorTooltips: function(){
86     var tooltip = Element('span', {
87       'class': 'CodeMirror-error-tooltip'
88     }).inject(document.body);
89     document.id('content').addEvents({
90       'mouseover:relay(.CodeMirror-line-error)': function(el){
91         tooltip.set({
92           html: this.get('data-title'),
93           styles: {
94             display: 'block'
95           }
96         });
97         tooltip.position({
98           relativeTo: this,
99           edge: 'centerBottom',
100           offset: {
101             y: -15
102           }
103         });
104       },
105       'mouseout:relay(.CodeMirror-line-error)': function(){
106         tooltip.set({
107           html: '',
108           styles: {
109             top: 0,
110             left: 0,
111             display: 'none'
112           }
113         });
114       }
115     });
116   },
117
118   routeReservedKey: function(keyEvent) {
119     this.reservedKeys.each(function(keyDef){
120       if (this.matchKey(keyEvent, keyDef)) {
121         mooshell[keyDef.getLast()].bind(mooshell).call();
122       }
123     }, this);
124   },
125
126   matchKey: function(keyEvent, keyDef) {
127     if (!keyEvent) return false;
128     var key = keyEvent.keyCode || keyEvent.code;
129     // check if the right key is pressed
130     if (!keyDef.contains(key)) return false;
131     // check for the modifications
132     var pass = true;
133     if (keyDef.length > 1) {
134       var mods = {};
135       keyDef[0].split('+').each(function(mod) {
136         mods[mod] = true;
137       });
138       // adding other mods
139       $each(keyMods, function(value, mod) {
140         if (!mods[mod]) mods[mod] = false;
141       });
142       // check all possibilities
143       $each(mods, function(required, mod) {
144         if (!!keyEvent[mod] != required) pass = false;
145       });
146     }
147     return pass;
148   },
149
150   isReservedKey: function(keyCode, keyEvent) {
151     return (this.reservedKeys.some(function(keyDef) {
152       return this.matchKey(keyEvent, keyDef);
153     }, this));
154   },
155
156   findLayoutElements: function() {
157     // look up some elements, and cache the findings
158     this.content = document.id('content');
159     this.columns = this.content.getChildren('.column');
160     this.windows = this.content.getElements('.window');
161     this.shims = this.content.getElements('.column .shim');
162     this.handlers = $H({
163       'vertical': this.content.getElementById('handler_vertical'),
164       'left': this.columns[0].getElement('.handler_horizontal'),
165       'right': this.columns[1].getElement('.handler_horizontal')
166     });
167   },
168
169   registerEditor: function(editor) {
170     this.editors[editor.options.name] = editor;
171     this.resize();
172   },
173
174   decodeEditors: function() {
175     this.editors.each(function(ed) {
176       ed.b64decode();
177     });
178   },
179
180   updateFromMirror: function() {
181     this.editors.each(function(ed) {
182       ed.updateFromMirror();
183     });
184   },
185
186   cleanMirrors: function() {
187     this.editors.each(function(ed) {
188       ed.clean();
189     });
190   },
191
192   createDragInstances: function() {
193     var onDrag_horizontal = function(h) {
194       var windows = h.getParent().getElements('.window');
195       var top = (h.getPosition(this.content).y + h.getHeight() / 2) / this.content.getHeight() * 100;
196       windows[0].setStyle('height', top + '%');
197       windows[1].setStyle('height', 100 - top + '%');
198       this.refreshEditors();
199     }.bind(this);
200
201     var onDrag_vertical = function(h) {
202       var left = (h.getPosition(this.content).x + h.getWidth() / 2) / this.content.getWidth() * 100;
203       this.columns[0].setStyle('width', left + '%');
204       this.columns[1].setStyle('width', 100 - left + '%');
205     }.bind(this);
206
207     this.handlers.each(function(h) {
208       var isHorizontal = h.hasClass('handler_horizontal');
209       h.dragInstance = new Drag(h, {
210         'modifiers': isHorizontal ? {'x': null, 'y': 'top'} : {'x': 'left', 'y': null},
211         'limit': {
212           'x': [100, this.content.getWidth() - 100],
213           'y': [100, this.content.getHeight() - 100]
214         },
215         'onBeforeStart': function() { this.shims.show(); }.bind(this),
216         'onDrag': isHorizontal ? onDrag_horizontal : onDrag_vertical,
217         'onCancel': function() { this.shims.hide(); }.bind(this),
218         'onComplete': function() { this.shims.hide(); }.bind(this)
219       });
220     }, this);
221
222     // Save window sizes to cookie onUnload.
223     window.addEvent('unload', function() {
224       var sizes = {
225         'w': [],
226         'h': []
227       };
228       this.columns.each(function(col, i) {
229         var width = col.getStyle('width');
230         sizes.w[i] = width.contains('%') ? width : null;
231       });
232       this.windows.each(function(win, i) {
233         var height = win.getStyle('height');
234         sizes.h[i] = height.contains('%') ? height : null;
235       });
236       Cookie.write('window_sizes', JSON.encode(sizes), {'domain': location.host});
237     }.bind(this));
238
239     // Read window sizes from cookie.
240     this.setWindowSizes();
241   },
242
243   setWindowSizes: function(sizes) {
244     // sizes === undefined --> read from cookie
245     // sizes == null/false --> reset sizes + delete cookie
246     // sizes == true       --> use sizes
247     if (typeof sizes === 'undefined') {
248       sizes = Cookie.read('window_sizes');
249       if (sizes) {
250         sizes = JSON.decode(sizes);
251       }
252     }
253     if (sizes) {
254       if ($type(sizes.w) === 'array') {
255         sizes.w.each(function(width, i) {
256           this.columns[i].setStyle('width', width);
257         }, this);
258       }
259       if ($type(sizes.h) == 'array') {
260         sizes.h.each(function(height, i) {
261           this.windows[i].setStyle('height', height);
262         }, this);
263       }
264     } else {
265       this.columns.setStyle('width', null);
266       this.windows.setStyle('height', null);
267       Cookie.dispose('window_sizes', {'domain': location.host});
268     }
269     this.resize();
270   },
271
272   resize: function(e) {
273     if (!this.content) {
274       this.findLayoutElements();
275     }
276
277     var win_size = window.getSize();
278     var av_height = win_size.y -
279       this.columns[0].getPosition().y +
280       this.windows[0].getStyle('top').toInt() +
281       this.windows[1].getStyle('bottom').toInt();
282
283     this.content.setStyle('height', av_height);
284
285     // set handler positions
286     this.handlers.vertical.setStyle('left', this.windows[0].getCoordinates(this.content).right);
287     this.handlers.left.setStyle('top', this.windows[0].getCoordinates(this.content).bottom);
288     this.handlers.right.setStyle('top', this.windows[2].getCoordinates(this.content).bottom);
289
290     this.fireEvent('resize');
291   },
292
293   refreshEditors: function(){
294     Object.each(this.editors, function(editorInstance){
295       editorInstance.editor.refresh();
296     });
297   }
298 };
299
300 // add events to Layout object
301 $extend(Layout, new Events());