sideshow 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. checksums.yaml +7 -0
  2. data/.bowerrc +3 -0
  3. data/.csslintrc +37 -0
  4. data/.editorconfig +27 -0
  5. data/.gitattributes +1 -0
  6. data/.gitignore +23 -0
  7. data/BUILDING.md +4 -0
  8. data/CHANGELOG.md +47 -0
  9. data/Gulpfile.js +404 -0
  10. data/LICENSE +191 -0
  11. data/README.md +342 -0
  12. data/VERSION +1 -0
  13. data/bower.json +61 -0
  14. data/distr/dependencies/jazz.min.js +8 -0
  15. data/distr/dependencies/jquery.min.js +5 -0
  16. data/distr/dependencies/pagedown.min.js +1 -0
  17. data/distr/fonts/open-sans-family/opensans-bold.eot +0 -0
  18. data/distr/fonts/open-sans-family/opensans-bold.svg +1825 -0
  19. data/distr/fonts/open-sans-family/opensans-bold.ttf +0 -0
  20. data/distr/fonts/open-sans-family/opensans-bold.woff +0 -0
  21. data/distr/fonts/open-sans-family/opensans-bolditalic.eot +0 -0
  22. data/distr/fonts/open-sans-family/opensans-bolditalic.svg +1825 -0
  23. data/distr/fonts/open-sans-family/opensans-bolditalic.ttf +0 -0
  24. data/distr/fonts/open-sans-family/opensans-bolditalic.woff +0 -0
  25. data/distr/fonts/open-sans-family/opensans-extrabold.eot +0 -0
  26. data/distr/fonts/open-sans-family/opensans-extrabold.svg +1825 -0
  27. data/distr/fonts/open-sans-family/opensans-extrabold.ttf +0 -0
  28. data/distr/fonts/open-sans-family/opensans-extrabold.woff +0 -0
  29. data/distr/fonts/open-sans-family/opensans-extrabolditalic.eot +0 -0
  30. data/distr/fonts/open-sans-family/opensans-extrabolditalic.svg +1825 -0
  31. data/distr/fonts/open-sans-family/opensans-extrabolditalic.ttf +0 -0
  32. data/distr/fonts/open-sans-family/opensans-extrabolditalic.woff +0 -0
  33. data/distr/fonts/open-sans-family/opensans-italic.eot +0 -0
  34. data/distr/fonts/open-sans-family/opensans-italic.svg +1825 -0
  35. data/distr/fonts/open-sans-family/opensans-italic.ttf +0 -0
  36. data/distr/fonts/open-sans-family/opensans-italic.woff +0 -0
  37. data/distr/fonts/open-sans-family/opensans-light.eot +0 -0
  38. data/distr/fonts/open-sans-family/opensans-light.svg +1825 -0
  39. data/distr/fonts/open-sans-family/opensans-light.ttf +0 -0
  40. data/distr/fonts/open-sans-family/opensans-light.woff +0 -0
  41. data/distr/fonts/open-sans-family/opensans-lightitalic.eot +0 -0
  42. data/distr/fonts/open-sans-family/opensans-lightitalic.svg +1825 -0
  43. data/distr/fonts/open-sans-family/opensans-lightitalic.ttf +0 -0
  44. data/distr/fonts/open-sans-family/opensans-lightitalic.woff +0 -0
  45. data/distr/fonts/open-sans-family/opensans-regular.eot +0 -0
  46. data/distr/fonts/open-sans-family/opensans-regular.svg +1825 -0
  47. data/distr/fonts/open-sans-family/opensans-regular.ttf +0 -0
  48. data/distr/fonts/open-sans-family/opensans-regular.woff +0 -0
  49. data/distr/fonts/open-sans-family/opensans-semibold.eot +0 -0
  50. data/distr/fonts/open-sans-family/opensans-semibold.svg +1825 -0
  51. data/distr/fonts/open-sans-family/opensans-semibold.ttf +0 -0
  52. data/distr/fonts/open-sans-family/opensans-semibold.woff +0 -0
  53. data/distr/fonts/open-sans-family/opensans-semibolditalic.eot +0 -0
  54. data/distr/fonts/open-sans-family/opensans-semibolditalic.svg +1825 -0
  55. data/distr/fonts/open-sans-family/opensans-semibolditalic.ttf +0 -0
  56. data/distr/fonts/open-sans-family/opensans-semibolditalic.woff +0 -0
  57. data/distr/fonts/sideshow-fontface.min.css +1 -0
  58. data/distr/fonts/sideshow-icons/sideshow-icons.eot +0 -0
  59. data/distr/fonts/sideshow-icons/sideshow-icons.svg +16 -0
  60. data/distr/fonts/sideshow-icons/sideshow-icons.ttf +0 -0
  61. data/distr/fonts/sideshow-icons/sideshow-icons.woff +0 -0
  62. data/distr/sideshow.js +2510 -0
  63. data/distr/sideshow.min.js +10 -0
  64. data/distr/stylesheets/sideshow.min.css +1 -0
  65. data/docs/api.js +29 -0
  66. data/docs/assets/css/external-small.png +0 -0
  67. data/docs/assets/css/logo.png +0 -0
  68. data/docs/assets/css/main.css +783 -0
  69. data/docs/assets/favicon.png +0 -0
  70. data/docs/assets/img/spinner.gif +0 -0
  71. data/docs/assets/index.html +10 -0
  72. data/docs/assets/js/api-filter.js +52 -0
  73. data/docs/assets/js/api-list.js +251 -0
  74. data/docs/assets/js/api-search.js +98 -0
  75. data/docs/assets/js/apidocs.js +370 -0
  76. data/docs/assets/js/yui-prettify.js +17 -0
  77. data/docs/assets/vendor/prettify/CHANGES.html +130 -0
  78. data/docs/assets/vendor/prettify/COPYING +202 -0
  79. data/docs/assets/vendor/prettify/README.html +203 -0
  80. data/docs/assets/vendor/prettify/prettify-min.css +1 -0
  81. data/docs/assets/vendor/prettify/prettify-min.js +1 -0
  82. data/docs/classes/Arrow.html +541 -0
  83. data/docs/classes/Arrows.html +805 -0
  84. data/docs/classes/ControlVariables.html +1005 -0
  85. data/docs/classes/DetailsPanel.html +672 -0
  86. data/docs/classes/FadableItem.html +613 -0
  87. data/docs/classes/HidableItem.html +495 -0
  88. data/docs/classes/Mask.CloseButton.html +706 -0
  89. data/docs/classes/Mask.CompositeMask.html +721 -0
  90. data/docs/classes/Mask.CornerPart.html +613 -0
  91. data/docs/classes/Mask.Part.html +395 -0
  92. data/docs/classes/Mask.Polling.html +809 -0
  93. data/docs/classes/Mask.Subject.html +199 -0
  94. data/docs/classes/Mask.SubjectMask.html +417 -0
  95. data/docs/classes/Mask.WizardMenu.html +1401 -0
  96. data/docs/classes/SS.html +267 -0
  97. data/docs/classes/SSException.html +203 -0
  98. data/docs/classes/Screen.html +363 -0
  99. data/docs/classes/StepDescription.html +1025 -0
  100. data/docs/classes/StepDescriptionNextButton.html +746 -0
  101. data/docs/classes/VisualItem.html +339 -0
  102. data/docs/classes/Wizard.html +967 -0
  103. data/docs/files/c +0 -0
  104. data/docs/index.html +162 -0
  105. data/example.html +81 -0
  106. data/examples/images/clemenza.jpg +0 -0
  107. data/examples/images/doc_brown.png +0 -0
  108. data/examples/images/forkme.png +0 -0
  109. data/examples/images/fortes-logo.png +0 -0
  110. data/examples/images/sideshow-logo.png +0 -0
  111. data/examples/images/sideshow-logo.svg +155 -0
  112. data/examples/scripts/sideshow.config.js +2 -0
  113. data/examples/scripts/tutorials/introducing_sideshow.js +259 -0
  114. data/examples/stylesheets/example.min.css +1 -0
  115. data/examples/stylesheets/styl/example.styl +272 -0
  116. data/gulp/config.js +0 -0
  117. data/gulp/extensions/gulp-html-extend.js +151 -0
  118. data/gulp/tasks/.gitkeep +0 -0
  119. data/icons/iconfont.zip +0 -0
  120. data/package.json +56 -0
  121. data/sideshow.gemspec +20 -0
  122. data/sideshow.nuspec +72 -0
  123. data/sideshow.sublime-project +22 -0
  124. data/src/copyright_info.js +8 -0
  125. data/src/general/config.js +42 -0
  126. data/src/general/dictionary.js +42 -0
  127. data/src/general/exception.js +15 -0
  128. data/src/general/global_object.js +287 -0
  129. data/src/general/polling.js +151 -0
  130. data/src/general/screen.js +39 -0
  131. data/src/general/utility_functions.js +88 -0
  132. data/src/general/variables.js +42 -0
  133. data/src/interface_itens/fadable_item.js +55 -0
  134. data/src/interface_itens/hidable_item.js +32 -0
  135. data/src/interface_itens/visual_item.js +42 -0
  136. data/src/main.js +52 -0
  137. data/src/mask/composite_mask.js +193 -0
  138. data/src/mask/composite_mask_corner_part.js +105 -0
  139. data/src/mask/composite_mask_part.js +51 -0
  140. data/src/mask/mask.js +6 -0
  141. data/src/mask/subject_mask.js +34 -0
  142. data/src/step/arrow.js +88 -0
  143. data/src/step/arrows.js +155 -0
  144. data/src/step/step_description.js +165 -0
  145. data/src/step/step_description_next_button.js +55 -0
  146. data/src/step/step_details_panel.js +87 -0
  147. data/src/step/subject.js +100 -0
  148. data/src/wizard/wizard.js +395 -0
  149. data/src/wizard/wizard_control_variables.js +95 -0
  150. data/src/wizard/wizard_menu.js +101 -0
  151. data/stylesheets/_animations.styl +87 -0
  152. data/stylesheets/_font-face.styl +135 -0
  153. data/stylesheets/_icons.styl +27 -0
  154. data/stylesheets/_layout.styl +362 -0
  155. data/stylesheets/_mixins.styl +52 -0
  156. data/stylesheets/_variables.styl +35 -0
  157. data/stylesheets/sideshow-fontface.styl +4 -0
  158. data/stylesheets/sideshow.styl +7 -0
  159. data/yuidoc.json +15 -0
  160. metadata +246 -0
data/distr/sideshow.js ADDED
@@ -0,0 +1,2510 @@
1
+ /**
2
+ @license
3
+ Sideshow - An incredible Javascript interactive help Library
4
+ Version: 0.4.1
5
+ Date: 2014-11-27
6
+ Author: Alcides Queiroz [alcidesqueiroz(at)gmail(dot)com]
7
+ Available under Apache License 2.0 (https://raw2.github.com/fortesinformatica/sideshow/master/LICENSE)
8
+ **/
9
+
10
+ ;
11
+ (function (global, $, jazz, markdown) {
12
+ (function (name, module) {
13
+ var ss = module();
14
+
15
+ if (typeof define === 'function' && define.amd) {
16
+ define(module);
17
+ } else {
18
+ global[name] = ss;
19
+ }
20
+ })('sideshow', function () {
21
+ //jQuery is needed
22
+ if ($ === undefined) throw new SSException("2", "jQuery is required for Sideshow to work.");
23
+
24
+ //Jazz is needed
25
+ if (jazz === undefined) throw new SSException("3", "Jazz is required for Sideshow to work.");
26
+
27
+ //Pagedown (the Markdown parser used by Sideshow) is needed
28
+ if (markdown === undefined) throw new SSException("4", "Pagedown (the Markdown parser used by Sideshow) is required for Sideshow to work.");
29
+ var globalObjectName = "Sideshow",
30
+
31
+
32
+ $window,
33
+
34
+ $body,
35
+
36
+ $document,
37
+
38
+ pollingDuration = 150,
39
+
40
+
41
+ longAnimationDuration = 600,
42
+
43
+
44
+
45
+
46
+
47
+ /**
48
+
49
+ The main class for Sideshow
50
+
51
+
52
+
53
+ @class SS
54
+
55
+ @static
56
+
57
+ **/
58
+
59
+
60
+
61
+ SS = {
62
+
63
+ /**
64
+
65
+ The current Sideshow version
66
+
67
+
68
+
69
+ @property VERSION
70
+
71
+ @type String
72
+
73
+ **/
74
+
75
+ get VERSION() {
76
+
77
+ return "0.4.1";
78
+
79
+ }
80
+
81
+ },
82
+
83
+
84
+
85
+
86
+ controlVariables = [],
87
+
88
+
89
+ flags = {
90
+
91
+ lockMaskUpdate: false,
92
+
93
+ changingStep: false,
94
+
95
+ skippingStep: false,
96
+
97
+ running: false
98
+
99
+ },
100
+
101
+
102
+ wizards = [],
103
+
104
+
105
+ currentWizard,
106
+
107
+
108
+
109
+
110
+ /**
111
+
112
+ Possible statuses for an animation
113
+
114
+
115
+
116
+ @@enum AnimationStatus
117
+
118
+ **/
119
+
120
+
121
+
122
+ AnimationStatus = jazz.Enum("VISIBLE", "FADING_IN", "FADING_OUT", "NOT_DISPLAYED", "NOT_RENDERED", "TRANSPARENT");
123
+
124
+
125
+
126
+
127
+ /**
128
+ A custom exception class for Sideshow
129
+
130
+ @class SSException
131
+ @extends Error
132
+ @param {String} code The error code
133
+ @param {String} message The error message
134
+ **/
135
+
136
+ function SSException(code, message) {
137
+ this.name = "SSException";
138
+ this.message = "[SIDESHOW_E#" + ("00000000" + code).substr(-8) + "] " + message;
139
+ }
140
+
141
+ SSException.prototype = new Error();
142
+ SSException.prototype.constructor = SSException;
143
+
144
+ /**
145
+ Shows a warning in a pre-defined format
146
+
147
+ @@function showWarning
148
+ @param {String} code The warning code
149
+ @param {String} message The warning message
150
+ **/
151
+
152
+ function showWarning(code, message) {
153
+ console.warn("[SIDESHOW_W#" + ("00000000" + code).substr(-8) + "] " + message);
154
+ }
155
+
156
+ /**
157
+ Parses a string in the format "#px" in a number
158
+
159
+ @@function parsePxValue
160
+ @param {String} value A value with/without a px unit
161
+ @return Number The number value without unit
162
+ **/
163
+
164
+ function parsePxValue(value) {
165
+ if (value.constructor !== String) return value;
166
+ var br = value === "" ? "0" : value;
167
+ return +br.replace("px", "");
168
+ }
169
+
170
+ /**
171
+ Gets a string from the dictionary in the current language
172
+
173
+ @@function getString
174
+ @param {Object} stringKeyValuePair A string key-value pair in dictionary
175
+ @return String The string value in the current language
176
+ **/
177
+
178
+ function getString(stringKeyValuePair) {
179
+ if (!(SS.config.language in stringKeyValuePair)) {
180
+ showWarning("2001", "String not found for the selected language, getting the first available.");
181
+ return stringKeyValuePair[Object.keys(stringKeyValuePair)[0]];
182
+ }
183
+
184
+ return stringKeyValuePair[SS.config.language];
185
+ }
186
+
187
+ /**
188
+ Registers hotkeys to be used when running Sideshow
189
+
190
+ @@function registerInnerHotkeys
191
+ **/
192
+
193
+ function registerInnerHotkeys() {
194
+ $document.keyup(innerHotkeysListener);
195
+ }
196
+
197
+ /**
198
+ Unregisters hotkeys used when running Sideshow
199
+
200
+ @@function Unregisters
201
+ **/
202
+
203
+ function unregisterInnerHotkeys() {
204
+ $document.unbind("keyup", innerHotkeysListener);
205
+ }
206
+
207
+ function innerHotkeysListener(e) {
208
+ //Esc or F1
209
+ if (e.keyCode == 27 || e.keyCode == 112) SS.close();
210
+ }
211
+
212
+ /**
213
+ Registers global hotkeys
214
+
215
+ @@function registerGlobalHotkeys
216
+ **/
217
+
218
+ function registerGlobalHotkeys() {
219
+ $document.keyup(function (e) {
220
+ //F2
221
+ if (e.keyCode == 113) {
222
+ if (e.shiftKey) SS.start({
223
+ listAll: true
224
+ });
225
+ else SS.start();
226
+ }
227
+ });
228
+ }
229
+
230
+ /**
231
+ Removes nodes created by Sideshow (except mask, which remains due to performance reasons when recalling Sideshow)
232
+
233
+ @@function removeDOMGarbage
234
+ **/
235
+
236
+ function removeDOMGarbage() {
237
+ $("[class*=\"sideshow\"]").not(".sideshow-mask-part, .sideshow-mask-corner-part, .sideshow-subject-mask").remove();
238
+ }
239
+
240
+ /**
241
+ Strings Dictionary
242
+
243
+ @@object strings
244
+ **/
245
+ var strings = {
246
+ availableWizards: {
247
+ "en": "Available Tutorials",
248
+ "pt-br": "Tutoriais Disponíveis",
249
+ "es": "Tutoriales Disponibles"
250
+ },
251
+ relatedWizards: {
252
+ "en": "Related Wizards",
253
+ "pt-br": "Tutoriais Relacionados",
254
+ "es": "Tutoriales Relacionados"
255
+ },
256
+ noAvailableWizards: {
257
+ "en": "There's no tutorials available.",
258
+ "pt-br": "Não há tutoriais disponíveis para esta tela.",
259
+ "es": "No hay tutoriales disponibles."
260
+ },
261
+ close: {
262
+ "en": "Close",
263
+ "pt-br": "Fechar",
264
+ "es": "Cerrar"
265
+ },
266
+ estimatedTime: {
267
+ "en": "Estimated Time",
268
+ "pt-br": "Tempo Estimado",
269
+ "es": "Tiempo Estimado"
270
+ },
271
+ next: {
272
+ "en": "Next",
273
+ "pt-br": "Continuar",
274
+ "es": "Continuar"
275
+ },
276
+ finishWizard: {
277
+ "en": "Finish Wizard",
278
+ "pt-br": "Concluir Tutorial",
279
+ "es": "Concluir Tutorial"
280
+ }
281
+ };
282
+ /**
283
+ Sideshow Settings
284
+
285
+ @@object config
286
+ **/
287
+ SS.config = {};
288
+
289
+ /**
290
+ Application route to persists user preferences
291
+
292
+ @@field userPreferencesRoute
293
+ @type String
294
+ @@unused
295
+ @@todo Implement persistence logic
296
+ **/
297
+ SS.config.userPreferencesRoute = null;
298
+
299
+ /**
300
+ Logged in user
301
+
302
+ @@field loggedInUser
303
+ @type String
304
+ @@unused
305
+ **/
306
+ SS.config.loggedInUser = null;
307
+
308
+ /**
309
+ Chosen language for sideshow interface
310
+
311
+ @@field language
312
+ @type String
313
+ **/
314
+ SS.config.language = "en";
315
+
316
+ /**
317
+ Defines if the intro screen (the tutorial list) will be skipped when there's just one
318
+ tutorial available. This way, when Sideshow is invoked, the first step is directly shown.
319
+
320
+ @@field autoSkipIntro
321
+ @type boolean
322
+ **/
323
+ SS.config.autoSkipIntro = false;
324
+
325
+ /**
326
+ Stores the variables used in step evaluators
327
+
328
+ @class ControlVariables
329
+ @static
330
+ **/
331
+ SS.ControlVariables = {};
332
+
333
+ /**
334
+ Sets a variable value
335
+
336
+ @method set
337
+ @param {String} name The variable name
338
+ @param {String} value The variable value
339
+ @return {String} A formatted key=value pair representing the defined variable
340
+ **/
341
+ SS.ControlVariables.set = function (name, value) {
342
+ var variable = {};
343
+ if (this.isDefined(name)) {
344
+ variable = this.getNameValuePair(name);
345
+ } else controlVariables.push(variable);
346
+
347
+ variable.name = name;
348
+ variable.value = value;
349
+ return name + "=" + value;
350
+ };
351
+
352
+ /**
353
+ Sets a variable if not defined yet
354
+
355
+ @method setIfUndefined
356
+ @param {String} name The variable name
357
+ @param {String} value The variable value
358
+ @return {String} A formatted key=value pair representing the defined variable
359
+ **/
360
+ SS.ControlVariables.setIfUndefined = function (name, value) {
361
+ if (!this.isDefined(name)) return this.set(name, value);
362
+ };
363
+
364
+ /**
365
+ Checks if some variable is already defined
366
+
367
+ @method isDefined
368
+ @param {String} name The variable name
369
+ @return {boolean} A boolean indicating if the variable is already defined
370
+ **/
371
+ SS.ControlVariables.isDefined = function (name) {
372
+ return this.getNameValuePair(name) !== undefined;
373
+ };
374
+
375
+ /**
376
+ Gets a variable value
377
+
378
+ @method get
379
+ @param {String} name The variable name
380
+ @return {any} The variable value
381
+ **/
382
+ SS.ControlVariables.get = function (name) {
383
+ var pair = this.getNameValuePair(name);
384
+ return pair ? pair.value : undefined;
385
+ };
386
+
387
+ /**
388
+ Gets a pair with name and value
389
+
390
+ @method getNameValuePair
391
+ @param {String} name The variable name
392
+ @return {Object} A pair with the variable name and value
393
+ **/
394
+ SS.ControlVariables.getNameValuePair = function (name) {
395
+ for (var i = 0; i < controlVariables.length; i++) {
396
+ var variable = controlVariables[i];
397
+ if (variable.name === name) return variable;
398
+ }
399
+ };
400
+
401
+ /**
402
+ Remove some variable from the control variables collection
403
+
404
+ @method remove
405
+ @param {String} name The variable name
406
+ @return {Object} A pair with the removed variable name and value
407
+ **/
408
+ SS.ControlVariables.remove = function (name) {
409
+ return controlVariables.splice(controlVariables.indexOf(this.getNameValuePair(name)), 1);
410
+ };
411
+
412
+ /**
413
+ Clear the control variables collection
414
+
415
+ @method clear
416
+ **/
417
+ SS.ControlVariables.clear = function () {
418
+ controlVariables = [];
419
+ };
420
+
421
+ /**
422
+ A visual item
423
+
424
+ @class VisualItem
425
+ @@abstract
426
+ **/
427
+ var VisualItem = jazz.Class().abstract;
428
+
429
+ /**
430
+ The jQuery wrapped DOM element for the visual item
431
+
432
+ @@field $el
433
+ @type Object
434
+ **/
435
+ VisualItem.field("$el");
436
+
437
+ /**
438
+ The jQuery wrapped DOM element for the visual item
439
+
440
+ @@field $el
441
+ @type AnimationStatus
442
+ **/
443
+ VisualItem.field("status", AnimationStatus.NOT_RENDERED);
444
+
445
+ /**
446
+ Renders the item's DOM object
447
+
448
+ @method render
449
+ **/
450
+ VisualItem.method("render", function ($parent) {
451
+ ($parent || $body).append(this.$el);
452
+ this.status = AnimationStatus.NOT_DISPLAYED;
453
+ });
454
+
455
+ /**
456
+ Destroys the item's DOM object
457
+
458
+ @method destroy
459
+ **/
460
+ VisualItem.method("destroy", function () {
461
+ this.$el.remove();
462
+ });
463
+ /**
464
+ A visual item which can be shown and hidden
465
+
466
+ @class HidableItem
467
+ @@abstract
468
+ @extends VisualItem
469
+ **/
470
+ var HidableItem = jazz.Class().extending(VisualItem).abstract;
471
+
472
+ /**
473
+ Shows the visual item
474
+
475
+ @method show
476
+ @param {boolean} displayButKeepTransparent The item will hold space but keep invisible
477
+ **/
478
+ HidableItem.method("show", function (displayButKeepTransparent) {
479
+ if (!this.$el) this.render();
480
+ if (!displayButKeepTransparent) this.$el.removeClass("sideshow-invisible");
481
+ this.$el.removeClass("sideshow-hidden");
482
+ this.status = AnimationStatus.VISIBLE;
483
+ });
484
+
485
+ /**
486
+ Hides the visual item
487
+
488
+ @method hide
489
+ **/
490
+ HidableItem.method("hide", function (keepHoldingSpace) {
491
+ if (!keepHoldingSpace) this.$el.addClass("sideshow-hidden");
492
+ this.$el.addClass("sideshow-invisible");
493
+ this.status = AnimationStatus.NOT_DISPLAYED;
494
+ });
495
+ /**
496
+ A visual item which holds fading in and out capabilities
497
+
498
+ @class FadableItem
499
+ @@abstract
500
+ @extends HidableItem
501
+ **/
502
+ var FadableItem = jazz.Class().extending(HidableItem).abstract;
503
+
504
+ /**
505
+ Does a fade in transition for the visual item
506
+
507
+ @method fadeIn
508
+ **/
509
+ FadableItem.method("fadeIn", function (callback, linearTimingFunction) {
510
+ var item = this;
511
+ item.status = AnimationStatus.FADING_IN;
512
+
513
+ if (!item.$el) this.render();
514
+ if (linearTimingFunction) item.$el.css("animation-timing-function", "linear");
515
+ item.$el.removeClass("sideshow-hidden");
516
+
517
+ //Needed hack to get CSS transition to work properly
518
+ setTimeout(function () {
519
+ item.$el.removeClass("sideshow-invisible");
520
+
521
+ setTimeout(function () {
522
+ item.status = AnimationStatus.VISIBLE;
523
+ if (linearTimingFunction) item.$el.css("animation-timing-function", "ease");
524
+ if (callback) callback();
525
+ }, longAnimationDuration);
526
+ }, 20); //<-- Yeap, I'm really scheduling a timeout for 20 milliseconds... this is a dirty trick =)
527
+ });
528
+
529
+ /**
530
+ Does a fade out transition for the visual item
531
+
532
+ @method fadeOut
533
+ **/
534
+ FadableItem.method("fadeOut", function (callback, linearTimingFunction) {
535
+ var item = this;
536
+ if (item.status != AnimationStatus.NOT_RENDERED) {
537
+ item.status = AnimationStatus.FADING_OUT;
538
+
539
+ if (linearTimingFunction) item.$el.css("animation-timing-function", "linear");
540
+ item.$el.addClass("sideshow-invisible");
541
+
542
+ setTimeout(function () {
543
+ item.$el.addClass("sideshow-hidden");
544
+ item.status = AnimationStatus.NOT_DISPLAYED;
545
+ if (linearTimingFunction) item.$el.css("animation-timing-function", "ease");
546
+ if (callback) callback();
547
+ }, longAnimationDuration);
548
+ }
549
+ });
550
+
551
+ /**
552
+ Represents a tutorial
553
+
554
+ @class Wizard
555
+ @@initializer
556
+ @param {Object} wizardConfig The wizard configuration object
557
+ **/
558
+ var Wizard = jazz.Class(function (wizardConfig) {
559
+ this.name = wizardConfig.name;
560
+ this.title = wizardConfig.title;
561
+ this.description = wizardConfig.description;
562
+ this.estimatedTime = wizardConfig.estimatedTime;
563
+ this.affects = wizardConfig.affects;
564
+ this.preparation = wizardConfig.preparation;
565
+ this.listeners = wizardConfig.listeners;
566
+ this.showStepPosition = wizardConfig.showStepPosition;
567
+ this.relatedWizards = wizardConfig.relatedWizards;
568
+ });
569
+
570
+ /**
571
+ A function to prepare the environment for running a wizard (e.g. redirecting to some screen)
572
+
573
+ @@field preparation
574
+ @type Function
575
+ **/
576
+ Wizard.field("preparation");
577
+
578
+ /**
579
+ An object with listeners to this wizard (e.g. beforeWizardStarts, afterWizardEnds)
580
+
581
+ @@field listeners
582
+ @type Object
583
+ **/
584
+ Wizard.field("listeners");
585
+
586
+ /**
587
+ A configuration flag that defines if the step position (e.g. 2/10, 3/15, 12/12) will be shown
588
+
589
+ @@field showStepPosition
590
+ @type boolean
591
+ **/
592
+ Wizard.field("showStepPosition");
593
+
594
+ /**
595
+ An array with related wizards names. These wizards are listed after the ending of the current wizard.
596
+
597
+ @@field relatedWizards
598
+ @type Array
599
+ **/
600
+ Wizard.field("relatedWizards");
601
+
602
+ /**
603
+ The wizard unique name (used internally as an identifier)
604
+
605
+ @@field name
606
+ @type String
607
+ **/
608
+ Wizard.field("name");
609
+
610
+ /**
611
+ The wizard title (will be shown in the list of available wizards)
612
+
613
+ @@field title
614
+ @type String
615
+ **/
616
+ Wizard.field("title");
617
+
618
+ /**
619
+ The wizard description (will be shown in the list of available wizards)
620
+
621
+ @@field description
622
+ @type String
623
+ **/
624
+ Wizard.field("description");
625
+
626
+ /**
627
+ The wizard estimated completion time (will be shown in the list of available wizards)
628
+
629
+ @@field estimatedTime
630
+ @type String
631
+ **/
632
+ Wizard.field("estimatedTime");
633
+
634
+ /**
635
+ A collection of rules to infer whether a wizard should be available in a specific screen
636
+
637
+ @@field affects
638
+ @type Array
639
+ **/
640
+ Wizard.field("affects");
641
+
642
+ /**
643
+ The sequence of steps for this wizard
644
+
645
+ @@field storyline
646
+ @private
647
+ @type Object
648
+ **/
649
+ Wizard.field("_storyline");
650
+
651
+ /**
652
+ Points to the current step object in a playing wizard
653
+
654
+ @@field currentStep
655
+ @type Object
656
+ **/
657
+ Wizard.field("currentStep");
658
+
659
+ /**
660
+ Sets the storyline for the wizard
661
+
662
+ @method storyLine
663
+ **/
664
+ Wizard.method("storyLine", function (storyline) {
665
+ this._storyline = storyline;
666
+ });
667
+
668
+ /**
669
+ Runs the wizard
670
+
671
+ @method play
672
+ **/
673
+ Wizard.method("play", function () {
674
+ var wiz = this;
675
+
676
+ Polling.enqueue("check_composite_mask_subject_changes", function () {
677
+ Mask.CompositeMask.singleInstance.pollForSubjectChanges();
678
+ });
679
+
680
+ Polling.enqueue("check_arrow_changes", function () {
681
+ Arrows.pollForArrowsChanges(true);
682
+ });
683
+
684
+ //Checks if the wizard has a storyline
685
+ if (!this._storyline) throw new SSException("201", "A wizard needs to have a storyline.");
686
+ var steps = this._storyline.steps;
687
+
688
+ //Checks if the storyline has at least one step
689
+ if (steps.length === 0) throw new SSException("202", "A storyline must have at least one step.");
690
+
691
+ DetailsPanel.singleInstance.render();
692
+
693
+ StepDescription.singleInstance.render();
694
+
695
+ var listeners = this.listeners;
696
+ if (listeners && listeners.beforeWizardStarts) listeners.beforeWizardStarts();
697
+
698
+ flags.changingStep = true;
699
+ this.showStep(steps[0], function () {
700
+ //Releases the polling for checking any changes in the current subject
701
+ //flags.lockMaskUpdate = false;
702
+
703
+ //Register the function that checks the completing of a step in the polling queue
704
+ Polling.enqueue("check_completed_step", function () {
705
+ wiz.pollForCheckCompletedStep();
706
+ });
707
+ });
708
+
709
+ Mask.CompositeMask.singleInstance.fadeIn();
710
+ });
711
+
712
+ /**
713
+ Shows a specific step
714
+
715
+ @method showStep
716
+ @param {Object} step The step to be shown
717
+ @param {Function} callback A callback function to be called
718
+ **/
719
+ Wizard.method("showStep", function (step, callback) {
720
+ var wizard = this;
721
+ flags.skippingStep = false;
722
+
723
+ Arrows.clear();
724
+
725
+ if (this.currentStep && this.currentStep.listeners && this.currentStep.listeners.afterStep) this.currentStep.listeners.afterStep();
726
+
727
+ function skipStep(wiz) {
728
+ flags.skippingStep = true;
729
+ wizard.next();
730
+ }
731
+
732
+ if (step && step.listeners && step.listeners.beforeStep) step.listeners.beforeStep();
733
+
734
+ //The shown step is, of course, the current
735
+ this.currentStep = step;
736
+
737
+ //If the step has a skipIf evaluator and it evaluates to true, we'll skip to the next step!
738
+ if (step.skipIf && step.skipIf()) skipStep(this);
739
+
740
+ if (flags.changingStep && !flags.skippingStep) {
741
+ //Sets the current subject and updates its dimension and position
742
+ if (step.subject) SS.setSubject(step.subject);
743
+ else SS.setEmptySubject();
744
+ //Updates the mask
745
+ Mask.CompositeMask.singleInstance.update(Subject.position, Subject.dimension, Subject.borderRadius);
746
+
747
+ var sm = Mask.SubjectMask.singleInstance;
748
+ sm.fadeOut(function () {
749
+ if (step.lockSubject) sm.show(true);
750
+ });
751
+ //The details panel (that wraps the step description and arrow) is shown
752
+ DetailsPanel.singleInstance.show();
753
+ //Repositionate the details panel depending on the remaining space in the screen
754
+ DetailsPanel.singleInstance.positionate();
755
+ //Sets the description properties (text, title and step position)
756
+ var description = StepDescription.singleInstance;
757
+ var text = step.text;
758
+ text = text instanceof Function ? SS.heredoc(text) : text;
759
+ if (step.format == "markdown") {
760
+ description.setHTML(new markdown.Converter().makeHtml(text));
761
+ } else description.setText(text);
762
+
763
+ description.setTitle(step.title);
764
+ description.setStepPosition((this.getStepPosition() + 1) + "/" + this._storyline.steps.length);
765
+ //If this step doesn't have its own passing conditions/evaluators, or the flag "showNextButton" is true, then, the button is visible
766
+ if (step.showNextButton || step.autoContinue === false || !(step.completingConditions && step.completingConditions.length > 0)) {
767
+ var nextStep = this._storyline.steps[this.getStepPosition() + 1];
768
+ if (nextStep) {
769
+ description.nextButton.setText(getString(strings.next) + ": " + this._storyline.steps[this.getStepPosition() + 1].title);
770
+ } else {
771
+ description.nextButton.setText(getString(strings.finishWizard));
772
+ }
773
+ description.nextButton.show();
774
+
775
+ if (step.autoContinue === false) description.nextButton.disable();
776
+ } else {
777
+ description.nextButton.hide();
778
+ }
779
+
780
+ if (step.targets && step.targets.length > 0) {
781
+ Arrows.setTargets(step.targets);
782
+ Arrows.render();
783
+ Arrows.positionate();
784
+ Arrows.fadeIn();
785
+ }
786
+
787
+ //Step Description is shown, but is transparent yet (since we need to know its dimension to positionate it properly)
788
+ description.show(true);
789
+ if (!Mask.CompositeMask.singleInstance.scrollIfNecessary(Subject.position, Subject.dimension)) {
790
+ description.positionate();
791
+ //Do a simple fade in for the description box
792
+ description.fadeIn();
793
+ }
794
+
795
+
796
+ //If a callback is passed, call it
797
+ if (callback) callback();
798
+ flags.changingStep = false;
799
+ }
800
+ });
801
+
802
+ /**
803
+ Shows the next step of the wizard
804
+
805
+ @method next
806
+ @param {Function} callback A callback function to be called
807
+ **/
808
+ Wizard.method("next", function (callback, nextStep) {
809
+ if (!flags.changingStep || flags.skippingStep) {
810
+ flags.changingStep = true;
811
+ var currentStep = this.currentStep;
812
+ nextStep = nextStep || this._storyline.steps[this.getStepPosition(this.currentStep) + 1];
813
+ var self = this;
814
+
815
+ this.hideStep(function () {
816
+ if (nextStep) self.showStep(nextStep, function () {
817
+ if (callback) callback();
818
+ });
819
+ else {
820
+ if (currentStep && currentStep.listeners && currentStep.listeners.afterStep) currentStep.listeners.afterStep();
821
+
822
+ var completedWizard = currentWizard;
823
+ currentWizard = null;
824
+ var listeners = self.listeners;
825
+ if (listeners && listeners.afterWizardEnds) listeners.afterWizardEnds();
826
+
827
+ if (!SS.showRelatedWizardsList(completedWizard)) SS.close();
828
+ }
829
+ });
830
+ }
831
+ });
832
+
833
+ /**
834
+ Hides the step
835
+
836
+ @method hideStep
837
+ @param {Function} callback A callback function to be called in the ending of the hiding process
838
+ **/
839
+ Wizard.method("hideStep", function (callback) {
840
+ StepDescription.singleInstance.fadeOut(function () {
841
+ DetailsPanel.singleInstance.hide();
842
+ });
843
+ Arrows.fadeOut();
844
+ Mask.SubjectMask.singleInstance.update(Subject.position, Subject.dimension, Subject.borderRadius);
845
+ Mask.SubjectMask.singleInstance.fadeIn(callback);
846
+ });
847
+
848
+ /**
849
+ Returns the position of the step passed as argument or (by default) the current step
850
+
851
+ @method getStepPosition
852
+ @param {Object} step The step object to get position
853
+ **/
854
+ Wizard.method("getStepPosition", function (step) {
855
+ return this._storyline.steps.indexOf(step || this.currentStep);
856
+ });
857
+
858
+ /**
859
+ Checks if a wizard should be shown in the current context (running each evaluator defined for this wizard)
860
+
861
+ @method isEligible
862
+ @return {boolean} A boolean indicating if this wizard should be available in the current context
863
+ **/
864
+ Wizard.method("isEligible", function () {
865
+ var l = global.location;
866
+
867
+ function isEqual(a, b, caseSensitive) {
868
+ return (caseSensitive) ? a === b : a.toLowerCase() === b.toLowerCase();
869
+ }
870
+
871
+ for (var c = 0; c < this.affects.length; c++) {
872
+ var condition = this.affects[c];
873
+ if (condition instanceof Function) {
874
+ if (condition()) return true;
875
+ } else if (condition instanceof Object) {
876
+ if ("route" in condition) {
877
+ var route = l.pathname + l.search + l.hash;
878
+ if (isEqual(route, condition.route, condition.caseSensitive)) return true;
879
+ }
880
+
881
+ if ("hash" in condition) {
882
+ if (isEqual(location.hash, condition.hash, condition.caseSensitive)) return true;
883
+ }
884
+
885
+ if ("url" in condition) {
886
+ if (isEqual(location.href, condition.url, condition.caseSensitive)) return true;
887
+ }
888
+ }
889
+ }
890
+ return false;
891
+ });
892
+
893
+ /**
894
+ Checks if the current user already watched this wizard
895
+
896
+ @method isAlreadyWatched
897
+ @return {boolean} A boolean indicating if the user watched this wizard
898
+ @@todo Implement this method...
899
+ **/
900
+ Wizard.method("isAlreadyWatched", function () {
901
+ //ToDo
902
+ return false;
903
+ });
904
+
905
+ /**
906
+ A Polling function to check if the current step is completed
907
+
908
+ @method pollForCheckCompletedStep
909
+ **/
910
+ Wizard.method("pollForCheckCompletedStep", function () {
911
+ var conditions = this.currentStep.completingConditions;
912
+ if (conditions && conditions.length > 0 && !flags.skippingStep) {
913
+ var completed = true;
914
+ for (var fn = 0; fn < conditions.length; fn++) {
915
+ var completingCondition = conditions[fn];
916
+ if (!completingCondition()) completed = false;
917
+ }
918
+
919
+ if (completed) {
920
+ if (this.currentStep.autoContinue === false) StepDescription.singleInstance.nextButton.enable();
921
+ else currentWizard.next();
922
+ }
923
+ }
924
+ });
925
+
926
+
927
+ Wizard.method("prepareAndPlay", function () {
928
+ currentWizard = this;
929
+
930
+ if (!this.isEligible()) {
931
+ if (this.preparation) this.preparation(function () {
932
+ currentWizard.play();
933
+ });
934
+ else throw new SSException("203", "This wizard is not eligible neither has a preparation function.");
935
+ } else this.play();
936
+ });
937
+
938
+ /**
939
+ The panel that holds step description, is positionated over the biggest remaining space among the four parts of a composite mask
940
+
941
+ @class DetailsPanel
942
+ @@singleton
943
+ @extends FadableItem
944
+ **/
945
+ var DetailsPanel = jazz.Class().extending(FadableItem).singleton;
946
+
947
+ /**
948
+ An object holding dimension information for the Details Panel
949
+
950
+ @@field dimension
951
+ @type Object
952
+ **/
953
+ DetailsPanel.field("dimension", {});
954
+
955
+ /**
956
+ An object holding positioning information for the Details Panel
957
+
958
+ @@field position
959
+ @type Object
960
+ **/
961
+ DetailsPanel.field("position", {});
962
+
963
+ /**
964
+ Renders the Details Panel
965
+
966
+ @method render
967
+ **/
968
+ DetailsPanel.method("render", function () {
969
+ this.$el = $("<div>").addClass("sideshow-details-panel").addClass("sideshow-hidden");
970
+ this.callSuper("render");
971
+ });
972
+
973
+ /**
974
+ Positionates the panel automatically, calculating the biggest available area and putting the panel over there
975
+
976
+ @method positionate
977
+ **/
978
+ DetailsPanel.method("positionate", function () {
979
+ var parts = Mask.CompositeMask.singleInstance.parts;
980
+
981
+ //Considering the four parts surrounding the current subject, gets the biggest one
982
+ var sortedSides = [
983
+ [parts.top, "height"],
984
+ [parts.right, "width"],
985
+ [parts.bottom, "height"],
986
+ [parts.left, "width"]
987
+ ].sort(function (a, b) {
988
+ return a[0].dimension[a[1]] - b[0].dimension[b[1]];
989
+ });
990
+
991
+ var biggestSide = sortedSides.slice(-1)[0];
992
+
993
+ for (var i = 2; i > 0; i--) {
994
+ var side = sortedSides[i];
995
+ var dimension = side[0].dimension;
996
+ if (dimension.width > 250 && dimension.height > 250) {
997
+ if ((dimension.width + dimension.height) > ((biggestSide[0].dimension.width + biggestSide[0].dimension.height) * 2)) biggestSide = side;
998
+ }
999
+ }
1000
+
1001
+ if (biggestSide[1] == "width") {
1002
+ this.$el.css("left", biggestSide[0].position.x).css("top", 0).css("height", Screen.dimension.height).css("width", biggestSide[0].dimension.width);
1003
+ } else {
1004
+ this.$el.css("left", 0).css("top", biggestSide[0].position.y).css("height", biggestSide[0].dimension.height).css("width", Screen.dimension.width);
1005
+ }
1006
+
1007
+ this.dimension = {
1008
+ width: parsePxValue(this.$el.css("width")),
1009
+ height: parsePxValue(this.$el.css("height"))
1010
+ };
1011
+
1012
+ this.position = {
1013
+ x: parsePxValue(this.$el.css("left")),
1014
+ y: parsePxValue(this.$el.css("top"))
1015
+ };
1016
+ });
1017
+
1018
+
1019
+ /**
1020
+ Class representing all the current shown arrows
1021
+
1022
+ @class Arrows
1023
+ @static
1024
+ **/
1025
+ var Arrows = {};
1026
+
1027
+ Arrows.arrows = [];
1028
+
1029
+ /**
1030
+ Clear the currently defined arrows
1031
+
1032
+ @method clear
1033
+ @static
1034
+ **/
1035
+ Arrows.clear = function () {
1036
+ this.arrows = [];
1037
+ };
1038
+
1039
+ /**
1040
+ Sets the targets for arrows to point
1041
+
1042
+ @method setTargets
1043
+ @static
1044
+ **/
1045
+ Arrows.setTargets = function (targets, targetsChanged) {
1046
+ if (targets.constructor === String) targets = $(targets);
1047
+
1048
+ if (targets instanceof $ && targets.length > 0) {
1049
+ targets.each(function () {
1050
+ var arrow = Arrow.build();
1051
+ arrow.target.$el = $(this);
1052
+ if (arrow.target.$el.is(":visible")) {
1053
+ Arrows.arrows.push(arrow);
1054
+ arrow.onceVisible = true;
1055
+ }
1056
+ });
1057
+ }
1058
+ else if (!targetsChanged) throw new SSException("150", "Invalid targets.");
1059
+ };
1060
+
1061
+ Arrows.recreateDOMReferences = function () {
1062
+ for (var a = 0; a < this.arrows.length; a++) {
1063
+ var arrow = this.arrows[a];
1064
+ arrow.$el.remove();
1065
+ }
1066
+
1067
+ Arrows.clear();
1068
+ Arrows.setTargets(currentWizard.currentStep.targets, true);
1069
+ Arrows.render();
1070
+ Arrows.positionate();
1071
+ Arrows.show();
1072
+ };
1073
+
1074
+ /**
1075
+ Iterates over the arrows collection showing each arrow
1076
+
1077
+ @method show
1078
+ @static
1079
+ **/
1080
+ Arrows.show = function () {
1081
+ for (var a = 0; a < this.arrows.length; a++) {
1082
+ var arrow = this.arrows[a];
1083
+ arrow.show();
1084
+ }
1085
+ };
1086
+
1087
+ /**
1088
+ Iterates over the arrows collection hiding each arrow
1089
+
1090
+ @method hide
1091
+ @static
1092
+ **/
1093
+ Arrows.hide = function () {
1094
+ for (var a = 0; a < this.arrows.length; a++) {
1095
+ var arrow = this.arrows[a];
1096
+ arrow.hide();
1097
+ }
1098
+ };
1099
+
1100
+ /**
1101
+ Iterates over the arrows collection fading in each arrow
1102
+
1103
+ @method fadeIn
1104
+ @static
1105
+ **/
1106
+ Arrows.fadeIn = function () {
1107
+ for (var a = 0; a < this.arrows.length; a++) {
1108
+ var arrow = this.arrows[a];
1109
+ arrow.fadeIn();
1110
+ }
1111
+ };
1112
+
1113
+ /**
1114
+ Iterates over the arrows collection fading out each arrow
1115
+
1116
+ @method fadeOut
1117
+ @static
1118
+ **/
1119
+ Arrows.fadeOut = function () {
1120
+ for (var a = 0; a < this.arrows.length; a++) {
1121
+ var arrow = this.arrows[a];
1122
+ registerFadeOut(arrow);
1123
+ }
1124
+
1125
+ function registerFadeOut(arrow) {
1126
+ arrow.fadeOut(function () {
1127
+ arrow.destroy();
1128
+ });
1129
+ }
1130
+ };
1131
+
1132
+ /**
1133
+ Iterates over the arrows collection repositionating each arrow
1134
+
1135
+ @method positionate
1136
+ @static
1137
+ **/
1138
+ Arrows.positionate = function () {
1139
+ for (var a = 0; a < this.arrows.length; a++) {
1140
+ var arrow = this.arrows[a];
1141
+ arrow.positionate();
1142
+ }
1143
+ };
1144
+
1145
+ /**
1146
+ Iterates over the arrows collection rendering each arrow
1147
+
1148
+ @method render
1149
+ @static
1150
+ **/
1151
+ Arrows.render = function () {
1152
+ for (var a = 0; a < this.arrows.length; a++) {
1153
+ var arrow = this.arrows[a];
1154
+ arrow.render();
1155
+ }
1156
+ };
1157
+
1158
+ /**
1159
+ A Polling function to check if arrows coordinates has changed
1160
+
1161
+ @method pollForArrowsChanges
1162
+ **/
1163
+ Arrows.pollForArrowsChanges = function () {
1164
+ var brokenReference = false;
1165
+ for (var a = 0; a < this.arrows.length; a++) {
1166
+ var arrow = this.arrows[a];
1167
+ if (arrow.hasChanged()) arrow.positionate();
1168
+ if (arrow.onceVisible && !arrow.target.$el.is(":visible")) brokenReference = true;
1169
+ }
1170
+
1171
+ if (brokenReference) this.recreateDOMReferences();
1172
+ };
1173
+
1174
+ /**
1175
+ A single arrow for pointing individual items in current subject
1176
+
1177
+ @class Arrow
1178
+ **/
1179
+ var Arrow = jazz.Class().extending(FadableItem);
1180
+
1181
+ /**
1182
+ The jQuery wrapped object which will be pointed by this arrow
1183
+
1184
+ @@field target
1185
+ @type Object
1186
+ **/
1187
+ Arrow.field("target", {});
1188
+
1189
+ /**
1190
+ Flag created to set if the arrow was visible once, this is used for recreating references to the targets DOM objects
1191
+
1192
+ @@field onceVisible
1193
+ @type Object
1194
+ **/
1195
+ Arrow.field("onceVisible", false);
1196
+
1197
+ /**
1198
+ Renders the Arrow
1199
+
1200
+ @method render
1201
+ **/
1202
+ Arrow.method("render", function () {
1203
+ this.$el = $("<div>").addClass("sideshow-subject-arrow").addClass("sideshow-hidden").addClass("sideshow-invisible");
1204
+ this.callSuper("render");
1205
+ });
1206
+
1207
+ /**
1208
+ Positionates the Arrow according to its target
1209
+
1210
+ @method positionate
1211
+ **/
1212
+ Arrow.method("positionate", function () {
1213
+ var target = this.target;
1214
+ target.position = {
1215
+ x: target.$el.offset().left - $window.scrollLeft(),
1216
+ y: target.$el.offset().top - $window.scrollTop()
1217
+ };
1218
+ target.dimension = {
1219
+ width: target.$el.outerWidth(),
1220
+ height: target.$el.outerHeight()
1221
+ };
1222
+
1223
+ this.$el.css("top", target.position.y - 30 + "px").css("left", target.position.x + (parsePxValue(target.dimension.width) / 2) - 12 + "px");
1224
+ });
1225
+
1226
+ /**
1227
+ Shows the Arrow
1228
+
1229
+ @method show
1230
+ **/
1231
+ Arrow.method("show", function () {
1232
+ this.callSuper("show");
1233
+ this.positionate();
1234
+ });
1235
+
1236
+ /**
1237
+ Does a fade in transition in the Arrow
1238
+
1239
+ @method fadeIn
1240
+ **/
1241
+ Arrow.method("fadeIn", function () {
1242
+ this.callSuper("fadeIn");
1243
+ this.positionate();
1244
+ });
1245
+
1246
+ /**
1247
+ Checks if the arrow's target position or dimension has changed
1248
+
1249
+ @method hasChanged
1250
+ @return boolean
1251
+ **/
1252
+ Arrow.method("hasChanged", function () {
1253
+ return (this.target.dimension.width !== this.target.$el.outerWidth() || this.target.dimension.height !== this.target.$el.outerHeight() || this.target.position.y !== (this.target.$el.offset().top - $window.scrollTop()) || this.target.position.x !== (this.target.$el.offset().left - $window.scrollLeft()));
1254
+ });
1255
+ /**
1256
+ Represents a panel holding the step description
1257
+
1258
+ @class StepDescription
1259
+ @extends FadableItem
1260
+ @@initializer
1261
+ **/
1262
+ var StepDescription = jazz.Class(function () {
1263
+ this.nextButton = StepDescriptionNextButton.build();
1264
+ }).extending(FadableItem).singleton;
1265
+
1266
+ /**
1267
+ The step description text content
1268
+
1269
+ @@field text
1270
+ @type String
1271
+ **/
1272
+ StepDescription.field("text", "");
1273
+
1274
+ /**
1275
+ The title text for the step description panel
1276
+
1277
+ @@field title
1278
+ @type String
1279
+ **/
1280
+ StepDescription.field("title", "");
1281
+
1282
+ /**
1283
+ An object holding dimension information for the Step Description panel
1284
+
1285
+ @@field dimension
1286
+ @type Object
1287
+ **/
1288
+ StepDescription.field("dimension", {});
1289
+
1290
+ /**
1291
+ An object holding positioning information for the Step Description panel
1292
+
1293
+ @@field position
1294
+ @type Object
1295
+ **/
1296
+ StepDescription.field("position", {});
1297
+
1298
+ /**
1299
+ An object representing the next button for a step description panel
1300
+
1301
+ @@field nextButton
1302
+ @type Object
1303
+ **/
1304
+ StepDescription.field("nextButton");
1305
+
1306
+ /**
1307
+ Sets the text for the step description panel
1308
+
1309
+ @method setText
1310
+ @param {String} text The text for the step description panel
1311
+ **/
1312
+ StepDescription.method("setText", function (text) {
1313
+ this.text = text;
1314
+ this.$el.find(".sideshow-step-text").text(text);
1315
+ });
1316
+
1317
+ /**
1318
+ Sets the HTML content for the step description panel
1319
+
1320
+ @method setHTML
1321
+ @param {String} text The HTML content for step description panel
1322
+ **/
1323
+ StepDescription.method("setHTML", function (text) {
1324
+ this.text = text;
1325
+ this.$el.find(".sideshow-step-text").html(text);
1326
+ });
1327
+
1328
+ /**
1329
+ Sets the title for the step description panel
1330
+
1331
+ @method setTitle
1332
+ @param {String} title The text for the step description panel
1333
+ **/
1334
+ StepDescription.method("setTitle", function (title) {
1335
+ this.title = title;
1336
+ this.$el.find("h2:first").text(title);
1337
+ });
1338
+
1339
+ /**
1340
+ Sets the title for the step description panel
1341
+
1342
+ @method setStepPosition
1343
+ @param {String} title The text for the step description panel
1344
+ **/
1345
+ StepDescription.method("setStepPosition", function (stepPosition) {
1346
+ this.stepPosition = stepPosition;
1347
+ this.$el.find(".sideshow-step-position").text(stepPosition);
1348
+ });
1349
+
1350
+ /**
1351
+ Renders the step description panel
1352
+
1353
+ @method render
1354
+ **/
1355
+ StepDescription.method("render", function () {
1356
+ this.$el = $("<div>").addClass("sideshow-step-description").addClass("sideshow-hidden").addClass("sideshow-invisible");
1357
+
1358
+ var stepPosition = $("<span>").addClass("sideshow-step-position");
1359
+ this.$el.append(stepPosition);
1360
+ if (currentWizard.showStepPosition === false) stepPosition.hide();
1361
+
1362
+ this.$el.append($("<h2>"));
1363
+ this.$el.append($("<div>").addClass("sideshow-step-text"));
1364
+ this.nextButton.render(this.$el);
1365
+ this.nextButton.$el.click(function () {
1366
+ currentWizard.next();
1367
+ });
1368
+ DetailsPanel.singleInstance.$el.append(this.$el);
1369
+ });
1370
+
1371
+ /**
1372
+ Shows the step description panel
1373
+
1374
+ @method show
1375
+ **/
1376
+ StepDescription.method("show", function (displayButKeepTransparent) {
1377
+ this.callSuper("show", displayButKeepTransparent);
1378
+ //this.positionate();
1379
+ });
1380
+
1381
+ /**
1382
+ Positionates the step description panel
1383
+
1384
+ @method positionate
1385
+ **/
1386
+ StepDescription.method("positionate", function () {
1387
+ var dp = DetailsPanel.singleInstance;
1388
+
1389
+ if (dp.dimension.width >= 900) this.dimension.width = 900;
1390
+ else this.dimension.width = dp.dimension.width * 0.9;
1391
+
1392
+ this.$el.css("width", this.dimension.width);
1393
+
1394
+ var paddingLeftRight = (parsePxValue(this.$el.css("padding-left")) + parsePxValue(this.$el.css("padding-right"))) / 2;
1395
+ var paddingTopBottom = (parsePxValue(this.$el.css("padding-top")) + parsePxValue(this.$el.css("padding-bottom"))) / 2;
1396
+
1397
+ this.dimension.height = parsePxValue(this.$el.outerHeight());
1398
+
1399
+ //Checks if the description dimension overflow the available space in the details panel
1400
+ if (this.dimension.height > dp.dimension.height || this.dimension.width < 400) {
1401
+ this.dimension.width = $window.width() * 0.9;
1402
+ this.$el.css("width", this.dimension.width);
1403
+ this.dimension.height = parsePxValue(this.$el.outerHeight());
1404
+
1405
+ this.position.x = ($window.width() - this.dimension.width) / 2;
1406
+ this.position.y = ($window.height() - this.dimension.height) / 2;
1407
+ } else {
1408
+ this.position.x = (dp.dimension.width - this.dimension.width) / 2;
1409
+ this.position.y = (dp.dimension.height - this.dimension.height) / 2;
1410
+ }
1411
+
1412
+ this.$el.css("left", this.position.x - paddingLeftRight);
1413
+ this.$el.css("top", this.position.y - paddingTopBottom);
1414
+ });
1415
+
1416
+ /**
1417
+ Step next button
1418
+
1419
+ @class StepDescriptionNextButton
1420
+ @extends HidableItem
1421
+ **/
1422
+ var StepDescriptionNextButton = jazz.Class().extending(HidableItem);
1423
+
1424
+ /**
1425
+ The text for the next button
1426
+
1427
+ @@field _text
1428
+ @private
1429
+ **/
1430
+ StepDescriptionNextButton.field("_text");
1431
+
1432
+ /**
1433
+ Disables the next button
1434
+
1435
+ @method disable
1436
+ **/
1437
+ StepDescriptionNextButton.method("disable", function () {
1438
+ this.$el.attr("disabled", "disabled");
1439
+ });
1440
+
1441
+ /**
1442
+ Enables the next button
1443
+
1444
+ @method enable
1445
+ **/
1446
+ StepDescriptionNextButton.method("enable", function () {
1447
+ this.$el.attr("disabled", null);
1448
+ });
1449
+
1450
+ /**
1451
+ Sets the text for the next button
1452
+
1453
+ @method setText
1454
+ @param {String} text The text for the next button
1455
+ **/
1456
+ StepDescriptionNextButton.method("setText", function (text) {
1457
+ this._text = text;
1458
+ this.$el.text(text);
1459
+ });
1460
+
1461
+ /**
1462
+ Renders the Next Button
1463
+
1464
+ @method render
1465
+ @param {Object} $stepDescriptionEl The jQuery wrapped DOM element for the Step Description panel
1466
+ **/
1467
+ StepDescriptionNextButton.method("render", function ($stepDescriptionEl) {
1468
+ this.$el = $("<button>").addClass("sideshow-next-step-button");
1469
+ this.callSuper("render", $stepDescriptionEl);
1470
+ });
1471
+ /**
1472
+ Represents the current available area in the browser
1473
+
1474
+ @class Screen
1475
+ @static
1476
+ **/
1477
+ var Screen = {};
1478
+
1479
+ /**
1480
+ Object holding dimension information for the screen
1481
+
1482
+ @@field
1483
+ @static
1484
+ @type Object
1485
+ **/
1486
+ Screen.dimension = {};
1487
+
1488
+ /**
1489
+ Checks if the screen dimension information has changed
1490
+
1491
+ @method hasChanged
1492
+ @static
1493
+ @return boolean
1494
+ **/
1495
+ Screen.hasChanged = function () {
1496
+ return ($window.width() !== this.dimension.width) || ($window.height() !== this.dimension.height);
1497
+ };
1498
+
1499
+ /**
1500
+ Updates the dimension information for the screen
1501
+
1502
+ @method updateInfo
1503
+ @static
1504
+ **/
1505
+ Screen.updateInfo = function () {
1506
+ this.dimension.width = $window.width();
1507
+ this.dimension.height = $window.height();
1508
+ };
1509
+ /**
1510
+ The current subject (the object being shown by the current wizard)
1511
+
1512
+ @class Subject
1513
+ @static
1514
+ **/
1515
+ var Subject = {};
1516
+
1517
+ /**
1518
+ The current subject jQuery wrapped DOM element
1519
+
1520
+ @@field obj
1521
+ @static
1522
+ @type Object
1523
+ **/
1524
+ Subject.obj = null;
1525
+
1526
+ /**
1527
+ The current subject dimension information
1528
+
1529
+ @@field position
1530
+ @static
1531
+ @type Object
1532
+ **/
1533
+ Subject.dimension = {};
1534
+
1535
+ /**
1536
+ The current subject positioning information
1537
+
1538
+ @@field position
1539
+ @static
1540
+ @type Object
1541
+ **/
1542
+ Subject.position = {};
1543
+
1544
+ /**
1545
+ The current subject border radius information
1546
+
1547
+ @@field borderRadius
1548
+ @static
1549
+ @type Object
1550
+ **/
1551
+ Subject.borderRadius = {};
1552
+
1553
+ /**
1554
+ Checks if the object has changed since the last checking
1555
+
1556
+ @method hasChanged
1557
+ @return boolean
1558
+ **/
1559
+ Subject.hasChanged = function () {
1560
+ if (!this.obj) return false;
1561
+
1562
+ return (this.obj.offset().left - $window.scrollLeft() !== this.position.x) || (this.obj.offset().top - $window.scrollTop() !== this.position.y) || (this.obj.outerWidth() !== this.dimension.width) || (this.obj.outerHeight() !== this.dimension.height) || (parsePxValue(this.obj.css("border-top-left-radius")) !== this.borderRadius.leftTop) || (parsePxValue(this.obj.css("border-top-right-radius")) !== this.borderRadius.rightTop) || (parsePxValue(this.obj.css("border-bottom-left-radius")) !== this.borderRadius.leftBottom) || (parsePxValue(this.obj.css("border-bottom-right-radius")) !== this.borderRadius.rightBottom) || Screen.hasChanged();
1563
+ };
1564
+
1565
+ /**
1566
+ Updates the information about the suject
1567
+
1568
+ @method updateInfo
1569
+ @param {Object} config Dimension, positioning and border radius information
1570
+ **/
1571
+ Subject.updateInfo = function (config) {
1572
+ if (config === undefined) {
1573
+ this.position.x = this.obj.offset().left - $window.scrollLeft();
1574
+ this.position.y = this.obj.offset().top - $window.scrollTop();
1575
+ this.dimension.width = this.obj.outerWidth();
1576
+ this.dimension.height = this.obj.outerHeight();
1577
+ this.borderRadius.leftTop = parsePxValue(this.obj.css("border-top-left-radius"));
1578
+ this.borderRadius.rightTop = parsePxValue(this.obj.css("border-top-right-radius"));
1579
+ this.borderRadius.leftBottom = parsePxValue(this.obj.css("border-bottom-left-radius"));
1580
+ this.borderRadius.rightBottom = parsePxValue(this.obj.css("border-bottom-right-radius"));
1581
+ } else {
1582
+ this.position.x = config.position.x;
1583
+ this.position.y = config.position.y;
1584
+ this.dimension.width = config.dimension.width;
1585
+ this.dimension.height = config.dimension.height;
1586
+ this.borderRadius.leftTop = config.borderRadius.leftTop;
1587
+ this.borderRadius.rightTop = config.borderRadius.rightTop;
1588
+ this.borderRadius.leftBottom = config.borderRadius.leftBottom;
1589
+ this.borderRadius.rightBottom = config.borderRadius.rightBottom;
1590
+ }
1591
+
1592
+ Screen.updateInfo();
1593
+ };
1594
+
1595
+ Subject.isSubjectVisible = function (position, dimension) {
1596
+ if ((position.y + dimension.height) > $window.height() || position.y < 0) {
1597
+ return false;
1598
+ }
1599
+ return true;
1600
+ };
1601
+ /**
1602
+ Namespace to hold classes for mask control
1603
+
1604
+ @namespace Mask
1605
+ **/
1606
+ var Mask = {};
1607
+ /**
1608
+ Controls the mask that covers the subject during a step transition
1609
+
1610
+ @class SubjectMask
1611
+ @@singleton
1612
+ **/
1613
+ Mask.SubjectMask = jazz.Class().extending(FadableItem).singleton;
1614
+
1615
+ /**
1616
+ Renders the subject mask
1617
+
1618
+ @method render
1619
+ **/
1620
+ Mask.SubjectMask.method("render", function () {
1621
+ this.$el = $("<div>").addClass("sideshow-subject-mask");
1622
+ this.callSuper("render");
1623
+ });
1624
+
1625
+ /**
1626
+ Updates the dimension, positioning and border radius of the subject mask
1627
+
1628
+ @method update
1629
+ @param {Object} position The positioning information
1630
+ @param {Object} dimension The dimension information
1631
+ @param {Object} borderRadius The border radius information
1632
+ **/
1633
+ Mask.SubjectMask.method("update", function (position, dimension, borderRadius) {
1634
+ this.$el.css("left", position.x).css("top", position.y).css("width", dimension.width).css("height", dimension.height).css("border-radius", borderRadius.leftTop + "px " + borderRadius.rightTop + "px " + borderRadius.leftBottom + "px " + borderRadius.rightBottom + "px ");
1635
+ });
1636
+ /**
1637
+ Controls the mask surrounds the subject (the step focussed area)
1638
+
1639
+ @class CompositeMask
1640
+ @@singleton
1641
+ **/
1642
+ Mask.CompositeMask = jazz.Class().extending(FadableItem).singleton;
1643
+
1644
+ /**
1645
+ Initializes the composite mask
1646
+
1647
+ @method init
1648
+ **/
1649
+ Mask.CompositeMask.method("init", function () {
1650
+ var mask = this;
1651
+ ["top", "left", "right", "bottom"].forEach(function (d) {
1652
+ mask.parts[d] = Mask.CompositeMask.Part.build();
1653
+ });
1654
+ ["leftTop", "rightTop", "leftBottom", "rightBottom"].forEach(function (d) {
1655
+ mask.parts[d] = Mask.CompositeMask.CornerPart.build();
1656
+ });
1657
+ });
1658
+
1659
+ /**
1660
+ The parts composing the mask
1661
+
1662
+ @@field parts
1663
+ @type Object
1664
+ **/
1665
+ Mask.CompositeMask.field("parts", {});
1666
+
1667
+ /**
1668
+ Renders the composite mask
1669
+
1670
+ @method render
1671
+ **/
1672
+ Mask.CompositeMask.method("render", function () {
1673
+ var mask = this;
1674
+ for (var p in this.parts) {
1675
+ var part = this.parts[p];
1676
+ if (part.render) part.render();
1677
+ }
1678
+ this.$el = $(".sideshow-mask-part, .sideshow-mask-corner-part");
1679
+ // if(!this.$el || this.$el.length === 0) this.$el = $(".sideshow-mask-part, .sideshow-mask-corner-part");
1680
+ Mask.SubjectMask.singleInstance.render();
1681
+ ["leftTop", "rightTop", "leftBottom", "rightBottom"].forEach(function (d) {
1682
+ mask.parts[d].$el.addClass(d);
1683
+ });
1684
+ this.status = AnimationStatus.NOT_DISPLAYED;
1685
+ });
1686
+
1687
+ /**
1688
+ Checks if the subject is fully visible, if not, scrolls 'til it became fully visible
1689
+
1690
+ @method scrollIfNecessary
1691
+ @param {Object} position An object representing the positioning info for the mask
1692
+ @param {Object} dimension An object representing the dimension info for the mask
1693
+ **/
1694
+ Mask.CompositeMask.method("scrollIfNecessary", function (position, dimension) {
1695
+ function doSmoothScroll(scrollTop, callback) {
1696
+ $("body,html").animate({
1697
+ scrollTop: scrollTop
1698
+ }, 300, callback);
1699
+ }
1700
+
1701
+ if (!Subject.isSubjectVisible(position, dimension)) {
1702
+ var description = StepDescription.singleInstance;
1703
+ var y = dimension.height > ($window.height() - 50) ? position.y : position.y - 25;
1704
+ y += $window.scrollTop();
1705
+
1706
+ doSmoothScroll(y, function () {
1707
+ setTimeout(function () {
1708
+ DetailsPanel.singleInstance.positionate();
1709
+ description.positionate();
1710
+ description.fadeIn();
1711
+ }, 300);
1712
+ });
1713
+
1714
+ return true;
1715
+ }
1716
+ return false;
1717
+ });
1718
+
1719
+ /**
1720
+ Updates the positioning and dimension of each part composing the whole mask, according to the subject coordinates
1721
+
1722
+ @method update
1723
+ @param {Object} position An object representing the positioning info for the mask
1724
+ @param {Object} dimension An object representing the dimension info for the mask
1725
+ @param {Object} borderRadius An object representing the borderRadius info for the mask
1726
+ **/
1727
+ Mask.CompositeMask.method("update", function (position, dimension, borderRadius) {
1728
+ Mask.SubjectMask.singleInstance.update(position, dimension, borderRadius);
1729
+ //Aliases
1730
+ var left = position.x,
1731
+ top = position.y,
1732
+ width = dimension.width,
1733
+ height = dimension.height,
1734
+ br = borderRadius;
1735
+
1736
+ //Updates the divs surrounding the subject
1737
+ this.parts.top.update({
1738
+ x: 0,
1739
+ y: 0
1740
+ }, {
1741
+ width: $window.width(),
1742
+ height: top
1743
+ });
1744
+ this.parts.left.update({
1745
+ x: 0,
1746
+ y: top
1747
+ }, {
1748
+ width: left,
1749
+ height: height
1750
+ });
1751
+ this.parts.right.update({
1752
+ x: left + width,
1753
+ y: top
1754
+ }, {
1755
+ width: $window.width() - (left + width),
1756
+ height: height
1757
+ });
1758
+ this.parts.bottom.update({
1759
+ x: 0,
1760
+ y: top + height
1761
+ }, {
1762
+ width: $window.width(),
1763
+ height: $window.height() - (top + height)
1764
+ });
1765
+
1766
+ //Updates the Rounded corners
1767
+ this.parts.leftTop.update({
1768
+ x: left,
1769
+ y: top
1770
+ }, br.leftTop);
1771
+ this.parts.rightTop.update({
1772
+ x: left + width - br.rightTop,
1773
+ y: top
1774
+ }, br.rightTop);
1775
+ this.parts.leftBottom.update({
1776
+ x: left,
1777
+ y: top + height - br.leftBottom
1778
+ }, br.leftBottom);
1779
+ this.parts.rightBottom.update({
1780
+ x: left + width - br.rightBottom,
1781
+ y: top + height - br.rightBottom
1782
+ }, br.rightBottom);
1783
+ });
1784
+
1785
+ /**
1786
+ A Polling function to check if subject coordinates has changed
1787
+
1788
+ @method pollForSubjectChanges
1789
+ **/
1790
+ Mask.CompositeMask.method("pollForSubjectChanges", function () {
1791
+ if (!flags.lockMaskUpdate) {
1792
+ if (currentWizard && currentWizard.currentStep.subject) {
1793
+ var subject = $(currentWizard.currentStep.subject);
1794
+ if (Subject.obj[0] !== subject[0]) SS.setSubject(subject, true);
1795
+ }
1796
+
1797
+ if (Subject.hasChanged()) {
1798
+ Subject.updateInfo();
1799
+ this.update(Subject.position, Subject.dimension, Subject.borderRadius);
1800
+ }
1801
+ }
1802
+ });
1803
+
1804
+ /**
1805
+ A Polling function to check if screen dimension has changed
1806
+
1807
+ @method pollForScreenChanges
1808
+ **/
1809
+ Mask.CompositeMask.method("pollForScreenChanges", function () {
1810
+ if (Screen.hasChanged()) {
1811
+ Screen.updateInfo();
1812
+ this.update(Subject.position, Subject.dimension, Subject.borderRadius);
1813
+ }
1814
+ });
1815
+
1816
+ /**
1817
+ A part composing the mask
1818
+
1819
+ @class Part
1820
+ @@initializer
1821
+ @param {Object} position The positioning information
1822
+ @param {Object} dimension The dimension information
1823
+ **/
1824
+ Mask.CompositeMask.Part = jazz.Class(function (position, dimension) {
1825
+ this.position = position;
1826
+ this.dimension = dimension;
1827
+ }).extending(VisualItem);
1828
+
1829
+
1830
+ /**
1831
+ @@alias Part
1832
+ @@to Mask.CompositeMask.Part
1833
+ **/
1834
+ var Part = Mask.CompositeMask.Part;
1835
+
1836
+ /**
1837
+ An object holding positioning information for the mask part
1838
+
1839
+ @@field position
1840
+ @type Object
1841
+ **/
1842
+ Part.field("position", {});
1843
+
1844
+ /**
1845
+ An object holding dimension information for the mask part
1846
+
1847
+ @@field position
1848
+ @type Object
1849
+ **/
1850
+ Part.field("dimension", {});
1851
+
1852
+ /**
1853
+ Renders the mask part
1854
+
1855
+ @method render
1856
+ **/
1857
+ Part.method("render", function () {
1858
+ this.$el = $("<div>").addClass("sideshow-mask-part").addClass("sideshow-hidden").addClass("sideshow-invisible");
1859
+ this.callSuper("render");
1860
+ });
1861
+
1862
+ /**
1863
+ Updates the dimension and positioning of the subject mask part
1864
+
1865
+ @method update
1866
+ @param {Object} position The positioning information
1867
+ @param {Object} dimension The dimension information
1868
+ **/
1869
+ Part.method("update", function (position, dimension) {
1870
+ this.position = position;
1871
+ this.dimension = dimension;
1872
+ this.$el.css("left", position.x).css("top", position.y).css("width", dimension.width).css("height", dimension.height);
1873
+ });
1874
+ /**
1875
+ A corner part composing the mask
1876
+
1877
+ @class CornerPart
1878
+ @@initializer
1879
+ @param {Object} position The positioning information
1880
+ @param {Object} dimension The dimension information
1881
+ **/
1882
+ Mask.CompositeMask.CornerPart = jazz.Class().extending(VisualItem);
1883
+
1884
+ /**
1885
+ @@alias CornerPart
1886
+ @@to Mask.CompositeMask.CornerPart
1887
+ **/
1888
+ var CornerPart = Mask.CompositeMask.CornerPart;
1889
+
1890
+ /**
1891
+ An object holding positioning information for the mask corner part
1892
+
1893
+ @@field position
1894
+ @type Object
1895
+ **/
1896
+ CornerPart.field("position", {});
1897
+
1898
+ /**
1899
+ An object holding dimension information for the mask corner part
1900
+
1901
+ @@field position
1902
+ @type Object
1903
+ **/
1904
+ CornerPart.field("dimension", {});
1905
+
1906
+ /**
1907
+ An object holding border radius information for the mask corner part
1908
+
1909
+ @@field borderRadius
1910
+ @type Object
1911
+ **/
1912
+ CornerPart.field("borderRadius", 0);
1913
+
1914
+ /**
1915
+ Formats the SVG path for the corner part
1916
+
1917
+ @method SVGPathPointsTemplate
1918
+ @param {Number} borderRadius The corner part border radius
1919
+ @static
1920
+ **/
1921
+ CornerPart.static.SVGPathPointsTemplate = function (borderRadius) {
1922
+ return "m 0,0 0," + borderRadius + " C 0," + borderRadius * 0.46 + " " + borderRadius * 0.46 + ",0 " + borderRadius + ",0";
1923
+ };
1924
+
1925
+ /**
1926
+ Renders the SVG for the corner part
1927
+
1928
+ @method buildSVG
1929
+ @param {Number} borderRadius The corner part border radius
1930
+ @static
1931
+ **/
1932
+ CornerPart.static.buildSVG = function (borderRadius) {
1933
+ function SVG(nodeName) {
1934
+ return document.createElementNS("http://www.w3.org/2000/svg", nodeName);
1935
+ }
1936
+
1937
+ var bezierPoints = this.SVGPathPointsTemplate(borderRadius);
1938
+ var $svg = $(SVG("svg"));
1939
+ var $path = $(SVG("path"));
1940
+
1941
+ $path.attr("d", bezierPoints);
1942
+ $svg.append($path);
1943
+
1944
+ return $svg[0];
1945
+ };
1946
+
1947
+ /**
1948
+ Renders the mask corner part
1949
+
1950
+ @method render
1951
+ @return {Object} The corner part jQuery wrapped DOM element
1952
+ **/
1953
+ CornerPart.prototype.render = function () {
1954
+ this.$el = $("<div>").addClass("sideshow-mask-corner-part").addClass("sideshow-hidden").addClass("sideshow-invisible");
1955
+ this.$el.append(CornerPart.buildSVG(this.borderRadius));
1956
+ $body.append(this.$el);
1957
+ return this.$el;
1958
+ };
1959
+
1960
+ /**
1961
+ Updates the positioning and border radius of the mask corner part
1962
+
1963
+ @method update
1964
+ @param {Object} position The positioning information
1965
+ @param {Object} borderRadius The border radius information
1966
+ **/
1967
+ CornerPart.prototype.update = function (position, borderRadius) {
1968
+ this.$el.css("left", position.x).css("top", position.y).css("width", borderRadius).css("height", borderRadius);
1969
+
1970
+ $(this.$el).find("path").attr("d", CornerPart.SVGPathPointsTemplate(borderRadius));
1971
+ };
1972
+ /**
1973
+ Controls the polling functions needed by Sideshow
1974
+
1975
+ @class Polling
1976
+ @static
1977
+ **/
1978
+ var Polling = {};
1979
+
1980
+ /**
1981
+ The polling functions queue
1982
+
1983
+ @@field queue
1984
+ @type Object
1985
+ @static
1986
+ **/
1987
+ Polling.queue = [];
1988
+
1989
+ /**
1990
+ A flag that controls if the polling is locked
1991
+
1992
+ @@field lock
1993
+ @type boolean
1994
+ @static
1995
+ **/
1996
+ Polling.lock = false;
1997
+
1998
+ /**
1999
+ Pushes a polling function in the queue
2000
+
2001
+ @method enqueue
2002
+ @static
2003
+ **/
2004
+ Polling.enqueue = function () {
2005
+ var firstArg = arguments[0];
2006
+ var fn;
2007
+ var name = "";
2008
+
2009
+ if (typeof firstArg == "function") fn = firstArg;
2010
+ else {
2011
+ name = arguments[0];
2012
+ fn = arguments[1];
2013
+ }
2014
+
2015
+ if (this.getFunctionIndex(fn) < 0 && (name === "" || this.getFunctionIndex(name) < 0)) {
2016
+ this.queue.push({
2017
+ name: name,
2018
+ fn: fn,
2019
+ enabled: true
2020
+ });
2021
+ } else throw new SSException("301", "The function is already in the polling queue.");
2022
+ };
2023
+
2024
+ /**
2025
+ Removes a polling function from the queue
2026
+
2027
+ @method dequeue
2028
+ @static
2029
+ **/
2030
+ Polling.dequeue = function () {
2031
+ this.queue.splice(this.getFunctionIndex(arguments[0]), 1);
2032
+ };
2033
+
2034
+ /**
2035
+ Enables an specific polling function
2036
+
2037
+ @method enable
2038
+ @static
2039
+ **/
2040
+ Polling.enable = function () {
2041
+ this.queue[this.getFunctionIndex(arguments[0])].enabled = true;
2042
+ }
2043
+
2044
+ /**
2045
+ Disables an specific polling function, but preserving it in the polling queue
2046
+
2047
+ @method disable
2048
+ @static
2049
+ **/
2050
+ Polling.disable = function () {
2051
+ this.queue[this.getFunctionIndex(arguments[0])].enabled = false;
2052
+ }
2053
+
2054
+ /**
2055
+ Gets the position of a polling function in the queue based on its name or the function itself
2056
+
2057
+ @method getFunctionIndex
2058
+ @static
2059
+ **/
2060
+ Polling.getFunctionIndex = function () {
2061
+ var firstArg = arguments[0];
2062
+
2063
+ if (typeof firstArg == "function") return this.queue.map(function (p) {
2064
+ return p.fn;
2065
+ }).indexOf(firstArg);
2066
+ else if (typeof firstArg == "string") return this.queue.map(function (p) {
2067
+ return p.name;
2068
+ }).indexOf(firstArg);
2069
+
2070
+ throw new SSException("302", "Invalid argument for getFunctionIndex method. Expected a string (the polling function name) or a function (the polling function itself).");
2071
+ }
2072
+
2073
+ /**
2074
+ Unlocks the polling and starts the checking process
2075
+
2076
+ @method start
2077
+ @static
2078
+ **/
2079
+ Polling.start = function () {
2080
+ this.lock = false;
2081
+ this.doPolling();
2082
+ };
2083
+
2084
+ /**
2085
+ Stops the polling process
2086
+
2087
+ @method stop
2088
+ @static
2089
+ **/
2090
+ Polling.stop = function () {
2091
+ this.lock = true;
2092
+ };
2093
+
2094
+ /**
2095
+ Clear the polling queue
2096
+
2097
+ @method clear
2098
+ @static
2099
+ **/
2100
+ Polling.clear = function () {
2101
+ var lock = this.lock;
2102
+
2103
+ this.lock = true;
2104
+ this.queue = [];
2105
+ this.lock = lock;
2106
+ };
2107
+
2108
+ /**
2109
+ Starts the polling process
2110
+
2111
+ @method doPolling
2112
+ @static
2113
+ **/
2114
+ Polling.doPolling = function () {
2115
+ if (!this.lock) {
2116
+ //Using timeout to avoid the queue to not complete in a cycle
2117
+ setTimeout(function () {
2118
+ for (var fn = 0; fn < Polling.queue.length; fn++) {
2119
+ var pollingFunction = Polling.queue[fn];
2120
+ pollingFunction.enabled && pollingFunction.fn();
2121
+ }
2122
+ Polling.doPolling();
2123
+ }, pollingDuration);
2124
+ }
2125
+ };
2126
+ /**
2127
+ The main menu, where the available wizards are listed
2128
+
2129
+ @class WizardMenu
2130
+ @static
2131
+ **/
2132
+ var WizardMenu = {};
2133
+
2134
+ /**
2135
+ Renders the wizard menu
2136
+
2137
+ @method render
2138
+ @param {Array} wizards The wizards list
2139
+ @static
2140
+ **/
2141
+ WizardMenu.render = function (wizards) {
2142
+ var $menu = $("<div>").addClass("sideshow-wizard-menu");
2143
+ this.$el = $menu;
2144
+ var $title = $("<h1>").addClass("sideshow-wizard-menu-title");
2145
+ $menu.append($title);
2146
+
2147
+ if (wizards.length > 0) {
2148
+ var $wizardsList = $("<ul>");
2149
+
2150
+ //Extracting this function to avoid the JSHint warning W083
2151
+
2152
+ function setClick($wiz, wizard) {
2153
+ $wiz.click(function () {
2154
+ WizardMenu.hide(function () {
2155
+ wizard.prepareAndPlay();
2156
+ });
2157
+ });
2158
+ }
2159
+
2160
+ for (var w = 0; w < wizards.length; w++) {
2161
+ var wiz = wizards[w];
2162
+ var $wiz = $("<li>");
2163
+ var $wizTitle = $("<h2>").text(wiz.title);
2164
+
2165
+ var description = wiz.description;
2166
+ description.length > 100 && (description = description.substr(0, 100) + "...");
2167
+
2168
+ var $wizDescription = $("<span>").addClass("sideshow-wizard-menu-item-description").text(description);
2169
+ var $wizEstimatedTime = $("<span>").addClass("sideshow-wizard-menu-item-estimated-time").text(wiz.estimatedTime);
2170
+ $wiz.append($wizEstimatedTime, $wizTitle, $wizDescription);
2171
+ $wizardsList.append($wiz);
2172
+
2173
+ setClick($wiz, wiz);
2174
+ }
2175
+ $menu.append($wizardsList);
2176
+ } else {
2177
+ $("<div>").addClass("sideshow-no-wizards-available").text(getString(strings.noAvailableWizards)).appendTo($menu);
2178
+ }
2179
+
2180
+ $body.append($menu);
2181
+ };
2182
+
2183
+ /**
2184
+ Shows the wizard menu
2185
+
2186
+ @method show
2187
+ @param {Array} wizards The wizards list
2188
+ @static
2189
+ **/
2190
+ WizardMenu.show = function (wizards, title) {
2191
+ if (wizards.length == 1 && SS.config.autoSkipIntro) wizards[0].prepareAndPlay();
2192
+ else {
2193
+ SS.setEmptySubject();
2194
+ Mask.CompositeMask.singleInstance.update(Subject.position, Subject.dimension, Subject.borderRadius);
2195
+ Mask.CompositeMask.singleInstance.fadeIn();
2196
+
2197
+ WizardMenu.render(wizards);
2198
+
2199
+ if (title) this.setTitle(title);
2200
+ else this.setTitle(getString(strings.availableWizards));
2201
+ }
2202
+ };
2203
+
2204
+ /**
2205
+ Hides the wizard menu
2206
+
2207
+ @method hide
2208
+ @param {Function} callback The callback to be called after hiding the menu
2209
+ @static
2210
+ **/
2211
+ WizardMenu.hide = function (callback) {
2212
+ var menu = this,
2213
+ $el = menu.$el;
2214
+
2215
+ $el && $el.addClass("sideshow-menu-closed");
2216
+ setTimeout(function () {
2217
+ $el && $el.hide();
2218
+ if (callback) callback();
2219
+ }, longAnimationDuration);
2220
+ };
2221
+
2222
+ WizardMenu.setTitle = function (title) {
2223
+ this.$el.find(".sideshow-wizard-menu-title").text(title);
2224
+ };
2225
+
2226
+ /**
2227
+ Initializes Sideshow
2228
+
2229
+ @method init
2230
+ @static
2231
+ **/
2232
+ SS.init = function () {
2233
+ $window = $(global);
2234
+ $document = $(global.document);
2235
+ $body = $("body", global.document);
2236
+ registerGlobalHotkeys();
2237
+ Polling.start();
2238
+ Mask.CompositeMask.singleInstance.init();
2239
+ flags.lockMaskUpdate = true;
2240
+ Mask.CompositeMask.singleInstance.render();
2241
+ };
2242
+
2243
+ /**
2244
+ Receives a function with just a multiline comment as body and converts to a here-document string
2245
+
2246
+ @method heredoc
2247
+ @param {Function} A function without body but a multiline comment
2248
+ @return {String} A multiline string
2249
+ @static
2250
+ **/
2251
+ SS.heredoc = function (fn) {
2252
+ return fn.toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1];
2253
+ }
2254
+
2255
+ /**
2256
+ Stops and Closes Sideshow
2257
+
2258
+ @method closes
2259
+ @static
2260
+ **/
2261
+ SS.close = function () {
2262
+ if (!currentWizard) WizardMenu.hide();
2263
+
2264
+ DetailsPanel.singleInstance.fadeOut();
2265
+
2266
+ this.CloseButton.singleInstance.fadeOut();
2267
+ Arrows.fadeOut();
2268
+
2269
+ setTimeout(function () {
2270
+ if (Mask.CompositeMask.singleInstance.status === AnimationStatus.VISIBLE || Mask.CompositeMask.singleInstance.status === AnimationStatus.FADING_IN) Mask.CompositeMask.singleInstance.fadeOut();
2271
+
2272
+ Mask.SubjectMask.singleInstance.fadeOut();
2273
+
2274
+ }, longAnimationDuration);
2275
+
2276
+ removeDOMGarbage();
2277
+ Polling.clear();
2278
+ SS.ControlVariables.clear();
2279
+ unregisterInnerHotkeys();
2280
+ currentWizard = null;
2281
+ flags.running = false;
2282
+ };
2283
+
2284
+ SS.runWizard = function (name) {
2285
+ var wiz = wizards.filter(function (w) {
2286
+ return w.name === name
2287
+ })[0];
2288
+ currentWizard = wiz;
2289
+ if (wiz) {
2290
+ if (wiz.isEligible()) wiz.play();
2291
+ else if (wiz.preparation) wiz.preparation(function () {
2292
+ setTimeout(function () {
2293
+ wiz.play();
2294
+ }, 1000);
2295
+ });
2296
+ else throw new SSException("204", "This wizard hasn't preparation.");
2297
+ } else throw new SSException("204", "There's no wizard with name " + name + ".");
2298
+ };
2299
+
2300
+ SS.gotoStep = function () {
2301
+ var firstArg = arguments[0],
2302
+ steps = currentWizard._storyline.steps,
2303
+ destination;
2304
+
2305
+ flags.skippingStep = true;
2306
+
2307
+ //First argument is the step position (1-based)
2308
+ if (typeof firstArg == "number") {
2309
+ if (firstArg <= steps.length) destination = steps[firstArg - 1];
2310
+ else throw new SSException("401", "There's no step in the storyline with position " + firstArg + ".");
2311
+ } //First argument is the step name
2312
+ else if (typeof firstArg == "string") {
2313
+ destination = steps.filter(function (i) {
2314
+ return i.name === firstArg;
2315
+ })[0];
2316
+
2317
+ if (!destination) throw new SSException("401", "There's no step in the storyline with name " + firstArg + ".");
2318
+ }
2319
+ setTimeout(function () {
2320
+ currentWizard.next(null, destination);
2321
+ }, 100);
2322
+ };
2323
+
2324
+ /**
2325
+ A trick to use the composite mask to simulate the behavior of a solid mask, setting an empty subject
2326
+
2327
+ @method setEmptySubject
2328
+ @static
2329
+ **/
2330
+ SS.setEmptySubject = function () {
2331
+ flags.lockMaskUpdate = true;
2332
+ Subject.obj = null;
2333
+ Subject.updateInfo({
2334
+ dimension: {
2335
+ width: 0,
2336
+ height: 0
2337
+ },
2338
+ position: {
2339
+ x: 0,
2340
+ y: 0
2341
+ },
2342
+ borderRadius: {
2343
+ leftTop: 0,
2344
+ rightTop: 0,
2345
+ leftBottom: 0,
2346
+ rightBottom: 0
2347
+ }
2348
+ });
2349
+ };
2350
+
2351
+ /**
2352
+ Sets the current subject
2353
+
2354
+ @method setSubject
2355
+ @param {Object} subj
2356
+ @static
2357
+ **/
2358
+ SS.setSubject = function (subj, subjectChanged) {
2359
+ if (subj.constructor === String) subj = $(subj);
2360
+
2361
+ if (subj instanceof $ && subj.length > 0) {
2362
+ if (subj.length === 1) {
2363
+ Subject.obj = subj;
2364
+ Subject.updateInfo();
2365
+ flags.lockMaskUpdate = false;
2366
+ } else throw new SSException("101", "A subject must have only one element. Multiple elements by step will be supported in future versions of Sideshow.");
2367
+ }
2368
+ else if (subjectChanged) SS.setEmptySubject();
2369
+ else throw new SSException("100", "Invalid subject.");
2370
+ };
2371
+
2372
+ /**
2373
+ Registers a wizard
2374
+
2375
+ @method registerWizard
2376
+ @param {Object} wizardConfig
2377
+ @return {Object} The wizard instance
2378
+ @static
2379
+ **/
2380
+ SS.registerWizard = function (wizardConfig) {
2381
+ var wiz = Wizard.build(wizardConfig);
2382
+ wizards.push(wiz);
2383
+ return wiz;
2384
+ };
2385
+
2386
+ /**
2387
+ Registers a wizard
2388
+
2389
+ @method registerWizard
2390
+ @param {boolean} onlyNew Checks only recently added wizards
2391
+ @return {Array} The eligible wizards list
2392
+ @static
2393
+ **/
2394
+ SS.getElegibleWizards = function (onlyNew) {
2395
+ var eligibleWizards = [];
2396
+ var somethingNew = false;
2397
+ for (var w = 0; w < wizards.length; w++) {
2398
+ var wiz = wizards[w];
2399
+ if (wiz.isEligible()) {
2400
+ if (!wiz.isAlreadyWatched()) somethingNew = true;
2401
+ eligibleWizards.push(wiz);
2402
+ }
2403
+ }
2404
+
2405
+ return !onlyNew || somethingNew ? eligibleWizards : [];
2406
+ };
2407
+
2408
+ /**
2409
+ Checks if there are eligible wizards, if exists, shows the wizard menu
2410
+
2411
+ @method showWizardsList
2412
+ @param {boolean} onlyNew Checks only recently added wizards
2413
+ @return {boolean} Returns a boolean indicating whether there is some wizard available
2414
+ @static
2415
+ **/
2416
+ SS.showWizardsList = function () {
2417
+ var firstArg = arguments[0];
2418
+ var title = arguments[1];
2419
+ var onlyNew = typeof firstArg == "boolean" ? false : firstArg;
2420
+ var wizards = firstArg instanceof Array ? firstArg : this.getElegibleWizards(onlyNew);
2421
+
2422
+ WizardMenu.show(wizards, title);
2423
+
2424
+ return wizards.length > 0;
2425
+ };
2426
+
2427
+ /**
2428
+ Shows a list with the related wizards
2429
+
2430
+ @method showRelatedWizardsList
2431
+ @param {Object} completedWizard The recently completed wizard
2432
+ @return {boolean} Returns a boolean indicating whether there is some related wizard available
2433
+ @static
2434
+ **/
2435
+ SS.showRelatedWizardsList = function (completedWizard) {
2436
+ var relatedWizardsNames = completedWizard.relatedWizards;
2437
+ if (!relatedWizardsNames) return false;
2438
+
2439
+ //Gets only related tutorials which are eligible or have a preparation function
2440
+ var relatedWizards = wizards.filter(function (w) {
2441
+ return relatedWizardsNames.indexOf(w.name) > -1 && (w.isEligible() || w.preparation);
2442
+ });
2443
+ if (relatedWizards.length == 0) return false;
2444
+
2445
+ Polling.clear();
2446
+ SS.ControlVariables.clear();
2447
+ SS.showWizardsList(relatedWizards, getString(strings.relatedWizards));
2448
+
2449
+ return true;
2450
+ };
2451
+
2452
+ /**
2453
+ The close button for the wizard
2454
+
2455
+ @class CloseButton
2456
+ @@singleton
2457
+ @extends FadableItem
2458
+ **/
2459
+ SS.CloseButton = jazz.Class().extending(FadableItem).singleton;
2460
+
2461
+ /**
2462
+ Renders the close button
2463
+
2464
+ @method render
2465
+ **/
2466
+ SS.CloseButton.method("render", function () {
2467
+ this.$el = $("<button>").addClass("sideshow-close-button").text(getString(strings.close));
2468
+ this.$el.click(function () {
2469
+ SS.close();
2470
+ });
2471
+ this.callSuper("render");
2472
+ });
2473
+
2474
+ /**
2475
+ Starts Sideshow
2476
+
2477
+ @method start
2478
+ @param {Object} config The config object for Sideshow
2479
+ **/
2480
+ SS.start = function (config) {
2481
+ config = config || {};
2482
+ if (!flags.running) {
2483
+ var onlyNew = "onlyNew" in config && !! config.onlyNew;
2484
+ var listAll = "listAll" in config && !! config.listAll;
2485
+
2486
+ if (listAll) SS.showWizardsList(wizards.filter(function (w) {
2487
+ return w.isEligible() || w.preparation;
2488
+ }));
2489
+ else SS.showWizardsList(onlyNew);
2490
+
2491
+ this.CloseButton.singleInstance.render();
2492
+ this.CloseButton.singleInstance.fadeIn();
2493
+
2494
+ registerInnerHotkeys();
2495
+ flags.running = true;
2496
+
2497
+ Polling.enqueue("check_composite_mask_screen_changes", function () {
2498
+ Mask.CompositeMask.singleInstance.pollForScreenChanges();
2499
+ });
2500
+ }
2501
+ };
2502
+
2503
+
2504
+
2505
+ //Tries to register the Global Access Point
2506
+ if (global[globalObjectName] === undefined) {
2507
+ global[globalObjectName] = SS;
2508
+ } else throw new SSException("1", "The global access point \"Sideshow\" is already being used.");
2509
+ });
2510
+ })(this, jQuery, Jazz, Markdown);