Add key 's' for cycling through stylesheets.
[hacks/simpleWebSlides.git] / simpleWebSlides.js
1 /*
2   Namespace object.
3 */
4
5 var SWS = SWS || {};
6
7
8
9 SWS.Utils = new function () {
10     var self = this;
11
12     self.isUndefined = function (o) { return typeof o == "undefined"; };
13     self.push2 = function (t, i, v) {
14         if ((typeof t[i]) == 'undefined') {
15             t[i] = new Array();
16         };
17         var l = t[i].length;
18         t[i][l] = v;
19     };
20
21     self.isEmpty = function (o) {
22         for(var _ in o) return false;
23         return true;
24     };
25
26     self.parseFrameSpec = function (s) {
27         var elems = s.split("_");
28         var result = {};
29         var min_last = 10000;
30         var max_value = -1;
31         for(var i = 0; i < elems.length; i++){
32             var bounds = elems[i].split("-");
33             if (bounds.length > 2 || bounds.length == 0) return {};
34             if (bounds.length == 1) bounds[1] = bounds[0];
35             var a = parseInt(bounds[0]);
36             var b = parseInt(bounds[1])
37             if (!isFinite(a) || !isFinite(b)) return {};
38             a = Math.min(a, 1000); // don't allow more than 1000 frames/slide
39             b = Math.min(b, 1000);
40             if (b > max_value) max_value = b;
41             for (var j = a; j <= b; j++)
42                 result[j] = true;
43         };
44         return result;
45     };
46
47
48 };
49
50
51
52
53 SWS.Templates = new function () {
54     var self = this;
55     self.controlPanel = "<div id='sws-control-panel'>\
56 <a onclick='SWS.Presentation.goToSlide(SWS.Presentation.firstSlide());'>◀◀◀</a>\
57 <a onclick='SWS.Presentation.previousSlide();SWS.Presentation.refresh();'>◀◀ </a>\
58 <a onclick='SWS.Presentation.previous();SWS.Presentation.refresh();'>◀</a>\
59 <a onclick='SWS.Presentation.next();SWS.Presentation.refresh();'>▶</a>\
60 <a onclick='SWS.Presentation.nextSlide();SWS.Presentation.refresh();'>▶▶</a>\
61 <a rel='Last slide' onclick='SWS.Presentation.goToSlide(SWS.Presentation.lastSlide());'>▶▶▶</a>\
62 </div>";
63     self.slideActivate = function (o) {
64         if (!(o.hasClass("sws-active-slide"))){
65             o.removeClass("sws-inactive-slide").addClass("sws-active-slide");
66         };
67     };
68
69     self.slideDeactivate = function (o) {
70         if (!(o.hasClass("sws-inactive-slide"))){
71             o.removeClass("sws-active-slide").addClass("sws-inactive-slide");
72         };
73     };
74
75     self.slideChange = function (from, to) {
76         var canvas = $(".sws-canvas");
77         self.slideDeactivate($(canvas[from]));
78         self.slideActivate($(canvas[to]));
79     };
80
81     self.objectActivate = function (o) {
82         if (!(o.hasClass("sws-active-object"))){
83             o.removeClass("sws-inactive-object").addClass("sws-active-object");
84             o.css({'visibility':'visible'});
85             return true;
86         };
87         return false;
88     };
89
90     self.objectDeactivate = function (o) {
91         if (!(o.hasClass("sws-inactive-object"))){
92             o.addClass("sws-inactive-object").removeClass("sws-active-object");
93             return true;
94         };
95         return false;
96     };
97
98     self.updateFooter = function (o) {
99         var footer = o.find(".sws-footer");
100         if (footer.length && (footer.children("*").length == 0)) {
101             var i = SWS.Presentation.getCurrentSlide();
102             var cur = $( "<span class='sws-current-slide-number'>"
103                          + (i + 1)
104                          +"</span>");
105             var sep = $( "<span class='sws-slide-num-sep' />");
106             var tot = $( "<span class='sws-last-slide-number'>"
107                          + (SWS.Presentation.getNumSlides())
108                          +"</span>");
109             footer.append(cur).append(sep).append(tot);
110         };
111     };
112     self.updateHeader = function (o) {};
113 };
114 SWS.ConfigBuilder = function () {
115     var self = this;
116     self['sws-object-activate'] = SWS.Templates.objectActivate;
117     self['sws-object-deactivate'] = SWS.Templates.objectDeactivate;
118     self['sws-slide-change'] = SWS.Templates.slideChange;
119     self['sws-update-footer'] = SWS.Templates.updateFooter;
120     self['sws-update-header'] = SWS.Templates.updateHeader;
121 };
122
123 SWS.Defaults = new SWS.ConfigBuilder ();
124
125 SWS.Config = new SWS.ConfigBuilder ();
126
127
128 SWS.Effects = new function () {
129     var self = this;
130
131     self.objectDeactivateFadeOut = function (o) {
132         o.animate({'opacity': '0'}, 200,
133                   function () { SWS.Templates.objectDeactivate(o)});
134     };
135
136     self.objectActivateFadeIn = function (o) {
137
138         if (SWS.Templates.objectActivate(o)){
139             o.animate({'opacity': '1' }, 200);
140         };
141
142     };
143
144     self.slideChangeHorizontalFlip = function (from, to){
145         var f = SWS.Presentation.getSlide(from);
146         var t = SWS.Presentation.getSlide(to);
147         f.animate({ 'left': '50%', 'width': '0pt', 'opacity':'0' }, 150,
148                   function  () {
149                       SWS.Templates.slideDeactivate(f);
150                       f.css({'left':'0%', 'width': '100%'});
151                       t.css({ 'left': '50%', 'width': '0pt','opacity':'0' });
152                       SWS.Templates.slideActivate(t);
153                       t.animate({'left':'0%', 'width': '100%','opacity':'1'});
154                   });
155     };
156     self.slideChangeFadeOutIn = function (from, to) {
157         var f = SWS.Presentation.getSlide(from);
158         var t = SWS.Presentation.getSlide(to);
159         f.animate({ 'opacity': '0'}, 150,
160                   function () { SWS.Templates.slideDeactivate(f);
161                                 SWS.Templates.slideActivate(t);
162                                 t.css('opacity', '0');
163                                 t.animate({ 'opacity': '1'}, 150);
164                               });
165     };
166     self.slideChangeHorizontalSlide = function (from, to) {
167         var f = SWS.Presentation.getSlide(from);
168         var t = SWS.Presentation.getSlide(to);
169         if (from < to) {
170             t.css('left', '100%');
171             SWS.Templates.slideActivate(t);
172             f.animate({ 'left': '-100%' }, 250, function () { SWS.Templates.slideDeactivate(f); });
173             t.animate({ 'left': '0%' }, 250);
174         } else {
175             t.css('left', '-100%');
176             SWS.Templates.slideActivate(t);
177             f.animate({ 'left': '100%' }, 250, function () { SWS.Templates.slideDeactivate(f); });
178             t.animate({ 'left': '0%' }, 250);
179         };
180     };
181
182
183     self.slideChangeVerticalSlide = function (from, to) {
184         var f = SWS.Presentation.getSlide(from);
185         var t = SWS.Presentation.getSlide(to);
186         if (from < to) {
187             t.css('top', '100%');
188             SWS.Templates.slideActivate(t);
189             f.animate({ 'top': '-100%' }, 250, function () { SWS.Templates.slideDeactivate(f); });
190             t.animate({ 'top': '0%' }, 250);
191         } else {
192             t.css('top', '-100%');
193             SWS.Templates.slideActivate(t);
194             f.animate({ 'top': '100%' }, 250, function () { SWS.Templates.slideDeactivate(f); });
195             t.animate({ 'top': '0%' }, 250);
196         };
197     };
198
199 };
200
201 SWS.Presentation = new function () {
202
203
204     var self = this;
205
206     //TODO move outside of the Presentation object
207
208
209     var _total_slides;
210     var _initialized = false;
211     var _disable_input_events = false;
212
213     var _slide_callbacks = new Array ();
214
215
216     self.getNumSlides = function () { return _total_slides; };
217
218     self.getSlide = function(i) {
219         return $($(".sws-canvas")[i]);
220     };
221
222     self.registerCallback = function (i, f) {
223         if (_initialized) return;
224         //jQuery does not seem to work well
225         //on a partial DOM
226
227         var slide_num = $(".sws-slide").length - 1;
228
229         SWS.Utils.push2(_slide_callbacks, slide_num,{ 'fn': f, 'frame': i });
230
231     };
232
233     if (typeof(Storage)!=="undefined"){
234         self.getCurrentSlide = function () {
235             //unary + casts to integer
236             var i = +(sessionStorage.getItem("current_slide"));
237             if (!(i >= 0 && i < self.getNumSlides())){
238                 return 0;
239             } else {
240                 return i;
241             };
242         };
243
244         self.setCurrentSlide = function (i) {
245             sessionStorage.setItem("current_slide", i);
246         };
247
248     } else {
249         var _current_slide = 0;
250         self.getCurrentSlide = function () { return _current_slide; };
251         self.setCurrentSlide = function (i) { _current_slide = i; };
252
253     };
254     self.firstSlide = function () { return 0; };
255     self.lastSlide = function () { return self.getNumSlides() - 1; };
256     self.refresh = function () {
257         /* block upcoming input event until all animations are finished */
258         _disable_input_events = true;
259
260         var canvas = $(".sws-canvas");
261         var from_slide_num = canvas.index($(".sws-active-slide"));
262         var to_slide_num = self.getCurrentSlide();
263         var watch_slide_anim = false;
264         var to_slide = $(canvas[to_slide_num]);
265         var slide_change = (from_slide_num != to_slide_num);
266
267         var info = to_slide.data("sws-frame-info");
268         SWS.Config['sws-update-header'](to_slide);
269         SWS.Config['sws-update-footer'](to_slide);
270
271         if (slide_change) {
272             //Launch a slide transition:
273             SWS.Config['sws-slide-change'](from_slide_num, to_slide_num);
274             watch_slide_anim = true;
275             for (var i = 0; i < info.callbacks.at_slide.length;i++){
276                 info.callbacks.at_slide[i](to_slide);
277             };
278         };
279
280
281         var cur = info.current;
282         var custom = info.custom;
283         var real_slide = to_slide.children(".sws-slide");
284
285         real_slide.find("*").andSelf().each(function (i){
286             var frameset = $(this).data("sws-frame-set") || {};
287             if (frameset[cur])
288                 SWS.Config['sws-object-activate']($(this));
289             else
290                 SWS.Config['sws-object-deactivate']($(this));
291         });
292
293         var callbacks;
294         if (callbacks = info.callbacks.at_frame[self.getCurrentFrame()]){
295             for (var k = 0; k < callbacks.length; k++)
296                 callbacks[k]($(to_slide));
297         };
298
299         var to_watch = $(to_slide).find("*");
300         if (watch_slide_anim) {
301             to_watch = to_watch.add(to_slide).add($(canvas[from_slide_num]));
302         };
303
304         to_watch.find("*").promise().done(function() {
305             _disable_input_events = false;
306         });
307     };
308
309     self.nextSlide = function () {
310         self.setCurrentSlide(Math.min(self.getCurrentSlide()+1,
311                                       self.lastSlide()));
312         self.setCurrentFrame(self.firstFrame());
313     };
314
315     self.previousSlide = function () {
316         self.setCurrentSlide(Math.max(self.getCurrentSlide()-1,
317                                       self.firstSlide()));
318         self.setCurrentFrame(self.firstFrame());
319     };
320
321     self.getFrameInfo = function () {
322
323         var i = self.getCurrentSlide();
324         var canvas = $($(".sws-canvas")[i]);
325         var infos = canvas.data("sws-frame-info");
326         return infos;
327     };
328     self.getCurrentFrame = function () { return self.getFrameInfo().current; };
329     self.setCurrentFrame = function (i) { self.getFrameInfo().current = i; };
330     self.firstFrame = function () { return 0; };
331     self.lastFrame = function () { return self.getFrameInfo().last; };
332
333     self.nextFrame = function () {
334         self.setCurrentFrame(Math.min(self.getCurrentFrame()+1,
335                                       self.lastFrame()));
336
337     };
338     self.previousFrame = function () {
339         self.setCurrentFrame(Math.max(self.getCurrentFrame()-1,
340                                        self.firstFrame()));
341     };
342
343     self.next = function () {
344         var i = self.getCurrentFrame();
345         if (i == self.lastFrame()) {
346             self.nextSlide();
347             self.setCurrentFrame(self.firstFrame());
348         } else
349             self.nextFrame();
350     };
351
352     self.previous = function () {
353         var i = self.getCurrentFrame();
354         if (i == self.firstFrame()){
355             self.previousSlide();
356             self.setCurrentFrame(self.lastFrame());
357         }
358         else
359             self.previousFrame();
360     };
361
362     self.goToSlide = function (s, f) {
363         if (SWS.Utils.isUndefined(f))
364             f = 0;
365         if (!(s >= self.firstSlide() && s <= self.lastSlide())) return;
366         self.setCurrentSlide(s);
367         if (!(f >= self.firstFrame() && f <= self.lastFrame())) f = 0;
368         self.setCurrentFrame(f);
369         self.refresh();
370     };
371
372     self.cycleStyle = function() {
373         var styles = $("head").children('link[rel$="stylesheet"][title]');
374         var j = styles.index(styles.filter(':not(:disabled)'));
375         styles[j].disabled = true;
376         if (++j == styles.length) j = 0;
377         styles[j].disabled = false;
378     };
379
380     self.inputHandler = function (event) {
381         if (_disable_input_events) return;
382         switch (event.which) {
383         case 36:/* Home */
384             self.setCurrentSlide(self.firstSlide());
385             break;
386
387         case 35:/* End */
388             self.setCurrentSlide(self.lastSlide());
389             break;
390
391         case 32: /* space */
392         case 39: /* -> */
393
394             self.next();
395             break;
396         case 34: /* PgDown */
397         case 78: /* n */
398             self.nextSlide();
399             break;
400         case 8: /* backspace */
401         case 37: /* <-   */
402             self.previous();
403             break;
404         case 33: /* PgUp */
405         case 80: /* p */
406             self.previousSlide();
407             break;
408         case 83: /* s */
409             self.cycleStyle();
410             return;
411         case 67: /* c */
412             $("#sws-control-panel").toggle();
413         default:
414             return;
415         };
416         self.refresh();
417     };
418
419
420
421     function init_canvas(canvas, custom) {
422         var cur_frame = 0;
423         var last_frame = canvas.find(".sws-pause").length;
424         //Add all regular elements to the frame list
425         var slide = $(canvas.children(".sws-slide")[0]);
426
427         var callbacks = { at_slide : new Array(),
428                           at_frame : new Array() }
429
430         if (SWS.Utils.isUndefined(custom)) {
431             custom = new Array ();
432         };
433
434         for (var i = 0; i < custom.length; i++) {
435             if (isFinite(custom[i].frame)){
436                 var num = +(custom[i].frame);
437                 if (num > last_frame) last_frame = num;
438                 SWS.Utils.push2(callbacks.at_frame, num, custom[i].fn);
439             } else if (custom[i].frame == "slide")
440                 callbacks.at_slide.push(custom[i].fn);
441             else {
442                 var frame_set = SWS.Utils.parseFrameSpec(custom[i].frame);
443                 for(var f in frame_set){
444                     if (f > last_frame) last_frame = f;
445                     SWS.Utils.push2(callbacks.at_frame, +(f), custom[i].fn);
446                 };
447             }
448         };
449
450         var specials = null;
451
452         slide.find('*[class*="sws-onframe-"]').each(function(_){
453             var cls = $(this).attr('class');
454             var idx = cls.indexOf("sws-onframe-");
455             if (idx >= 0) {
456                 var end = cls.indexOf(" ", idx);
457                 end = (end == -1) ? cls.length : end;
458                 var spec = cls.substring(idx+12, end);
459                 var o = SWS.Utils.parseFrameSpec(spec);
460                 for(var f in o)
461                     if (f > last_frame) last_frame = f;
462                 $(this).find("*").andSelf().each(function(_){
463                     if (!SWS.Utils.isEmpty(o))
464                         $(this).data("sws-frame-set", o);
465                     if (specials)
466                         specials.add($(this));
467                     else
468                         specials = $(this);
469                 });
470             };
471         });
472
473         slide.find("*").andSelf().not(specials).each(function(i) {
474             if ($(this).hasClass("sws-pause"))  cur_frame++;
475             var o = {};
476             for (var j = cur_frame; j <= last_frame; j++)
477                 o[ j ] = true;
478             if (!SWS.Utils.isEmpty(o))
479                 $(this).data("sws-frame-set", o);
480         });
481
482         canvas.data("sws-frame-info", { current: 0,
483                                         last: last_frame,
484                                         callbacks: callbacks
485                                       });
486
487     };
488
489     self.init = function () {
490         console.log("inited");
491         $(window).bind('storage', function (e) {
492             console.log(e);
493         });
494
495         _total_slides = $(".sws-slide").length;
496
497         $(document).keydown(self.inputHandler);
498         $("body").append($(SWS.Templates.controlPanel));
499         var cur = self.getCurrentSlide();
500         $(".sws-slide").each (function (i) {
501             var par = $(this).parent();
502
503             $(this).remove();
504             var canvas = $('<div class="sws-canvas"/>');
505
506             if (!($(this).hasClass("sws-option-noheader"))) {
507                 canvas.append($('<div class="sws-header"/>'));
508             };
509             $(this).find('script[type="text/javascript"]').remove();
510             canvas.append($(this));
511             if (!($(this).hasClass("sws-option-nofooter"))) {
512                 canvas.append($('<div class="sws-footer"/>'));
513             };
514             par.append(canvas);
515
516             if (i == cur) {
517                 canvas
518                     .addClass("sws-active-slide")
519                     .removeClass("sws-inactive-slide");
520             } else {
521                 canvas
522                     .addClass("sws-inactive-slide")
523                     .removeClass("sws-active-slide");
524             };
525             init_canvas(canvas,_slide_callbacks[i]);
526
527         });
528         _slide_callbacks = null; /* avoid a leak */
529         self.refresh();
530         _initialized = true;
531
532     };
533
534 };