From 6d53ac2e88dc432c667d743a5c0b749415c603b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lionel=20Lask=C3=A9?= Date: Wed, 13 Nov 2013 14:57:03 +0100 Subject: [PATCH] Init version --- .gitattributes | 22 + .gitignore | 215 + .../activity/activity-clock.svg | 16 + .../Clock.activity/activity/activity.info | 6 + activities/Clock.activity/clock.svg | 40 + activities/Clock.activity/css/activity.css | 47 + .../Clock.activity/icons/nice-clock.svg | 14 + .../Clock.activity/icons/simple-clock.svg | 15 + .../Clock.activity/icons/write-date.svg | 10 + .../Clock.activity/icons/write-time.svg | 9 + activities/Clock.activity/index.html | 31 + activities/Clock.activity/js/activity.js | 366 + activities/Clock.activity/js/loader.js | 8 + activities/Clock.activity/lib/domReady.js | 129 + activities/Clock.activity/lib/moment.js | 1662 +++ activities/Clock.activity/lib/mustache.js | 610 ++ activities/Clock.activity/lib/require.js | 2000 ++++ .../Clock.activity/lib/sugar-web/LICENSE | 202 + .../Clock.activity/lib/sugar-web/README.md | 7 + .../lib/sugar-web/activity/activity.js | 114 + .../lib/sugar-web/activity/shortcut.js | 57 + .../Clock.activity/lib/sugar-web/bus.js | 229 + .../Clock.activity/lib/sugar-web/datastore.js | 221 + .../Clock.activity/lib/sugar-web/env.js | 27 + .../lib/sugar-web/graphics/README.md | 39 + .../lib/sugar-web/graphics/activitypalette.js | 70 + .../lib/sugar-web/graphics/css/sugar.css | 456 + .../lib/sugar-web/graphics/css/sugar.less | 589 ++ .../lib/sugar-web/graphics/grid.js | 54 + .../lib/sugar-web/graphics/icon.js | 81 + .../graphics/icons/actions/activity-stop.svg | 6 + .../actions/checkbox-checked-selected.svg | 27 + .../icons/actions/checkbox-checked.svg | 27 + .../actions/checkbox-unchecked-selected.svg | 22 + .../icons/actions/checkbox-unchecked.svg | 22 + .../icons/actions/dialog-cancel-active.svg | 6 + .../graphics/icons/actions/dialog-cancel.svg | 6 + .../icons/actions/dialog-ok-active.svg | 6 + .../graphics/icons/actions/dialog-ok.svg | 6 + .../icons/actions/entry-cancel-active.svg | 23 + .../icons/actions/entry-cancel-disabled.svg | 21 + .../graphics/icons/actions/entry-cancel.svg | 21 + .../icons/actions/radio-active-selected.svg | 31 + .../graphics/icons/actions/radio-active.svg | 31 + .../graphics/icons/actions/radio-selected.svg | 26 + .../graphics/icons/actions/radio.svg | 26 + .../graphics/icons/actions/zoom-groups.svg | 6 + .../graphics/icons/actions/zoom-home.svg | 6 + .../icons/actions/zoom-neighborhood.svg | 6 + .../graphics/icons/emblems/arrow-down.svg | 20 + .../graphics/icons/emblems/arrow-up.svg | 20 + .../lib/sugar-web/graphics/menupalette.js | 64 + .../lib/sugar-web/graphics/palette.js | 179 + .../sugar-web/graphics/radiobuttonsgroup.js | 59 + .../lib/sugar-web/graphics/xocolor.js | 729 ++ .../Clock.activity/lib/sugar-web/package.json | 9 + activities/Clock.activity/lib/webL10n.js | 1029 ++ activities/Clock.activity/package.json | 13 + activities/Clock.activity/setup.py | 5 + activities/ConnecttheDots.activity/.gitignore | 3 + activities/ConnecttheDots.activity/LICENSE | 202 + .../activity/activity-icon.svg | 69 + .../activity/activity.info | 6 + .../ConnecttheDots.activity/css/activity.css | 7 + .../icons/new-button.svg | 6 + .../ConnecttheDots.activity/images/dot.svg | 33 + .../ConnecttheDots.activity/images/pen.svg | 79 + .../ConnecttheDots.activity/images/star.svg | 33 + activities/ConnecttheDots.activity/index.html | 34 + .../js/activity-works.js | 271 + .../ConnecttheDots.activity/js/activity.js | 316 + .../ConnecttheDots.activity/js/loader.js | 15 + .../ConnecttheDots.activity/js/shapes.js | 44 + .../ConnecttheDots.activity/lib/domReady.js | 129 + .../ConnecttheDots.activity/lib/easeljs.js | 135 + .../ConnecttheDots.activity/lib/handlebars.js | 2278 ++++ .../ConnecttheDots.activity/lib/require.js | 2000 ++++ .../lib/sugar-web/LICENSE | 202 + .../lib/sugar-web/README.md | 7 + .../lib/sugar-web/activity/activity.js | 85 + .../lib/sugar-web/activity/shortcut.js | 57 + .../lib/sugar-web/bus.js | 229 + .../lib/sugar-web/datastore.js | 217 + .../lib/sugar-web/env.js | 27 + .../lib/sugar-web/graphics/README.md | 39 + .../lib/sugar-web/graphics/activitypalette.js | 105 + .../lib/sugar-web/graphics/css/sugar.css | 423 + .../lib/sugar-web/graphics/css/sugar.less | 550 + .../lib/sugar-web/graphics/grid.js | 54 + .../lib/sugar-web/graphics/icon.js | 81 + .../graphics/icons/actions/activity-stop.svg | 6 + .../actions/checkbox-checked-selected.svg | 27 + .../icons/actions/checkbox-checked.svg | 27 + .../actions/checkbox-unchecked-selected.svg | 22 + .../icons/actions/checkbox-unchecked.svg | 22 + .../icons/actions/dialog-cancel-active.svg | 6 + .../graphics/icons/actions/dialog-cancel.svg | 6 + .../icons/actions/dialog-ok-active.svg | 6 + .../graphics/icons/actions/dialog-ok.svg | 6 + .../icons/actions/entry-cancel-active.svg | 23 + .../icons/actions/entry-cancel-disabled.svg | 21 + .../graphics/icons/actions/entry-cancel.svg | 21 + .../icons/actions/radio-active-selected.svg | 31 + .../graphics/icons/actions/radio-active.svg | 31 + .../graphics/icons/actions/radio-selected.svg | 26 + .../graphics/icons/actions/radio.svg | 26 + .../graphics/icons/actions/zoom-groups.svg | 6 + .../graphics/icons/actions/zoom-home.svg | 6 + .../icons/actions/zoom-neighborhood.svg | 6 + .../graphics/icons/emblems/arrow-down.svg | 20 + .../graphics/icons/emblems/arrow-up.svg | 20 + .../lib/sugar-web/graphics/palette.js | 179 + .../sugar-web/graphics/radiobuttonsgroup.js | 59 + .../lib/sugar-web/graphics/xocolor.js | 729 ++ .../lib/sugar-web/package.json | 8 + .../ConnecttheDots.activity/lib/webL10n.js | 1029 ++ .../ConnecttheDots.activity/package.json | 11 + .../po/ConnecttheDots.pot | 22 + activities/ConnecttheDots.activity/setup.py | 5 + activities/Gears.activity/LICENSE | 202 + .../Gears.activity/activity/activity-icon.svg | 28 + .../Gears.activity/activity/activity.info | 6 + activities/Gears.activity/css/activity.css | 31 + activities/Gears.activity/icons/ChainIcon.svg | 28 + activities/Gears.activity/icons/GearIcon.svg | 25 + .../Gears.activity/icons/MomentumIcon.svg | 32 + .../Gears.activity/icons/edit-clear.svg | 6 + .../icons/media-playback-start.svg | 26 + .../Gears.activity/icons/toolbar-help.svg | 32 + activities/Gears.activity/img/ChainIcon.png | Bin 0 -> 2169 bytes activities/Gears.activity/img/ClearIcon.png | Bin 0 -> 652 bytes activities/Gears.activity/img/CloudIcon.png | Bin 0 -> 1443 bytes activities/Gears.activity/img/Gear.png | Bin 0 -> 2607 bytes activities/Gears.activity/img/GearIcon.png | Bin 0 -> 1702 bytes activities/Gears.activity/img/HelpIcon.png | Bin 0 -> 1810 bytes .../Gears.activity/img/MomentumIcon.png | Bin 0 -> 1818 bytes activities/Gears.activity/img/PlayIcon.png | Bin 0 -> 1246 bytes activities/Gears.activity/img/hand.png | Bin 0 -> 2541 bytes activities/Gears.activity/index.html | 34 + activities/Gears.activity/js/activity.js | 121 + activities/Gears.activity/js/hammer.min.js | 7 + activities/Gears.activity/js/loader.js | 24 + activities/Gears.activity/lib/domReady.js | 129 + .../lib/gearsketch/gearsketch_main.js | 1054 ++ .../lib/gearsketch/gearsketch_main.map | 10 + .../lib/gearsketch/gearsketch_model.js | 1450 +++ .../lib/gearsketch/gearsketch_model.map | 10 + .../lib/gearsketch/gearsketch_util.js | 727 ++ .../lib/gearsketch/gearsketch_util.map | 10 + .../lib/gearsketch/src/gearsketch_main.coffee | 863 ++ .../gearsketch/src/gearsketch_model.coffee | 921 ++ .../lib/gearsketch/src/gearsketch_util.coffee | 535 + activities/Gears.activity/lib/mustache.js | 610 ++ activities/Gears.activity/lib/require.js | 2000 ++++ .../Gears.activity/lib/sugar-web/LICENSE | 202 + .../Gears.activity/lib/sugar-web/README.md | 7 + .../lib/sugar-web/activity/activity.js | 114 + .../lib/sugar-web/activity/shortcut.js | 57 + .../Gears.activity/lib/sugar-web/bus.js | 229 + .../Gears.activity/lib/sugar-web/datastore.js | 221 + .../Gears.activity/lib/sugar-web/env.js | 27 + .../lib/sugar-web/graphics/README.md | 39 + .../lib/sugar-web/graphics/activitypalette.js | 70 + .../sugar-web/graphics/css/sugar-200dpi.css | 454 + .../sugar-web/graphics/css/sugar-200dpi.less | 2 + .../sugar-web/graphics/css/sugar-96dpi.css | 454 + .../sugar-web/graphics/css/sugar-96dpi.less | 2 + .../lib/sugar-web/graphics/css/sugar.less | 585 + .../lib/sugar-web/graphics/grid.js | 54 + .../lib/sugar-web/graphics/icon.js | 81 + .../graphics/icons/actions/activity-stop.svg | 6 + .../actions/checkbox-checked-selected.svg | 27 + .../icons/actions/checkbox-checked.svg | 27 + .../actions/checkbox-unchecked-selected.svg | 22 + .../icons/actions/checkbox-unchecked.svg | 22 + .../icons/actions/dialog-cancel-active.svg | 6 + .../graphics/icons/actions/dialog-cancel.svg | 6 + .../icons/actions/dialog-ok-active.svg | 6 + .../graphics/icons/actions/dialog-ok.svg | 6 + .../icons/actions/entry-cancel-active.svg | 23 + .../icons/actions/entry-cancel-disabled.svg | 21 + .../graphics/icons/actions/entry-cancel.svg | 21 + .../icons/actions/radio-active-selected.svg | 31 + .../graphics/icons/actions/radio-active.svg | 31 + .../graphics/icons/actions/radio-selected.svg | 26 + .../graphics/icons/actions/radio.svg | 26 + .../graphics/icons/actions/zoom-groups.svg | 6 + .../graphics/icons/actions/zoom-home.svg | 6 + .../icons/actions/zoom-neighborhood.svg | 6 + .../graphics/icons/emblems/arrow-down.svg | 20 + .../graphics/icons/emblems/arrow-up.svg | 20 + .../lib/sugar-web/graphics/menupalette.js | 64 + .../lib/sugar-web/graphics/palette.js | 179 + .../sugar-web/graphics/radiobuttonsgroup.js | 71 + .../lib/sugar-web/graphics/xocolor.js | 729 ++ .../Gears.activity/lib/sugar-web/package.json | 9 + activities/Gears.activity/lib/webL10n.js | 1029 ++ activities/Gears.activity/package.json | 12 + activities/Gears.activity/setup.py | 5 + .../activity/activity-icon.svg | 25 + .../activity/activity.info | 6 + .../GetThingsDone.activity/css/activity.css | 63 + .../GetThingsDone.activity/icons/list-add.svg | 6 + .../icons/list-remove.svg | 6 + activities/GetThingsDone.activity/index.html | 25 + .../GetThingsDone.activity/js/activity.js | 93 + .../GetThingsDone.activity/js/controller.js | 43 + .../GetThingsDone.activity/js/loader.js | 8 + activities/GetThingsDone.activity/js/model.js | 47 + activities/GetThingsDone.activity/js/view.js | 40 + .../GetThingsDone.activity/lib/domReady.js | 129 + .../GetThingsDone.activity/lib/require.js | 2000 ++++ .../lib/sugar-web/LICENSE | 202 + .../lib/sugar-web/README.md | 7 + .../lib/sugar-web/activity/activity.js | 114 + .../lib/sugar-web/activity/shortcut.js | 57 + .../lib/sugar-web/bus.js | 229 + .../lib/sugar-web/datastore.js | 221 + .../lib/sugar-web/env.js | 27 + .../lib/sugar-web/graphics/README.md | 39 + .../lib/sugar-web/graphics/activitypalette.js | 70 + .../lib/sugar-web/graphics/css/sugar.css | 456 + .../lib/sugar-web/graphics/css/sugar.less | 589 ++ .../lib/sugar-web/graphics/grid.js | 54 + .../lib/sugar-web/graphics/icon.js | 81 + .../graphics/icons/actions/activity-stop.svg | 6 + .../actions/checkbox-checked-selected.svg | 27 + .../icons/actions/checkbox-checked.svg | 27 + .../actions/checkbox-unchecked-selected.svg | 22 + .../icons/actions/checkbox-unchecked.svg | 22 + .../icons/actions/dialog-cancel-active.svg | 6 + .../graphics/icons/actions/dialog-cancel.svg | 6 + .../icons/actions/dialog-ok-active.svg | 6 + .../graphics/icons/actions/dialog-ok.svg | 6 + .../icons/actions/entry-cancel-active.svg | 23 + .../icons/actions/entry-cancel-disabled.svg | 21 + .../graphics/icons/actions/entry-cancel.svg | 21 + .../icons/actions/radio-active-selected.svg | 31 + .../graphics/icons/actions/radio-active.svg | 31 + .../graphics/icons/actions/radio-selected.svg | 26 + .../graphics/icons/actions/radio.svg | 26 + .../graphics/icons/actions/zoom-groups.svg | 6 + .../graphics/icons/actions/zoom-home.svg | 6 + .../icons/actions/zoom-neighborhood.svg | 6 + .../graphics/icons/emblems/arrow-down.svg | 20 + .../graphics/icons/emblems/arrow-up.svg | 20 + .../lib/sugar-web/graphics/menupalette.js | 64 + .../lib/sugar-web/graphics/palette.js | 179 + .../sugar-web/graphics/radiobuttonsgroup.js | 59 + .../lib/sugar-web/graphics/xocolor.js | 729 ++ .../lib/sugar-web/package.json | 9 + .../GetThingsDone.activity/lib/webL10n.js | 1029 ++ .../GetThingsDone.activity/package.json | 11 + activities/GetThingsDone.activity/setup.py | 5 + .../activity/activity-icon.svg | 111 + .../activity/activity.info | 6 + .../LastOneLoses.activity/audio/applause.ogg | Bin 0 -> 80234 bytes .../audio/disappointed.ogg | Bin 0 -> 37748 bytes .../LastOneLoses.activity/css/activity.css | 26 + .../LastOneLoses.activity/css/styles.css | 101 + .../icons/computer-icon.svg | 120 + .../icons/computer-win.svg | 153 + .../icons/empty-icon.svg | 113 + .../icons/level-easy.svg | 96 + .../icons/level-hard.svg | 96 + .../icons/level-medium.svg | 91 + .../icons/new-game-icon.svg | 112 + .../LastOneLoses.activity/icons/play.png | Bin 0 -> 1396 bytes .../icons/player-icon.svg | 86 + .../icons/player-win.svg | 119 + .../icons/switch-player.svg | 99 + activities/LastOneLoses.activity/index.html | 42 + .../LastOneLoses.activity/js/activity.js | 33 + activities/LastOneLoses.activity/js/app.js | 227 + activities/LastOneLoses.activity/js/audio.js | 112 + activities/LastOneLoses.activity/js/loader.js | 8 + .../LastOneLoses.activity/js/lolgame.js | 65 + .../LastOneLoses.activity/lib/domReady.js | 129 + .../LastOneLoses.activity/lib/enyo/enyo.css | 181 + .../LastOneLoses.activity/lib/enyo/enyo.js | 4306 ++++++++ .../lib/enyo/lib/canvas/canvas.js | 163 + .../lib/enyo/lib/layout/layout.css | 273 + .../lib/enyo/lib/layout/layout.js | 2449 +++++ .../LastOneLoses.activity/lib/require.js | 2000 ++++ .../lib/sugar-web/README.md | 7 + .../lib/sugar-web/activity/activity.js | 94 + .../lib/sugar-web/activity/shortcut.js | 57 + .../lib/sugar-web/bus.js | 229 + .../lib/sugar-web/datastore.js | 221 + .../lib/sugar-web/env.js | 27 + .../lib/sugar-web/graphics/README.md | 39 + .../lib/sugar-web/graphics/activitypalette.js | 70 + .../lib/sugar-web/graphics/css/sugar.css | 456 + .../lib/sugar-web/graphics/css/sugar.less | 589 ++ .../lib/sugar-web/graphics/grid.js | 54 + .../lib/sugar-web/graphics/icon.js | 81 + .../graphics/icons/actions/activity-stop.svg | 6 + .../actions/checkbox-checked-selected.svg | 27 + .../icons/actions/checkbox-checked.svg | 27 + .../actions/checkbox-unchecked-selected.svg | 22 + .../icons/actions/checkbox-unchecked.svg | 22 + .../icons/actions/dialog-cancel-active.svg | 6 + .../graphics/icons/actions/dialog-cancel.svg | 6 + .../icons/actions/dialog-ok-active.svg | 6 + .../graphics/icons/actions/dialog-ok.svg | 6 + .../icons/actions/entry-cancel-active.svg | 23 + .../icons/actions/entry-cancel-disabled.svg | 21 + .../graphics/icons/actions/entry-cancel.svg | 21 + .../icons/actions/radio-active-selected.svg | 31 + .../graphics/icons/actions/radio-active.svg | 31 + .../graphics/icons/actions/radio-selected.svg | 26 + .../graphics/icons/actions/radio.svg | 26 + .../graphics/icons/actions/zoom-groups.svg | 6 + .../graphics/icons/actions/zoom-home.svg | 6 + .../icons/actions/zoom-neighborhood.svg | 6 + .../graphics/icons/emblems/arrow-down.svg | 20 + .../graphics/icons/emblems/arrow-up.svg | 20 + .../lib/sugar-web/graphics/menupalette.js | 64 + .../lib/sugar-web/graphics/palette.js | 179 + .../sugar-web/graphics/radiobuttonsgroup.js | 59 + .../lib/sugar-web/graphics/xocolor.js | 729 ++ .../lib/sugar-web/package.json | 9 + .../lib/sugar-web/test/busSpec.js | 87 + .../lib/sugar-web/test/datastoreSpec.js | 238 + .../lib/sugar-web/test/envSpec.js | 14 + .../lib/sugar-web/test/graphics/iconSpec.js | 56 + .../test/graphics/menupaletteSpec.js | 75 + .../sugar-web/test/graphics/paletteSpec.js | 33 + .../test/graphics/radiobuttonsgroupSpec.js | 91 + .../lib/sugar-web/test/karma.conf.js | 81 + .../lib/sugar-web/test/loader.js | 19 + .../LastOneLoses.activity/lib/webL10n.js | 1029 ++ activities/LastOneLoses.activity/package.json | 10 + activities/LastOneLoses.activity/setup.py | 5 + activities/Markdown.activity/LICENSE | 202 + .../activity/activity-icon.svg | 29 + .../Markdown.activity/activity/activity.info | 6 + .../Markdown.activity/activity/code.svg | 51 + .../Markdown.activity/activity/edit-redo.svg | 16 + .../Markdown.activity/activity/edit-undo.svg | 15 + .../Markdown.activity/activity/font-text.svg | 32 + .../activity/format-text-bold.svg | 19 + .../activity/format-text-italic.svg | 14 + .../activity/format-text.svg | 8 + .../activity/horizontal-line3.svg | 51 + .../activity/insert-picture.svg | 86 + .../Markdown.activity/activity/link.svg | 51 + .../activity/paragraph-h1.svg | 13 + .../Markdown.activity/activity/quote.svg | 52 + .../activity/toolbar-bulletlist.svg | 101 + .../activity/toolbar-numbered-list.svg | 95 + .../activity/wmd-buttons.png | Bin 0 -> 7465 bytes activities/Markdown.activity/css/activity.css | 104 + activities/Markdown.activity/index.html | 42 + activities/Markdown.activity/js/activity.js | 99 + activities/Markdown.activity/js/loader.js | 11 + .../lib/Markdown.Converter.js | 1412 +++ .../Markdown.activity/lib/Markdown.Editor.js | 2258 ++++ .../lib/Markdown.Sanitizer.js | 108 + activities/Markdown.activity/lib/domReady.js | 129 + activities/Markdown.activity/lib/mustache.js | 610 ++ activities/Markdown.activity/lib/require.js | 2000 ++++ .../Markdown.activity/lib/sugar-web/LICENSE | 202 + .../Markdown.activity/lib/sugar-web/README.md | 7 + .../lib/sugar-web/activity/activity.js | 131 + .../lib/sugar-web/activity/shortcut.js | 57 + .../Markdown.activity/lib/sugar-web/bus.js | 229 + .../lib/sugar-web/datastore.js | 221 + .../Markdown.activity/lib/sugar-web/env.js | 27 + .../lib/sugar-web/graphics/README.md | 39 + .../sugar-web/graphics/activitypalette.html | 9 + .../lib/sugar-web/graphics/activitypalette.js | 60 + .../lib/sugar-web/graphics/css/sugar.css | 454 + .../lib/sugar-web/graphics/css/sugar.less | 586 + .../lib/sugar-web/graphics/grid.js | 54 + .../lib/sugar-web/graphics/icon.js | 81 + .../graphics/icons/actions/activity-stop.svg | 6 + .../actions/checkbox-checked-selected.svg | 27 + .../icons/actions/checkbox-checked.svg | 27 + .../actions/checkbox-unchecked-selected.svg | 22 + .../icons/actions/checkbox-unchecked.svg | 22 + .../icons/actions/dialog-cancel-active.svg | 6 + .../graphics/icons/actions/dialog-cancel.svg | 6 + .../icons/actions/dialog-ok-active.svg | 6 + .../graphics/icons/actions/dialog-ok.svg | 6 + .../icons/actions/entry-cancel-active.svg | 23 + .../icons/actions/entry-cancel-disabled.svg | 21 + .../graphics/icons/actions/entry-cancel.svg | 21 + .../icons/actions/radio-active-selected.svg | 31 + .../graphics/icons/actions/radio-active.svg | 31 + .../graphics/icons/actions/radio-selected.svg | 26 + .../graphics/icons/actions/radio.svg | 26 + .../graphics/icons/actions/zoom-groups.svg | 6 + .../graphics/icons/actions/zoom-home.svg | 6 + .../icons/actions/zoom-neighborhood.svg | 6 + .../graphics/icons/emblems/arrow-down.svg | 20 + .../graphics/icons/emblems/arrow-up.svg | 20 + .../lib/sugar-web/graphics/menupalette.html | 8 + .../lib/sugar-web/graphics/menupalette.js | 55 + .../lib/sugar-web/graphics/palette.js | 179 + .../sugar-web/graphics/radiobuttonsgroup.js | 59 + .../lib/sugar-web/graphics/xocolor.js | 729 ++ .../lib/sugar-web/package.json | 10 + activities/Markdown.activity/lib/text.js | 386 + activities/Markdown.activity/lib/webL10n.js | 1029 ++ activities/Markdown.activity/package.json | 13 + activities/Markdown.activity/setup.py | 5 + activities/Memorize.activity/LICENSE | 202 + .../activity/activity-icon.svg | 14 + .../Memorize.activity/activity/activity.info | 6 + activities/Memorize.activity/css/activity.css | 79 + activities/Memorize.activity/icons/4x4.svg | 88 + activities/Memorize.activity/icons/5x5.svg | 88 + activities/Memorize.activity/icons/6x6.svg | 88 + .../Memorize.activity/icons/restart-game.svg | 76 + .../Memorize.activity/images/suit-1.svg | 64 + .../Memorize.activity/images/suit-2.svg | 76 + activities/Memorize.activity/index.html | 23 + activities/Memorize.activity/js/activity.js | 85 + activities/Memorize.activity/js/controller.js | 61 + activities/Memorize.activity/js/loader.js | 8 + activities/Memorize.activity/js/model.js | 147 + activities/Memorize.activity/js/view.js | 141 + activities/Memorize.activity/lib/domReady.js | 129 + activities/Memorize.activity/lib/mustache.js | 610 ++ activities/Memorize.activity/lib/require.js | 2000 ++++ .../Memorize.activity/lib/sugar-web/LICENSE | 202 + .../Memorize.activity/lib/sugar-web/README.md | 7 + .../lib/sugar-web/activity/activity.js | 114 + .../lib/sugar-web/activity/shortcut.js | 57 + .../Memorize.activity/lib/sugar-web/bus.js | 229 + .../lib/sugar-web/datastore.js | 221 + .../Memorize.activity/lib/sugar-web/env.js | 27 + .../lib/sugar-web/graphics/README.md | 39 + .../lib/sugar-web/graphics/activitypalette.js | 70 + .../lib/sugar-web/graphics/css/sugar.css | 456 + .../lib/sugar-web/graphics/css/sugar.less | 589 ++ .../lib/sugar-web/graphics/grid.js | 54 + .../lib/sugar-web/graphics/icon.js | 81 + .../graphics/icons/actions/activity-stop.svg | 6 + .../actions/checkbox-checked-selected.svg | 27 + .../icons/actions/checkbox-checked.svg | 27 + .../actions/checkbox-unchecked-selected.svg | 22 + .../icons/actions/checkbox-unchecked.svg | 22 + .../icons/actions/dialog-cancel-active.svg | 6 + .../graphics/icons/actions/dialog-cancel.svg | 6 + .../icons/actions/dialog-ok-active.svg | 6 + .../graphics/icons/actions/dialog-ok.svg | 6 + .../icons/actions/entry-cancel-active.svg | 23 + .../icons/actions/entry-cancel-disabled.svg | 21 + .../graphics/icons/actions/entry-cancel.svg | 21 + .../icons/actions/radio-active-selected.svg | 31 + .../graphics/icons/actions/radio-active.svg | 31 + .../graphics/icons/actions/radio-selected.svg | 26 + .../graphics/icons/actions/radio.svg | 26 + .../graphics/icons/actions/zoom-groups.svg | 6 + .../graphics/icons/actions/zoom-home.svg | 6 + .../icons/actions/zoom-neighborhood.svg | 6 + .../graphics/icons/emblems/arrow-down.svg | 20 + .../graphics/icons/emblems/arrow-up.svg | 20 + .../lib/sugar-web/graphics/menupalette.js | 64 + .../lib/sugar-web/graphics/palette.js | 179 + .../sugar-web/graphics/radiobuttonsgroup.js | 59 + .../lib/sugar-web/graphics/xocolor.js | 729 ++ .../lib/sugar-web/package.json | 9 + activities/Memorize.activity/lib/webL10n.js | 1029 ++ activities/Memorize.activity/package.json | 12 + activities/Memorize.activity/setup.py | 5 + activities/Paint.activity/LICENSE | 202 + .../Paint.activity/activity/activity-icon.svg | 15 + .../Paint.activity/activity/activity.info | 6 + activities/Paint.activity/css/activity.css | 33 + .../Paint.activity/icons/edit-clear.svg | 6 + activities/Paint.activity/index.html | 24 + activities/Paint.activity/js/activity.js | 200 + activities/Paint.activity/js/colorpalette.js | 99 + activities/Paint.activity/js/loader.js | 14 + activities/Paint.activity/lib/domReady.js | 129 + activities/Paint.activity/lib/easel.js | 135 + activities/Paint.activity/lib/mustache.js | 610 ++ activities/Paint.activity/lib/require.js | 2000 ++++ .../Paint.activity/lib/sugar-web/LICENSE | 202 + .../Paint.activity/lib/sugar-web/README.md | 7 + .../lib/sugar-web/activity/activity.js | 114 + .../lib/sugar-web/activity/shortcut.js | 57 + .../Paint.activity/lib/sugar-web/bus.js | 229 + .../Paint.activity/lib/sugar-web/datastore.js | 221 + .../Paint.activity/lib/sugar-web/env.js | 27 + .../lib/sugar-web/graphics/README.md | 39 + .../lib/sugar-web/graphics/activitypalette.js | 70 + .../lib/sugar-web/graphics/css/sugar.css | 456 + .../lib/sugar-web/graphics/css/sugar.less | 589 ++ .../lib/sugar-web/graphics/grid.js | 54 + .../lib/sugar-web/graphics/icon.js | 81 + .../graphics/icons/actions/activity-stop.svg | 6 + .../actions/checkbox-checked-selected.svg | 27 + .../icons/actions/checkbox-checked.svg | 27 + .../actions/checkbox-unchecked-selected.svg | 22 + .../icons/actions/checkbox-unchecked.svg | 22 + .../icons/actions/dialog-cancel-active.svg | 6 + .../graphics/icons/actions/dialog-cancel.svg | 6 + .../icons/actions/dialog-ok-active.svg | 6 + .../graphics/icons/actions/dialog-ok.svg | 6 + .../icons/actions/entry-cancel-active.svg | 23 + .../icons/actions/entry-cancel-disabled.svg | 21 + .../graphics/icons/actions/entry-cancel.svg | 21 + .../icons/actions/radio-active-selected.svg | 31 + .../graphics/icons/actions/radio-active.svg | 31 + .../graphics/icons/actions/radio-selected.svg | 26 + .../graphics/icons/actions/radio.svg | 26 + .../graphics/icons/actions/zoom-groups.svg | 6 + .../graphics/icons/actions/zoom-home.svg | 6 + .../icons/actions/zoom-neighborhood.svg | 6 + .../graphics/icons/emblems/arrow-down.svg | 20 + .../graphics/icons/emblems/arrow-up.svg | 20 + .../lib/sugar-web/graphics/menupalette.js | 64 + .../lib/sugar-web/graphics/palette.js | 179 + .../sugar-web/graphics/radiobuttonsgroup.js | 59 + .../lib/sugar-web/graphics/xocolor.js | 729 ++ .../Paint.activity/lib/sugar-web/package.json | 9 + activities/Paint.activity/lib/webL10n.js | 1029 ++ activities/Paint.activity/package.json | 12 + activities/Paint.activity/setup.py | 5 + activities/Stopwatch.activity/LICENSE | 202 + .../activity/activity-icon.svg | 83 + .../Stopwatch.activity/activity/activity.info | 6 + .../Stopwatch.activity/css/activity.css | 120 + .../Stopwatch.activity/icons/edit-clear.svg | 6 + .../Stopwatch.activity/icons/edit-mark.svg | 25 + .../Stopwatch.activity/icons/list-add.svg | 6 + .../Stopwatch.activity/icons/list-remove.svg | 6 + .../icons/media-playback-pause.svg | 9 + .../icons/media-playback-start.svg | 15 + .../icons/media-playback-stop.svg | 6 + activities/Stopwatch.activity/index.html | 25 + activities/Stopwatch.activity/js/activity.js | 186 + activities/Stopwatch.activity/js/loader.js | 8 + activities/Stopwatch.activity/lib/domReady.js | 129 + activities/Stopwatch.activity/lib/mustache.js | 610 ++ activities/Stopwatch.activity/lib/require.js | 2000 ++++ .../Stopwatch.activity/lib/sugar-web/LICENSE | 202 + .../lib/sugar-web/README.md | 7 + .../lib/sugar-web/activity/activity.js | 85 + .../lib/sugar-web/activity/shortcut.js | 57 + .../Stopwatch.activity/lib/sugar-web/bus.js | 229 + .../lib/sugar-web/datastore.js | 217 + .../Stopwatch.activity/lib/sugar-web/env.js | 27 + .../lib/sugar-web/graphics/README.md | 39 + .../lib/sugar-web/graphics/activitypalette.js | 105 + .../lib/sugar-web/graphics/css/sugar.css | 423 + .../lib/sugar-web/graphics/css/sugar.less | 550 + .../lib/sugar-web/graphics/grid.js | 54 + .../lib/sugar-web/graphics/icon.js | 81 + .../graphics/icons/actions/activity-stop.svg | 6 + .../actions/checkbox-checked-selected.svg | 27 + .../icons/actions/checkbox-checked.svg | 27 + .../actions/checkbox-unchecked-selected.svg | 22 + .../icons/actions/checkbox-unchecked.svg | 22 + .../icons/actions/dialog-cancel-active.svg | 6 + .../graphics/icons/actions/dialog-cancel.svg | 6 + .../icons/actions/dialog-ok-active.svg | 6 + .../graphics/icons/actions/dialog-ok.svg | 6 + .../icons/actions/entry-cancel-active.svg | 23 + .../icons/actions/entry-cancel-disabled.svg | 21 + .../graphics/icons/actions/entry-cancel.svg | 21 + .../icons/actions/radio-active-selected.svg | 31 + .../graphics/icons/actions/radio-active.svg | 31 + .../graphics/icons/actions/radio-selected.svg | 26 + .../graphics/icons/actions/radio.svg | 26 + .../graphics/icons/actions/zoom-groups.svg | 6 + .../graphics/icons/actions/zoom-home.svg | 6 + .../icons/actions/zoom-neighborhood.svg | 6 + .../graphics/icons/emblems/arrow-down.svg | 20 + .../graphics/icons/emblems/arrow-up.svg | 20 + .../lib/sugar-web/graphics/palette.js | 179 + .../sugar-web/graphics/radiobuttonsgroup.js | 59 + .../lib/sugar-web/graphics/xocolor.js | 729 ++ .../lib/sugar-web/package.json | 8 + activities/Stopwatch.activity/lib/webL10n.js | 1029 ++ activities/Stopwatch.activity/package.json | 12 + activities/Stopwatch.activity/setup.py | 5 + activities/WelcomeWeb.activity/activity.py | 366 + .../activity/activity.info | 7 + .../activity/welcome-activity.svg | 17 + .../WelcomeWeb.activity/css/activity.css | 57 + .../icons/go-next-paired.svg | 8 + .../icons/go-previous-paired.svg | 8 + .../icons/view-fullscreen.svg | 6 + .../WelcomeWeb.activity/icons/view-return.svg | 74 + .../WelcomeWeb.activity/images/0/Init-1.gif | Bin 0 -> 28680 bytes .../images/1/1-PrenderPesonalizar-1.gif | Bin 0 -> 29334 bytes .../images/1/1-PrenderPesonalizar-10.gif | Bin 0 -> 24841 bytes .../images/1/1-PrenderPesonalizar-11.gif | Bin 0 -> 27067 bytes .../images/1/1-PrenderPesonalizar-2.gif | Bin 0 -> 24591 bytes .../images/1/1-PrenderPesonalizar-3.gif | Bin 0 -> 24570 bytes .../images/1/1-PrenderPesonalizar-4.gif | Bin 0 -> 24624 bytes .../images/1/1-PrenderPesonalizar-5.gif | Bin 0 -> 24618 bytes .../images/1/1-PrenderPesonalizar-6.gif | Bin 0 -> 25079 bytes .../images/1/1-PrenderPesonalizar-7.gif | Bin 0 -> 24002 bytes .../images/1/1-PrenderPesonalizar-8.gif | Bin 0 -> 24313 bytes .../images/1/1-PrenderPesonalizar-9.gif | Bin 0 -> 24687 bytes .../images/2/2-ElFrame-1.gif | Bin 0 -> 35702 bytes .../images/2/2-ElFrame-2.gif | Bin 0 -> 28174 bytes .../images/2/2-ElFrame-3.gif | Bin 0 -> 29444 bytes .../images/2/2-ElFrame-4.gif | Bin 0 -> 29953 bytes .../images/2/2-ElFrame-5.gif | Bin 0 -> 42190 bytes .../images/2/2-ElFrame-6.gif | Bin 0 -> 39793 bytes .../images/2/2-ElFrame-7.gif | Bin 0 -> 39978 bytes .../images/2/2-ElFrame-8.gif | Bin 0 -> 39535 bytes .../images/2/2-ElFrame-9.gif | Bin 0 -> 31427 bytes .../images/3/3-LasVistas-1.gif | Bin 0 -> 27626 bytes .../images/3/3-LasVistas-2.gif | Bin 0 -> 44016 bytes .../images/3/3-LasVistas-3.gif | Bin 0 -> 33890 bytes .../images/3/3-LasVistas-4.gif | Bin 0 -> 38757 bytes .../images/4/4-AbrirCerrar-1.gif | Bin 0 -> 27463 bytes .../images/4/4-AbrirCerrar-2.gif | Bin 0 -> 31783 bytes .../images/4/4-AbrirCerrar-3.gif | Bin 0 -> 24140 bytes .../images/4/4-AbrirCerrar-4.gif | Bin 0 -> 33042 bytes .../images/4/4-AbrirCerrar-5.gif | Bin 0 -> 30825 bytes .../images/4/4-AbrirCerrar-6.gif | Bin 0 -> 32600 bytes .../images/4/4-AbrirCerrar-7.gif | Bin 0 -> 35054 bytes .../images/4/4-AbrirCerrar-8.gif | Bin 0 -> 36040 bytes .../images/5/5-Journal-1.gif | Bin 0 -> 25836 bytes .../images/5/5-Journal-2.gif | Bin 0 -> 36694 bytes .../images/5/5-Journal-3.gif | Bin 0 -> 39725 bytes .../images/5/5-Journal-4.gif | Bin 0 -> 43572 bytes .../images/5/5-Journal-5.gif | Bin 0 -> 30629 bytes .../images/5/5-Journal-6.gif | Bin 0 -> 39113 bytes .../images/6/6-ApagarXO-1.gif | Bin 0 -> 39509 bytes .../images/6/6-ApagarXO-2.gif | Bin 0 -> 30948 bytes .../images/6/6-ApagarXO-3.gif | Bin 0 -> 36170 bytes .../images/6/6-ApagarXO-4.gif | Bin 0 -> 39749 bytes .../images/6/6-ApagarXO-5.gif | Bin 0 -> 37654 bytes activities/WelcomeWeb.activity/index.html | 40 + activities/WelcomeWeb.activity/js/activity.js | 93 + .../WelcomeWeb.activity/js/activity_custom.js | 95 + .../WelcomeWeb.activity/js/controller.js | 43 + activities/WelcomeWeb.activity/js/jquery.js | 9404 +++++++++++++++++ activities/WelcomeWeb.activity/js/loader.js | 8 + activities/WelcomeWeb.activity/js/model.js | 47 + activities/WelcomeWeb.activity/js/view.js | 40 + .../WelcomeWeb.activity/lib/domReady.js | 129 + activities/WelcomeWeb.activity/lib/require.js | 2000 ++++ .../WelcomeWeb.activity/lib/sugar-web/LICENSE | 202 + .../lib/sugar-web/README.md | 7 + .../lib/sugar-web/activity/activity.js | 114 + .../lib/sugar-web/activity/shortcut.js | 57 + .../WelcomeWeb.activity/lib/sugar-web/bus.js | 229 + .../lib/sugar-web/datastore.js | 221 + .../WelcomeWeb.activity/lib/sugar-web/env.js | 27 + .../lib/sugar-web/graphics/README.md | 39 + .../lib/sugar-web/graphics/activitypalette.js | 70 + .../lib/sugar-web/graphics/css/sugar.css | 456 + .../lib/sugar-web/graphics/css/sugar.less | 589 ++ .../lib/sugar-web/graphics/grid.js | 54 + .../lib/sugar-web/graphics/icon.js | 81 + .../graphics/icons/actions/activity-stop.svg | 6 + .../graphics/icons/emblems/arrow-down.svg | 20 + .../graphics/icons/emblems/arrow-up.svg | 20 + .../lib/sugar-web/graphics/menupalette.js | 64 + .../lib/sugar-web/graphics/palette.js | 179 + .../sugar-web/graphics/radiobuttonsgroup.js | 59 + .../lib/sugar-web/graphics/xocolor.js | 729 ++ .../lib/sugar-web/package.json | 9 + activities/WelcomeWeb.activity/lib/webL10n.js | 1029 ++ activities/WelcomeWeb.activity/setup.py | 5 + css/activity.css | 26 + css/styles.css | 24 + icons/owner-icon.svg | 89 + index.html | 37 + js/app.js | 67 + js/desktop.js | 15 + js/loader.js | 8 + js/settings.js | 83 + js/storage.js | 30 + js/webactivity.js | 51 + lib/domReady.js | 129 + lib/enyo/enyo.css | 181 + lib/enyo/enyo.js | 4306 ++++++++ lib/enyo/lib/canvas/canvas.js | 163 + lib/enyo/lib/layout/layout.css | 273 + lib/enyo/lib/layout/layout.js | 2449 +++++ lib/require.js | 2000 ++++ lib/sugar-web/LICENSE | 202 + lib/sugar-web/README.md | 7 + lib/sugar-web/activity/activity.js | 94 + lib/sugar-web/activity/shortcut.js | 57 + lib/sugar-web/bus.js | 229 + lib/sugar-web/datastore.js | 221 + lib/sugar-web/env.js | 27 + lib/sugar-web/graphics/README.md | 39 + lib/sugar-web/graphics/activitypalette.js | 70 + lib/sugar-web/graphics/css/sugar.css | 456 + lib/sugar-web/graphics/css/sugar.less | 589 ++ lib/sugar-web/graphics/grid.js | 54 + lib/sugar-web/graphics/icon.js | 81 + .../graphics/icons/actions/activity-stop.svg | 6 + .../actions/checkbox-checked-selected.svg | 27 + .../icons/actions/checkbox-checked.svg | 27 + .../actions/checkbox-unchecked-selected.svg | 22 + .../icons/actions/checkbox-unchecked.svg | 22 + .../icons/actions/dialog-cancel-active.svg | 6 + .../graphics/icons/actions/dialog-cancel.svg | 6 + .../icons/actions/dialog-ok-active.svg | 6 + .../graphics/icons/actions/dialog-ok.svg | 6 + .../icons/actions/entry-cancel-active.svg | 23 + .../icons/actions/entry-cancel-disabled.svg | 21 + .../graphics/icons/actions/entry-cancel.svg | 21 + .../icons/actions/radio-active-selected.svg | 31 + .../graphics/icons/actions/radio-active.svg | 31 + .../graphics/icons/actions/radio-selected.svg | 26 + .../graphics/icons/actions/radio.svg | 26 + .../graphics/icons/actions/zoom-groups.svg | 6 + .../graphics/icons/actions/zoom-home.svg | 6 + .../icons/actions/zoom-neighborhood.svg | 6 + .../graphics/icons/emblems/arrow-down.svg | 20 + .../graphics/icons/emblems/arrow-up.svg | 20 + lib/sugar-web/graphics/menupalette.js | 64 + lib/sugar-web/graphics/palette.js | 179 + lib/sugar-web/graphics/radiobuttonsgroup.js | 59 + lib/sugar-web/graphics/xocolor.js | 729 ++ lib/sugar-web/package.json | 9 + lib/sugar-web/test/busSpec.js | 87 + lib/sugar-web/test/datastoreSpec.js | 238 + lib/sugar-web/test/envSpec.js | 14 + lib/sugar-web/test/graphics/iconSpec.js | 56 + .../test/graphics/menupaletteSpec.js | 75 + lib/sugar-web/test/graphics/paletteSpec.js | 33 + .../test/graphics/radiobuttonsgroupSpec.js | 91 + lib/sugar-web/test/karma.conf.js | 81 + lib/sugar-web/test/loader.js | 19 + lib/webL10n.js | 1029 ++ 732 files changed, 126452 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 activities/Clock.activity/activity/activity-clock.svg create mode 100644 activities/Clock.activity/activity/activity.info create mode 100644 activities/Clock.activity/clock.svg create mode 100644 activities/Clock.activity/css/activity.css create mode 100644 activities/Clock.activity/icons/nice-clock.svg create mode 100644 activities/Clock.activity/icons/simple-clock.svg create mode 100644 activities/Clock.activity/icons/write-date.svg create mode 100644 activities/Clock.activity/icons/write-time.svg create mode 100644 activities/Clock.activity/index.html create mode 100644 activities/Clock.activity/js/activity.js create mode 100644 activities/Clock.activity/js/loader.js create mode 100644 activities/Clock.activity/lib/domReady.js create mode 100644 activities/Clock.activity/lib/moment.js create mode 100644 activities/Clock.activity/lib/mustache.js create mode 100644 activities/Clock.activity/lib/require.js create mode 100644 activities/Clock.activity/lib/sugar-web/LICENSE create mode 100644 activities/Clock.activity/lib/sugar-web/README.md create mode 100644 activities/Clock.activity/lib/sugar-web/activity/activity.js create mode 100644 activities/Clock.activity/lib/sugar-web/activity/shortcut.js create mode 100644 activities/Clock.activity/lib/sugar-web/bus.js create mode 100644 activities/Clock.activity/lib/sugar-web/datastore.js create mode 100644 activities/Clock.activity/lib/sugar-web/env.js create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/README.md create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/activitypalette.js create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/css/sugar.css create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/css/sugar.less create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/grid.js create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icon.js create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/radio.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/menupalette.js create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/palette.js create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 activities/Clock.activity/lib/sugar-web/graphics/xocolor.js create mode 100644 activities/Clock.activity/lib/sugar-web/package.json create mode 100644 activities/Clock.activity/lib/webL10n.js create mode 100644 activities/Clock.activity/package.json create mode 100644 activities/Clock.activity/setup.py create mode 100644 activities/ConnecttheDots.activity/.gitignore create mode 100644 activities/ConnecttheDots.activity/LICENSE create mode 100644 activities/ConnecttheDots.activity/activity/activity-icon.svg create mode 100644 activities/ConnecttheDots.activity/activity/activity.info create mode 100644 activities/ConnecttheDots.activity/css/activity.css create mode 100644 activities/ConnecttheDots.activity/icons/new-button.svg create mode 100644 activities/ConnecttheDots.activity/images/dot.svg create mode 100644 activities/ConnecttheDots.activity/images/pen.svg create mode 100644 activities/ConnecttheDots.activity/images/star.svg create mode 100644 activities/ConnecttheDots.activity/index.html create mode 100644 activities/ConnecttheDots.activity/js/activity-works.js create mode 100644 activities/ConnecttheDots.activity/js/activity.js create mode 100644 activities/ConnecttheDots.activity/js/loader.js create mode 100644 activities/ConnecttheDots.activity/js/shapes.js create mode 100644 activities/ConnecttheDots.activity/lib/domReady.js create mode 100644 activities/ConnecttheDots.activity/lib/easeljs.js create mode 100644 activities/ConnecttheDots.activity/lib/handlebars.js create mode 100644 activities/ConnecttheDots.activity/lib/require.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/LICENSE create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/README.md create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/activity/activity.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/activity/shortcut.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/bus.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/datastore.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/env.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/README.md create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/activitypalette.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/css/sugar.css create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/css/sugar.less create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/grid.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icon.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/palette.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/graphics/xocolor.js create mode 100644 activities/ConnecttheDots.activity/lib/sugar-web/package.json create mode 100644 activities/ConnecttheDots.activity/lib/webL10n.js create mode 100644 activities/ConnecttheDots.activity/package.json create mode 100644 activities/ConnecttheDots.activity/po/ConnecttheDots.pot create mode 100644 activities/ConnecttheDots.activity/setup.py create mode 100644 activities/Gears.activity/LICENSE create mode 100644 activities/Gears.activity/activity/activity-icon.svg create mode 100644 activities/Gears.activity/activity/activity.info create mode 100644 activities/Gears.activity/css/activity.css create mode 100644 activities/Gears.activity/icons/ChainIcon.svg create mode 100644 activities/Gears.activity/icons/GearIcon.svg create mode 100644 activities/Gears.activity/icons/MomentumIcon.svg create mode 100644 activities/Gears.activity/icons/edit-clear.svg create mode 100644 activities/Gears.activity/icons/media-playback-start.svg create mode 100644 activities/Gears.activity/icons/toolbar-help.svg create mode 100644 activities/Gears.activity/img/ChainIcon.png create mode 100644 activities/Gears.activity/img/ClearIcon.png create mode 100644 activities/Gears.activity/img/CloudIcon.png create mode 100644 activities/Gears.activity/img/Gear.png create mode 100644 activities/Gears.activity/img/GearIcon.png create mode 100644 activities/Gears.activity/img/HelpIcon.png create mode 100644 activities/Gears.activity/img/MomentumIcon.png create mode 100644 activities/Gears.activity/img/PlayIcon.png create mode 100644 activities/Gears.activity/img/hand.png create mode 100644 activities/Gears.activity/index.html create mode 100644 activities/Gears.activity/js/activity.js create mode 100644 activities/Gears.activity/js/hammer.min.js create mode 100644 activities/Gears.activity/js/loader.js create mode 100644 activities/Gears.activity/lib/domReady.js create mode 100644 activities/Gears.activity/lib/gearsketch/gearsketch_main.js create mode 100644 activities/Gears.activity/lib/gearsketch/gearsketch_main.map create mode 100644 activities/Gears.activity/lib/gearsketch/gearsketch_model.js create mode 100644 activities/Gears.activity/lib/gearsketch/gearsketch_model.map create mode 100644 activities/Gears.activity/lib/gearsketch/gearsketch_util.js create mode 100644 activities/Gears.activity/lib/gearsketch/gearsketch_util.map create mode 100644 activities/Gears.activity/lib/gearsketch/src/gearsketch_main.coffee create mode 100644 activities/Gears.activity/lib/gearsketch/src/gearsketch_model.coffee create mode 100644 activities/Gears.activity/lib/gearsketch/src/gearsketch_util.coffee create mode 100644 activities/Gears.activity/lib/mustache.js create mode 100644 activities/Gears.activity/lib/require.js create mode 100644 activities/Gears.activity/lib/sugar-web/LICENSE create mode 100644 activities/Gears.activity/lib/sugar-web/README.md create mode 100644 activities/Gears.activity/lib/sugar-web/activity/activity.js create mode 100644 activities/Gears.activity/lib/sugar-web/activity/shortcut.js create mode 100644 activities/Gears.activity/lib/sugar-web/bus.js create mode 100644 activities/Gears.activity/lib/sugar-web/datastore.js create mode 100644 activities/Gears.activity/lib/sugar-web/env.js create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/README.md create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/activitypalette.js create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/css/sugar-200dpi.css create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/css/sugar-200dpi.less create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/css/sugar-96dpi.css create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/css/sugar-96dpi.less create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/css/sugar.less create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/grid.js create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icon.js create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/radio.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/menupalette.js create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/palette.js create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 activities/Gears.activity/lib/sugar-web/graphics/xocolor.js create mode 100644 activities/Gears.activity/lib/sugar-web/package.json create mode 100644 activities/Gears.activity/lib/webL10n.js create mode 100644 activities/Gears.activity/package.json create mode 100644 activities/Gears.activity/setup.py create mode 100644 activities/GetThingsDone.activity/activity/activity-icon.svg create mode 100644 activities/GetThingsDone.activity/activity/activity.info create mode 100644 activities/GetThingsDone.activity/css/activity.css create mode 100644 activities/GetThingsDone.activity/icons/list-add.svg create mode 100644 activities/GetThingsDone.activity/icons/list-remove.svg create mode 100644 activities/GetThingsDone.activity/index.html create mode 100644 activities/GetThingsDone.activity/js/activity.js create mode 100644 activities/GetThingsDone.activity/js/controller.js create mode 100644 activities/GetThingsDone.activity/js/loader.js create mode 100644 activities/GetThingsDone.activity/js/model.js create mode 100644 activities/GetThingsDone.activity/js/view.js create mode 100644 activities/GetThingsDone.activity/lib/domReady.js create mode 100644 activities/GetThingsDone.activity/lib/require.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/LICENSE create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/README.md create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/activity/activity.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/activity/shortcut.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/bus.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/datastore.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/env.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/README.md create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/activitypalette.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/css/sugar.css create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/css/sugar.less create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/grid.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icon.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/menupalette.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/palette.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/graphics/xocolor.js create mode 100644 activities/GetThingsDone.activity/lib/sugar-web/package.json create mode 100644 activities/GetThingsDone.activity/lib/webL10n.js create mode 100644 activities/GetThingsDone.activity/package.json create mode 100644 activities/GetThingsDone.activity/setup.py create mode 100644 activities/LastOneLoses.activity/activity/activity-icon.svg create mode 100644 activities/LastOneLoses.activity/activity/activity.info create mode 100644 activities/LastOneLoses.activity/audio/applause.ogg create mode 100644 activities/LastOneLoses.activity/audio/disappointed.ogg create mode 100644 activities/LastOneLoses.activity/css/activity.css create mode 100644 activities/LastOneLoses.activity/css/styles.css create mode 100644 activities/LastOneLoses.activity/icons/computer-icon.svg create mode 100644 activities/LastOneLoses.activity/icons/computer-win.svg create mode 100644 activities/LastOneLoses.activity/icons/empty-icon.svg create mode 100644 activities/LastOneLoses.activity/icons/level-easy.svg create mode 100644 activities/LastOneLoses.activity/icons/level-hard.svg create mode 100644 activities/LastOneLoses.activity/icons/level-medium.svg create mode 100644 activities/LastOneLoses.activity/icons/new-game-icon.svg create mode 100644 activities/LastOneLoses.activity/icons/play.png create mode 100644 activities/LastOneLoses.activity/icons/player-icon.svg create mode 100644 activities/LastOneLoses.activity/icons/player-win.svg create mode 100644 activities/LastOneLoses.activity/icons/switch-player.svg create mode 100644 activities/LastOneLoses.activity/index.html create mode 100644 activities/LastOneLoses.activity/js/activity.js create mode 100644 activities/LastOneLoses.activity/js/app.js create mode 100644 activities/LastOneLoses.activity/js/audio.js create mode 100644 activities/LastOneLoses.activity/js/loader.js create mode 100644 activities/LastOneLoses.activity/js/lolgame.js create mode 100644 activities/LastOneLoses.activity/lib/domReady.js create mode 100644 activities/LastOneLoses.activity/lib/enyo/enyo.css create mode 100644 activities/LastOneLoses.activity/lib/enyo/enyo.js create mode 100644 activities/LastOneLoses.activity/lib/enyo/lib/canvas/canvas.js create mode 100644 activities/LastOneLoses.activity/lib/enyo/lib/layout/layout.css create mode 100644 activities/LastOneLoses.activity/lib/enyo/lib/layout/layout.js create mode 100644 activities/LastOneLoses.activity/lib/require.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/README.md create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/activity/activity.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/activity/shortcut.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/bus.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/datastore.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/env.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/README.md create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/activitypalette.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/css/sugar.css create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/css/sugar.less create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/grid.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icon.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/radio.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/menupalette.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/palette.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/graphics/xocolor.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/package.json create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/test/busSpec.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/test/datastoreSpec.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/test/envSpec.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/test/graphics/iconSpec.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/test/graphics/menupaletteSpec.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/test/graphics/paletteSpec.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/test/graphics/radiobuttonsgroupSpec.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/test/karma.conf.js create mode 100644 activities/LastOneLoses.activity/lib/sugar-web/test/loader.js create mode 100644 activities/LastOneLoses.activity/lib/webL10n.js create mode 100644 activities/LastOneLoses.activity/package.json create mode 100644 activities/LastOneLoses.activity/setup.py create mode 100644 activities/Markdown.activity/LICENSE create mode 100644 activities/Markdown.activity/activity/activity-icon.svg create mode 100644 activities/Markdown.activity/activity/activity.info create mode 100644 activities/Markdown.activity/activity/code.svg create mode 100644 activities/Markdown.activity/activity/edit-redo.svg create mode 100644 activities/Markdown.activity/activity/edit-undo.svg create mode 100644 activities/Markdown.activity/activity/font-text.svg create mode 100644 activities/Markdown.activity/activity/format-text-bold.svg create mode 100644 activities/Markdown.activity/activity/format-text-italic.svg create mode 100644 activities/Markdown.activity/activity/format-text.svg create mode 100644 activities/Markdown.activity/activity/horizontal-line3.svg create mode 100644 activities/Markdown.activity/activity/insert-picture.svg create mode 100644 activities/Markdown.activity/activity/link.svg create mode 100644 activities/Markdown.activity/activity/paragraph-h1.svg create mode 100644 activities/Markdown.activity/activity/quote.svg create mode 100644 activities/Markdown.activity/activity/toolbar-bulletlist.svg create mode 100644 activities/Markdown.activity/activity/toolbar-numbered-list.svg create mode 100644 activities/Markdown.activity/activity/wmd-buttons.png create mode 100644 activities/Markdown.activity/css/activity.css create mode 100644 activities/Markdown.activity/index.html create mode 100644 activities/Markdown.activity/js/activity.js create mode 100644 activities/Markdown.activity/js/loader.js create mode 100644 activities/Markdown.activity/lib/Markdown.Converter.js create mode 100644 activities/Markdown.activity/lib/Markdown.Editor.js create mode 100644 activities/Markdown.activity/lib/Markdown.Sanitizer.js create mode 100644 activities/Markdown.activity/lib/domReady.js create mode 100644 activities/Markdown.activity/lib/mustache.js create mode 100644 activities/Markdown.activity/lib/require.js create mode 100644 activities/Markdown.activity/lib/sugar-web/LICENSE create mode 100644 activities/Markdown.activity/lib/sugar-web/README.md create mode 100644 activities/Markdown.activity/lib/sugar-web/activity/activity.js create mode 100644 activities/Markdown.activity/lib/sugar-web/activity/shortcut.js create mode 100644 activities/Markdown.activity/lib/sugar-web/bus.js create mode 100644 activities/Markdown.activity/lib/sugar-web/datastore.js create mode 100644 activities/Markdown.activity/lib/sugar-web/env.js create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/README.md create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/activitypalette.html create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/activitypalette.js create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/css/sugar.css create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/css/sugar.less create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/grid.js create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icon.js create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/radio.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/menupalette.html create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/menupalette.js create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/palette.js create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 activities/Markdown.activity/lib/sugar-web/graphics/xocolor.js create mode 100644 activities/Markdown.activity/lib/sugar-web/package.json create mode 100644 activities/Markdown.activity/lib/text.js create mode 100644 activities/Markdown.activity/lib/webL10n.js create mode 100644 activities/Markdown.activity/package.json create mode 100644 activities/Markdown.activity/setup.py create mode 100644 activities/Memorize.activity/LICENSE create mode 100644 activities/Memorize.activity/activity/activity-icon.svg create mode 100644 activities/Memorize.activity/activity/activity.info create mode 100644 activities/Memorize.activity/css/activity.css create mode 100644 activities/Memorize.activity/icons/4x4.svg create mode 100644 activities/Memorize.activity/icons/5x5.svg create mode 100644 activities/Memorize.activity/icons/6x6.svg create mode 100644 activities/Memorize.activity/icons/restart-game.svg create mode 100644 activities/Memorize.activity/images/suit-1.svg create mode 100644 activities/Memorize.activity/images/suit-2.svg create mode 100644 activities/Memorize.activity/index.html create mode 100644 activities/Memorize.activity/js/activity.js create mode 100644 activities/Memorize.activity/js/controller.js create mode 100644 activities/Memorize.activity/js/loader.js create mode 100644 activities/Memorize.activity/js/model.js create mode 100644 activities/Memorize.activity/js/view.js create mode 100644 activities/Memorize.activity/lib/domReady.js create mode 100644 activities/Memorize.activity/lib/mustache.js create mode 100644 activities/Memorize.activity/lib/require.js create mode 100644 activities/Memorize.activity/lib/sugar-web/LICENSE create mode 100644 activities/Memorize.activity/lib/sugar-web/README.md create mode 100644 activities/Memorize.activity/lib/sugar-web/activity/activity.js create mode 100644 activities/Memorize.activity/lib/sugar-web/activity/shortcut.js create mode 100644 activities/Memorize.activity/lib/sugar-web/bus.js create mode 100644 activities/Memorize.activity/lib/sugar-web/datastore.js create mode 100644 activities/Memorize.activity/lib/sugar-web/env.js create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/README.md create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/activitypalette.js create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/css/sugar.css create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/css/sugar.less create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/grid.js create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icon.js create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/radio.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/menupalette.js create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/palette.js create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 activities/Memorize.activity/lib/sugar-web/graphics/xocolor.js create mode 100644 activities/Memorize.activity/lib/sugar-web/package.json create mode 100644 activities/Memorize.activity/lib/webL10n.js create mode 100644 activities/Memorize.activity/package.json create mode 100644 activities/Memorize.activity/setup.py create mode 100644 activities/Paint.activity/LICENSE create mode 100644 activities/Paint.activity/activity/activity-icon.svg create mode 100644 activities/Paint.activity/activity/activity.info create mode 100644 activities/Paint.activity/css/activity.css create mode 100644 activities/Paint.activity/icons/edit-clear.svg create mode 100644 activities/Paint.activity/index.html create mode 100644 activities/Paint.activity/js/activity.js create mode 100644 activities/Paint.activity/js/colorpalette.js create mode 100644 activities/Paint.activity/js/loader.js create mode 100644 activities/Paint.activity/lib/domReady.js create mode 100644 activities/Paint.activity/lib/easel.js create mode 100644 activities/Paint.activity/lib/mustache.js create mode 100644 activities/Paint.activity/lib/require.js create mode 100644 activities/Paint.activity/lib/sugar-web/LICENSE create mode 100644 activities/Paint.activity/lib/sugar-web/README.md create mode 100644 activities/Paint.activity/lib/sugar-web/activity/activity.js create mode 100644 activities/Paint.activity/lib/sugar-web/activity/shortcut.js create mode 100644 activities/Paint.activity/lib/sugar-web/bus.js create mode 100644 activities/Paint.activity/lib/sugar-web/datastore.js create mode 100644 activities/Paint.activity/lib/sugar-web/env.js create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/README.md create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/activitypalette.js create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/css/sugar.css create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/css/sugar.less create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/grid.js create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icon.js create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/menupalette.js create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/palette.js create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 activities/Paint.activity/lib/sugar-web/graphics/xocolor.js create mode 100644 activities/Paint.activity/lib/sugar-web/package.json create mode 100644 activities/Paint.activity/lib/webL10n.js create mode 100644 activities/Paint.activity/package.json create mode 100644 activities/Paint.activity/setup.py create mode 100644 activities/Stopwatch.activity/LICENSE create mode 100644 activities/Stopwatch.activity/activity/activity-icon.svg create mode 100644 activities/Stopwatch.activity/activity/activity.info create mode 100644 activities/Stopwatch.activity/css/activity.css create mode 100644 activities/Stopwatch.activity/icons/edit-clear.svg create mode 100644 activities/Stopwatch.activity/icons/edit-mark.svg create mode 100644 activities/Stopwatch.activity/icons/list-add.svg create mode 100644 activities/Stopwatch.activity/icons/list-remove.svg create mode 100644 activities/Stopwatch.activity/icons/media-playback-pause.svg create mode 100644 activities/Stopwatch.activity/icons/media-playback-start.svg create mode 100644 activities/Stopwatch.activity/icons/media-playback-stop.svg create mode 100644 activities/Stopwatch.activity/index.html create mode 100644 activities/Stopwatch.activity/js/activity.js create mode 100644 activities/Stopwatch.activity/js/loader.js create mode 100644 activities/Stopwatch.activity/lib/domReady.js create mode 100644 activities/Stopwatch.activity/lib/mustache.js create mode 100644 activities/Stopwatch.activity/lib/require.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/LICENSE create mode 100644 activities/Stopwatch.activity/lib/sugar-web/README.md create mode 100644 activities/Stopwatch.activity/lib/sugar-web/activity/activity.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/activity/shortcut.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/bus.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/datastore.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/env.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/README.md create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/activitypalette.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/css/sugar.css create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/css/sugar.less create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/grid.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icon.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/radio.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/palette.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/graphics/xocolor.js create mode 100644 activities/Stopwatch.activity/lib/sugar-web/package.json create mode 100644 activities/Stopwatch.activity/lib/webL10n.js create mode 100644 activities/Stopwatch.activity/package.json create mode 100644 activities/Stopwatch.activity/setup.py create mode 100644 activities/WelcomeWeb.activity/activity.py create mode 100644 activities/WelcomeWeb.activity/activity/activity.info create mode 100644 activities/WelcomeWeb.activity/activity/welcome-activity.svg create mode 100644 activities/WelcomeWeb.activity/css/activity.css create mode 100644 activities/WelcomeWeb.activity/icons/go-next-paired.svg create mode 100644 activities/WelcomeWeb.activity/icons/go-previous-paired.svg create mode 100644 activities/WelcomeWeb.activity/icons/view-fullscreen.svg create mode 100644 activities/WelcomeWeb.activity/icons/view-return.svg create mode 100644 activities/WelcomeWeb.activity/images/0/Init-1.gif create mode 100644 activities/WelcomeWeb.activity/images/1/1-PrenderPesonalizar-1.gif create mode 100644 activities/WelcomeWeb.activity/images/1/1-PrenderPesonalizar-10.gif create mode 100644 activities/WelcomeWeb.activity/images/1/1-PrenderPesonalizar-11.gif create mode 100644 activities/WelcomeWeb.activity/images/1/1-PrenderPesonalizar-2.gif create mode 100644 activities/WelcomeWeb.activity/images/1/1-PrenderPesonalizar-3.gif create mode 100644 activities/WelcomeWeb.activity/images/1/1-PrenderPesonalizar-4.gif create mode 100644 activities/WelcomeWeb.activity/images/1/1-PrenderPesonalizar-5.gif create mode 100644 activities/WelcomeWeb.activity/images/1/1-PrenderPesonalizar-6.gif create mode 100644 activities/WelcomeWeb.activity/images/1/1-PrenderPesonalizar-7.gif create mode 100644 activities/WelcomeWeb.activity/images/1/1-PrenderPesonalizar-8.gif create mode 100644 activities/WelcomeWeb.activity/images/1/1-PrenderPesonalizar-9.gif create mode 100644 activities/WelcomeWeb.activity/images/2/2-ElFrame-1.gif create mode 100644 activities/WelcomeWeb.activity/images/2/2-ElFrame-2.gif create mode 100644 activities/WelcomeWeb.activity/images/2/2-ElFrame-3.gif create mode 100644 activities/WelcomeWeb.activity/images/2/2-ElFrame-4.gif create mode 100644 activities/WelcomeWeb.activity/images/2/2-ElFrame-5.gif create mode 100644 activities/WelcomeWeb.activity/images/2/2-ElFrame-6.gif create mode 100644 activities/WelcomeWeb.activity/images/2/2-ElFrame-7.gif create mode 100644 activities/WelcomeWeb.activity/images/2/2-ElFrame-8.gif create mode 100644 activities/WelcomeWeb.activity/images/2/2-ElFrame-9.gif create mode 100644 activities/WelcomeWeb.activity/images/3/3-LasVistas-1.gif create mode 100644 activities/WelcomeWeb.activity/images/3/3-LasVistas-2.gif create mode 100644 activities/WelcomeWeb.activity/images/3/3-LasVistas-3.gif create mode 100644 activities/WelcomeWeb.activity/images/3/3-LasVistas-4.gif create mode 100644 activities/WelcomeWeb.activity/images/4/4-AbrirCerrar-1.gif create mode 100644 activities/WelcomeWeb.activity/images/4/4-AbrirCerrar-2.gif create mode 100644 activities/WelcomeWeb.activity/images/4/4-AbrirCerrar-3.gif create mode 100644 activities/WelcomeWeb.activity/images/4/4-AbrirCerrar-4.gif create mode 100644 activities/WelcomeWeb.activity/images/4/4-AbrirCerrar-5.gif create mode 100644 activities/WelcomeWeb.activity/images/4/4-AbrirCerrar-6.gif create mode 100644 activities/WelcomeWeb.activity/images/4/4-AbrirCerrar-7.gif create mode 100644 activities/WelcomeWeb.activity/images/4/4-AbrirCerrar-8.gif create mode 100644 activities/WelcomeWeb.activity/images/5/5-Journal-1.gif create mode 100644 activities/WelcomeWeb.activity/images/5/5-Journal-2.gif create mode 100644 activities/WelcomeWeb.activity/images/5/5-Journal-3.gif create mode 100644 activities/WelcomeWeb.activity/images/5/5-Journal-4.gif create mode 100644 activities/WelcomeWeb.activity/images/5/5-Journal-5.gif create mode 100644 activities/WelcomeWeb.activity/images/5/5-Journal-6.gif create mode 100644 activities/WelcomeWeb.activity/images/6/6-ApagarXO-1.gif create mode 100644 activities/WelcomeWeb.activity/images/6/6-ApagarXO-2.gif create mode 100644 activities/WelcomeWeb.activity/images/6/6-ApagarXO-3.gif create mode 100644 activities/WelcomeWeb.activity/images/6/6-ApagarXO-4.gif create mode 100644 activities/WelcomeWeb.activity/images/6/6-ApagarXO-5.gif create mode 100644 activities/WelcomeWeb.activity/index.html create mode 100644 activities/WelcomeWeb.activity/js/activity.js create mode 100644 activities/WelcomeWeb.activity/js/activity_custom.js create mode 100644 activities/WelcomeWeb.activity/js/controller.js create mode 100644 activities/WelcomeWeb.activity/js/jquery.js create mode 100644 activities/WelcomeWeb.activity/js/loader.js create mode 100644 activities/WelcomeWeb.activity/js/model.js create mode 100644 activities/WelcomeWeb.activity/js/view.js create mode 100644 activities/WelcomeWeb.activity/lib/domReady.js create mode 100644 activities/WelcomeWeb.activity/lib/require.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/LICENSE create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/README.md create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/activity/activity.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/activity/shortcut.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/bus.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/datastore.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/env.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/README.md create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/activitypalette.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/css/sugar.css create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/css/sugar.less create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/grid.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/icon.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/menupalette.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/palette.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/graphics/xocolor.js create mode 100644 activities/WelcomeWeb.activity/lib/sugar-web/package.json create mode 100644 activities/WelcomeWeb.activity/lib/webL10n.js create mode 100644 activities/WelcomeWeb.activity/setup.py create mode 100644 css/activity.css create mode 100644 css/styles.css create mode 100644 icons/owner-icon.svg create mode 100644 index.html create mode 100644 js/app.js create mode 100644 js/desktop.js create mode 100644 js/loader.js create mode 100644 js/settings.js create mode 100644 js/storage.js create mode 100644 js/webactivity.js create mode 100644 lib/domReady.js create mode 100644 lib/enyo/enyo.css create mode 100644 lib/enyo/enyo.js create mode 100644 lib/enyo/lib/canvas/canvas.js create mode 100644 lib/enyo/lib/layout/layout.css create mode 100644 lib/enyo/lib/layout/layout.js create mode 100644 lib/require.js create mode 100644 lib/sugar-web/LICENSE create mode 100644 lib/sugar-web/README.md create mode 100644 lib/sugar-web/activity/activity.js create mode 100644 lib/sugar-web/activity/shortcut.js create mode 100644 lib/sugar-web/bus.js create mode 100644 lib/sugar-web/datastore.js create mode 100644 lib/sugar-web/env.js create mode 100644 lib/sugar-web/graphics/README.md create mode 100644 lib/sugar-web/graphics/activitypalette.js create mode 100644 lib/sugar-web/graphics/css/sugar.css create mode 100644 lib/sugar-web/graphics/css/sugar.less create mode 100644 lib/sugar-web/graphics/grid.js create mode 100644 lib/sugar-web/graphics/icon.js create mode 100644 lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg create mode 100644 lib/sugar-web/graphics/icons/actions/checkbox-checked.svg create mode 100644 lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg create mode 100644 lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg create mode 100644 lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg create mode 100644 lib/sugar-web/graphics/icons/actions/dialog-cancel.svg create mode 100644 lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg create mode 100644 lib/sugar-web/graphics/icons/actions/dialog-ok.svg create mode 100644 lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg create mode 100644 lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg create mode 100644 lib/sugar-web/graphics/icons/actions/entry-cancel.svg create mode 100644 lib/sugar-web/graphics/icons/actions/radio-active-selected.svg create mode 100644 lib/sugar-web/graphics/icons/actions/radio-active.svg create mode 100644 lib/sugar-web/graphics/icons/actions/radio-selected.svg create mode 100644 lib/sugar-web/graphics/icons/actions/radio.svg create mode 100644 lib/sugar-web/graphics/icons/actions/zoom-groups.svg create mode 100644 lib/sugar-web/graphics/icons/actions/zoom-home.svg create mode 100644 lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg create mode 100644 lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 lib/sugar-web/graphics/menupalette.js create mode 100644 lib/sugar-web/graphics/palette.js create mode 100644 lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 lib/sugar-web/graphics/xocolor.js create mode 100644 lib/sugar-web/package.json create mode 100644 lib/sugar-web/test/busSpec.js create mode 100644 lib/sugar-web/test/datastoreSpec.js create mode 100644 lib/sugar-web/test/envSpec.js create mode 100644 lib/sugar-web/test/graphics/iconSpec.js create mode 100644 lib/sugar-web/test/graphics/menupaletteSpec.js create mode 100644 lib/sugar-web/test/graphics/paletteSpec.js create mode 100644 lib/sugar-web/test/graphics/radiobuttonsgroupSpec.js create mode 100644 lib/sugar-web/test/karma.conf.js create mode 100644 lib/sugar-web/test/loader.js create mode 100644 lib/webL10n.js diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..412eeda78 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..b9d6bd92f --- /dev/null +++ b/.gitignore @@ -0,0 +1,215 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist/ +build/ +eggs/ +parts/ +var/ +sdist/ +develop-eggs/ +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/activities/Clock.activity/activity/activity-clock.svg b/activities/Clock.activity/activity/activity-clock.svg new file mode 100644 index 000000000..01db45008 --- /dev/null +++ b/activities/Clock.activity/activity/activity-clock.svg @@ -0,0 +1,16 @@ + + +]> + + + + + + + + + + + diff --git a/activities/Clock.activity/activity/activity.info b/activities/Clock.activity/activity/activity.info new file mode 100644 index 000000000..7b3e1b29f --- /dev/null +++ b/activities/Clock.activity/activity/activity.info @@ -0,0 +1,6 @@ +[Activity] +name = Clock +activity_version = 1 +bundle_id = org.sugarlabs.Clock +exec = sugar-activity-web +icon = activity-clock diff --git a/activities/Clock.activity/clock.svg b/activities/Clock.activity/clock.svg new file mode 100644 index 000000000..cdf675397 --- /dev/null +++ b/activities/Clock.activity/clock.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/activities/Clock.activity/css/activity.css b/activities/Clock.activity/css/activity.css new file mode 100644 index 000000000..8a9385155 --- /dev/null +++ b/activities/Clock.activity/css/activity.css @@ -0,0 +1,47 @@ +#canvas { + margin: 0; + text-align: center; +} + +#clock-container { + position: relative; + margin: 0 auto; +} + +canvas { + position: absolute; + left: 0; + top: 0; +} + +#text-time { + font-family: monospace; + font-size: 35px; + font-weight: bold; + margin: 0; +} + +#text-date { + font-size: 25px; + margin: 0; +} + +#main-toolbar #activity-button { + background-image: url(../activity/activity-clock.svg); +} + +#main-toolbar #simple-clock-button { + background-image: url(../icons/simple-clock.svg); +} + +#main-toolbar #nice-clock-button { + background-image: url(../icons/nice-clock.svg); +} + +#main-toolbar #write-time-button { + background-image: url(../icons/write-time.svg); +} + +#main-toolbar #write-date-button { + background-image: url(../icons/write-date.svg); +} diff --git a/activities/Clock.activity/icons/nice-clock.svg b/activities/Clock.activity/icons/nice-clock.svg new file mode 100644 index 000000000..d18e4afd4 --- /dev/null +++ b/activities/Clock.activity/icons/nice-clock.svg @@ -0,0 +1,14 @@ + + +]> + + + + + + + + + diff --git a/activities/Clock.activity/icons/simple-clock.svg b/activities/Clock.activity/icons/simple-clock.svg new file mode 100644 index 000000000..9387bbf74 --- /dev/null +++ b/activities/Clock.activity/icons/simple-clock.svg @@ -0,0 +1,15 @@ + + +]> + + + + + + + + diff --git a/activities/Clock.activity/icons/write-date.svg b/activities/Clock.activity/icons/write-date.svg new file mode 100644 index 000000000..8aee71c10 --- /dev/null +++ b/activities/Clock.activity/icons/write-date.svg @@ -0,0 +1,10 @@ + + +]> + + + + 26 + diff --git a/activities/Clock.activity/icons/write-time.svg b/activities/Clock.activity/icons/write-time.svg new file mode 100644 index 000000000..8aede8ce2 --- /dev/null +++ b/activities/Clock.activity/icons/write-time.svg @@ -0,0 +1,9 @@ + + +]> + + + T + diff --git a/activities/Clock.activity/index.html b/activities/Clock.activity/index.html new file mode 100644 index 000000000..9eb21d42a --- /dev/null +++ b/activities/Clock.activity/index.html @@ -0,0 +1,31 @@ + + + + +Clock Activity + + + + + + +
+ + + +
+ + + +
+
+
+ +
+
+ + +
+
+ + diff --git a/activities/Clock.activity/js/activity.js b/activities/Clock.activity/js/activity.js new file mode 100644 index 000000000..bcd07ccb9 --- /dev/null +++ b/activities/Clock.activity/js/activity.js @@ -0,0 +1,366 @@ +define(function (require) { + var activity = require("sugar-web/activity/activity"); + var radioButtonsGroup = require("sugar-web/graphics/radiobuttonsgroup"); + var mustache = require("mustache"); + var moment = require("moment"); + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + // Initialize the activity. + activity.setup(); + + var requestAnimationFrame = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + window.msRequestAnimationFrame; + + function Clock() { + this.face = "simple"; + + this.handAngles = { + 'hours': 0, + 'minutes': 0, + 'seconds': 0 + }; + + this.colors = { + 'black': "#000000", + 'white': "#FFFFFF", + 'hours': "#005FE4", + 'minutes': "#00B20D", + 'seconds': "#E6000A" + }; + + this.writeTime = false; + this.writeDate = false; + + // These are calculated on each resize to fill the available space. + this.size = undefined; + this.margin = undefined; + this.radius = undefined; + this.centerX = undefined; + this.centerY = undefined; + this.lineWidthBase = undefined; + this.handSizes = undefined; + this.lineWidths = undefined; + + // DOM elements. + this.textTimeElem = document.getElementById('text-time'); + this.textDateElem = document.getElementById('text-date'); + this.clockCanvasElem = document.getElementById("clock-canvas"); + this.clockContainerElem = this.clockCanvasElem.parentNode; + + this.bgCanvasElem = document.createElement('canvas'); + this.clockContainerElem.insertBefore(this.bgCanvasElem, + this.clockCanvasElem); + + var that = this; + window.onresize = function (event) { + that.updateSizes(); + that.drawBackground(); + }; + } + + Clock.prototype.start = function (face) { + this.updateSizes(); + this.drawBackground(); + this.update(); + this.drawHands(); + + this.previousTime = Date.now(); + this.tick(); + } + + Clock.prototype.tick = function () { + var currentTime = Date.now(); + + // Update each second (1000 miliseconds). + if ((currentTime - this.previousTime) > 1000) { + this.previousTime = currentTime; + this.update(); + this.drawHands(); + } + requestAnimationFrame(this.tick.bind(this)); + } + + Clock.prototype.changeFace = function (face) { + this.face = face; + this.drawBackground(); + } + + Clock.prototype.changeWriteTime = function (writeTime) { + this.writeTime = writeTime; + if (writeTime) { + this.textTimeElem.style.display = "block"; + } + else { + this.textTimeElem.style.display = "none"; + } + this.updateSizes(); + this.update(); + this.drawBackground(); + } + + Clock.prototype.changeWriteDate = function (writeDate) { + this.writeDate = writeDate; + if (writeDate) { + this.textDateElem.style.display = "block"; + } + else { + this.textDateElem.style.display = "none"; + } + this.updateSizes(); + this.update(); + this.drawBackground(); + } + + Clock.prototype.updateSizes = function () { + var toolbarElem = document.getElementById("main-toolbar"); + var textContainerElem = document.getElementById("text-container"); + + var height = window.innerHeight - (textContainerElem.offsetHeight + + toolbarElem.offsetHeight) - 1; + + this.size = Math.min(window.innerWidth, height); + + this.clockCanvasElem.width = this.size; + this.clockCanvasElem.height = this.size; + + this.bgCanvasElem.width = this.size; + this.bgCanvasElem.height = this.size; + + this.clockContainerElem.style.width = this.size + "px"; + this.clockContainerElem.style.height = this.size + "px"; + + this.margin = this.size * 0.02; + this.radius = (this.size - (2 * this.margin)) / 2; + + this.centerX = this.radius + this.margin; + this.centerY = this.radius + this.margin; + this.lineWidthBase = this.radius / 150; + + this.handSizes = { + 'hours': this.radius * 0.5, + 'minutes': this.radius * 0.7, + 'seconds': this.radius * 0.8 + }; + + this.lineWidths = { + 'hours': this.lineWidthBase * 9, + 'minutes': this.lineWidthBase * 6, + 'seconds': this.lineWidthBase * 4 + }; + } + + // Update text and hand angles using the current time. + Clock.prototype.update = function () { + var date = new Date(); + var hours = date.getHours(); + var minutes = date.getMinutes(); + var seconds = date.getSeconds(); + + var zeroFill = function (number) { + return ('00' + number).substr(-2); + }; + + var template = + '{{ hours }}' + + '' + + ':{{ minutes }}' + + '' + + ':{{ seconds }}' + + ''; + + if (this.writeTime) { + var templateData = {'colors': this.colors, + 'hours': zeroFill(hours), + 'minutes': zeroFill(minutes), + 'seconds': zeroFill(seconds) + } + + this.textTimeElem.innerHTML = mustache.render(template, + templateData); + } + + if (this.writeDate) { + this.textDateElem.innerHTML = moment(date).format("dddd, LL"); + } + + this.handAngles.hours = Math.PI - (Math.PI / 6 * hours + + Math.PI / 360 * minutes); + + this.handAngles.minutes = Math.PI - Math.PI / 30 * minutes; + this.handAngles.seconds = Math.PI - Math.PI / 30 * seconds; + } + + Clock.prototype.drawBackground = function () { + if (this.face == "simple") { + this.drawSimpleBackground(); + this.drawNumbers(); + } + else { + this.drawNiceBackground(); + } + this.drawHands(); + } + + // Draw the background of the simple clock. + // + // The simple clock background is a white disk, with hours and + // minutes ticks, and the hour numbers. + Clock.prototype.drawSimpleBackground = function () { + var ctx = this.bgCanvasElem.getContext('2d'); + + ctx.clearRect(0, 0, this.size, this.size); + + // Simple clock background + var lineWidthBackground = this.lineWidthBase * 4; + ctx.lineCap = 'round'; + ctx.lineWidth = lineWidthBackground; + ctx.strokeStyle = this.colors.black; + ctx.fillStyle = this.colors.white; + ctx.beginPath(); + ctx.arc(this.centerX, this.centerY, + this.radius - lineWidthBackground, 0, 2 * Math.PI); + ctx.fill(); + ctx.stroke(); + + // Clock ticks + for (var i = 0; i < 60; i++) { + var inset; + if (i % 15 === 0) { + inset = 0.15 * this.radius; + ctx.lineWidth = this.lineWidthBase * 7; + } + else if (i % 5 === 0) { + inset = 0.12 * this.radius; + ctx.lineWidth = this.lineWidthBase * 5; + } + else { + inset = 0.08 * this.radius; + ctx.lineWidth = this.lineWidthBase * 4; + } + + ctx.lineCap = 'round'; + ctx.beginPath(); + + var cos = Math.cos(i * Math.PI / 30); + var sin = Math.sin(i * Math.PI / 30); + ctx.save(); + ctx.translate(this.margin, this.margin); + ctx.moveTo( + this.radius + (this.radius - inset) * cos, + this.radius + (this.radius - inset) * sin); + ctx.lineTo( + this.radius + (this.radius - ctx.lineWidth) * cos, + this.radius + (this.radius - ctx.lineWidth) * sin); + + ctx.stroke(); + ctx.restore(); + } + } + + Clock.prototype.drawNiceBackground = function () { + var ctx = this.bgCanvasElem.getContext('2d'); + + var niceImageElem = document.createElement('img'); + var that = this; + var onLoad = function () { + ctx.clearRect(that.margin, that.margin, + that.radius * 2, that.radius * 2); + ctx.drawImage(niceImageElem, that.margin, that.margin, + that.radius * 2, that.radius * 2); + }; + niceImageElem.addEventListener('load', onLoad, false); + niceImageElem.src = "clock.svg"; + } + + // Draw the numbers of the hours. + Clock.prototype.drawNumbers = function () { + var ctx = this.bgCanvasElem.getContext('2d'); + + var fontSize = 30 * this.radius / 160; + + ctx.fillStyle = this.colors.hours; + ctx.textBaseline = 'middle'; + ctx.font = "bold " + fontSize + "px sans-serif"; + + for (var i = 0; i < 12; i++) { + var cos = Math.cos((i - 2) * Math.PI / 6); + var sin = Math.sin((i - 2) * Math.PI / 6); + var text = i + 1; + var textWidth = ctx.measureText(text).width; + + ctx.save(); + ctx.translate(this.centerX - textWidth / 2, this.centerY); + ctx.translate(this.radius * 0.75 * cos, + this.radius * 0.75 * sin); + ctx.fillText(text, 0, 0); + ctx.restore(); + } + } + + // Draw the hands of the analog clocks. + Clock.prototype.drawHands = function () { + var ctx = this.clockCanvasElem.getContext("2d"); + + // Clear canvas first. + ctx.clearRect(this.margin, this.margin, this.radius * 2, + this.radius * 2); + + var handNames = ['hours', 'minutes', 'seconds']; + for (var i = 0; i < handNames.length; i++) { + var name = handNames[i]; + ctx.lineCap = 'round'; + ctx.lineWidth = this.lineWidths[name]; + ctx.strokeStyle = this.colors[name]; + ctx.beginPath(); + ctx.arc(this.centerX, this.centerY, ctx.lineWidth * 0.6, 0, + 2 * Math.PI); + ctx.moveTo(this.centerX, this.centerY); + ctx.lineTo( + this.centerX + this.handSizes[name] * + Math.sin(this.handAngles[name]), + this.centerY + this.handSizes[name] * + Math.cos(this.handAngles[name])); + ctx.stroke(); + } + } + + // Create the clock. + + var clock = new Clock(); + clock.start(); + + // UI controls. + + var simpleClockButton = document.getElementById("simple-clock-button"); + simpleClockButton.onclick = function () { + clock.changeFace("simple"); + }; + + var niceClockButton = document.getElementById("nice-clock-button"); + niceClockButton.onclick = function () { + clock.changeFace("nice"); + }; + + var simpleNiceRadio = new radioButtonsGroup.RadioButtonsGroup( + [simpleClockButton, niceClockButton]); + + var writeTimeButton = document.getElementById("write-time-button"); + writeTimeButton.onclick = function () { + this.classList.toggle('active'); + var active = this.classList.contains('active'); + clock.changeWriteTime(active); + }; + + var writeDateButton = document.getElementById("write-date-button"); + writeDateButton.onclick = function () { + this.classList.toggle('active'); + var active = this.classList.contains('active'); + clock.changeWriteDate(active); + }; + + }); +}); diff --git a/activities/Clock.activity/js/loader.js b/activities/Clock.activity/js/loader.js new file mode 100644 index 000000000..01021e981 --- /dev/null +++ b/activities/Clock.activity/js/loader.js @@ -0,0 +1,8 @@ +requirejs.config({ + baseUrl: "lib", + paths: { + activity: "../js" + } +}); + +requirejs(["activity/activity"]); diff --git a/activities/Clock.activity/lib/domReady.js b/activities/Clock.activity/lib/domReady.js new file mode 100644 index 000000000..2b5412209 --- /dev/null +++ b/activities/Clock.activity/lib/domReady.js @@ -0,0 +1,129 @@ +/** + * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/domReady for details + */ +/*jslint */ +/*global require: false, define: false, requirejs: false, + window: false, clearInterval: false, document: false, + self: false, setInterval: false */ + + +define(function () { + 'use strict'; + + var isTop, testDiv, scrollIntervalId, + isBrowser = typeof window !== "undefined" && window.document, + isPageLoaded = !isBrowser, + doc = isBrowser ? document : null, + readyCalls = []; + + function runCallbacks(callbacks) { + var i; + for (i = 0; i < callbacks.length; i += 1) { + callbacks[i](doc); + } + } + + function callReady() { + var callbacks = readyCalls; + + if (isPageLoaded) { + //Call the DOM ready callbacks + if (callbacks.length) { + readyCalls = []; + runCallbacks(callbacks); + } + } + } + + /** + * Sets the page as loaded. + */ + function pageLoaded() { + if (!isPageLoaded) { + isPageLoaded = true; + if (scrollIntervalId) { + clearInterval(scrollIntervalId); + } + + callReady(); + } + } + + if (isBrowser) { + if (document.addEventListener) { + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. + document.addEventListener("DOMContentLoaded", pageLoaded, false); + window.addEventListener("load", pageLoaded, false); + } else if (window.attachEvent) { + window.attachEvent("onload", pageLoaded); + + testDiv = document.createElement('div'); + try { + isTop = window.frameElement === null; + } catch (e) {} + + //DOMContentLoaded approximation that uses a doScroll, as found by + //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, + //but modified by other contributors, including jdalton + if (testDiv.doScroll && isTop && window.external) { + scrollIntervalId = setInterval(function () { + try { + testDiv.doScroll(); + pageLoaded(); + } catch (e) {} + }, 30); + } + } + + //Check if document already complete, and if so, just trigger page load + //listeners. Latest webkit browsers also use "interactive", and + //will fire the onDOMContentLoaded before "interactive" but not after + //entering "interactive" or "complete". More details: + //http://dev.w3.org/html5/spec/the-end.html#the-end + //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded + //Hmm, this is more complicated on further use, see "firing too early" + //bug: https://github.com/requirejs/domReady/issues/1 + //so removing the || document.readyState === "interactive" test. + //There is still a window.onload binding that should get fired if + //DOMContentLoaded is missed. + if (document.readyState === "complete") { + pageLoaded(); + } + } + + /** START OF PUBLIC API **/ + + /** + * Registers a callback for DOM ready. If DOM is already ready, the + * callback is called immediately. + * @param {Function} callback + */ + function domReady(callback) { + if (isPageLoaded) { + callback(doc); + } else { + readyCalls.push(callback); + } + return domReady; + } + + domReady.version = '2.0.1'; + + /** + * Loader Plugin API method + */ + domReady.load = function (name, req, onLoad, config) { + if (config.isBuild) { + onLoad(null); + } else { + domReady(onLoad); + } + }; + + /** END OF PUBLIC API **/ + + return domReady; +}); diff --git a/activities/Clock.activity/lib/moment.js b/activities/Clock.activity/lib/moment.js new file mode 100644 index 000000000..c8a870e8c --- /dev/null +++ b/activities/Clock.activity/lib/moment.js @@ -0,0 +1,1662 @@ +// moment.js +// version : 2.1.0 +// author : Tim Wood +// license : MIT +// momentjs.com + +(function (undefined) { + + /************************************ + Constants + ************************************/ + + var moment, + VERSION = "2.1.0", + round = Math.round, i, + // internal storage for language config files + languages = {}, + + // check for nodeJS + hasModule = (typeof module !== 'undefined' && module.exports), + + // ASP.NET json date format regex + aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, + aspNetTimeSpanJsonRegex = /(\-)?(\d*)?\.?(\d+)\:(\d+)\:(\d+)\.?(\d{3})?/, + + // format tokens + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g, + localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, + + // parsing token regexes + parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 + parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 + parseTokenThreeDigits = /\d{3}/, // 000 - 999 + parseTokenFourDigits = /\d{1,4}/, // 0 - 9999 + parseTokenSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 + parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. + parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z + parseTokenT = /T/i, // T (ISO seperator) + parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 + + // preliminary iso regex + // 0000-00-00 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 + isoRegex = /^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/, + isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', + + // iso time formats and regexes + isoTimes = [ + ['HH:mm:ss.S', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/], + ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], + ['HH:mm', /(T| )\d\d:\d\d/], + ['HH', /(T| )\d\d/] + ], + + // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] + parseTimezoneChunker = /([\+\-]|\d\d)/gi, + + // getter and setter names + proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), + unitMillisecondFactors = { + 'Milliseconds' : 1, + 'Seconds' : 1e3, + 'Minutes' : 6e4, + 'Hours' : 36e5, + 'Days' : 864e5, + 'Months' : 2592e6, + 'Years' : 31536e6 + }, + + unitAliases = { + ms : 'millisecond', + s : 'second', + m : 'minute', + h : 'hour', + d : 'day', + w : 'week', + M : 'month', + y : 'year' + }, + + // format function strings + formatFunctions = {}, + + // tokens to ordinalize and pad + ordinalizeTokens = 'DDD w W M D d'.split(' '), + paddedTokens = 'M D H h m s w W'.split(' '), + + formatTokenFunctions = { + M : function () { + return this.month() + 1; + }, + MMM : function (format) { + return this.lang().monthsShort(this, format); + }, + MMMM : function (format) { + return this.lang().months(this, format); + }, + D : function () { + return this.date(); + }, + DDD : function () { + return this.dayOfYear(); + }, + d : function () { + return this.day(); + }, + dd : function (format) { + return this.lang().weekdaysMin(this, format); + }, + ddd : function (format) { + return this.lang().weekdaysShort(this, format); + }, + dddd : function (format) { + return this.lang().weekdays(this, format); + }, + w : function () { + return this.week(); + }, + W : function () { + return this.isoWeek(); + }, + YY : function () { + return leftZeroFill(this.year() % 100, 2); + }, + YYYY : function () { + return leftZeroFill(this.year(), 4); + }, + YYYYY : function () { + return leftZeroFill(this.year(), 5); + }, + gg : function () { + return leftZeroFill(this.weekYear() % 100, 2); + }, + gggg : function () { + return this.weekYear(); + }, + ggggg : function () { + return leftZeroFill(this.weekYear(), 5); + }, + GG : function () { + return leftZeroFill(this.isoWeekYear() % 100, 2); + }, + GGGG : function () { + return this.isoWeekYear(); + }, + GGGGG : function () { + return leftZeroFill(this.isoWeekYear(), 5); + }, + e : function () { + return this.weekday(); + }, + E : function () { + return this.isoWeekday(); + }, + a : function () { + return this.lang().meridiem(this.hours(), this.minutes(), true); + }, + A : function () { + return this.lang().meridiem(this.hours(), this.minutes(), false); + }, + H : function () { + return this.hours(); + }, + h : function () { + return this.hours() % 12 || 12; + }, + m : function () { + return this.minutes(); + }, + s : function () { + return this.seconds(); + }, + S : function () { + return ~~(this.milliseconds() / 100); + }, + SS : function () { + return leftZeroFill(~~(this.milliseconds() / 10), 2); + }, + SSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + Z : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(~~(a / 60), 2) + ":" + leftZeroFill(~~a % 60, 2); + }, + ZZ : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(~~(10 * a / 6), 4); + }, + z : function () { + return this.zoneAbbr(); + }, + zz : function () { + return this.zoneName(); + }, + X : function () { + return this.unix(); + } + }; + + function padToken(func, count) { + return function (a) { + return leftZeroFill(func.call(this, a), count); + }; + } + function ordinalizeToken(func, period) { + return function (a) { + return this.lang().ordinal(func.call(this, a), period); + }; + } + + while (ordinalizeTokens.length) { + i = ordinalizeTokens.pop(); + formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); + } + while (paddedTokens.length) { + i = paddedTokens.pop(); + formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); + } + formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); + + + /************************************ + Constructors + ************************************/ + + function Language() { + + } + + // Moment prototype object + function Moment(config) { + extend(this, config); + } + + // Duration Constructor + function Duration(duration) { + var years = duration.years || duration.year || duration.y || 0, + months = duration.months || duration.month || duration.M || 0, + weeks = duration.weeks || duration.week || duration.w || 0, + days = duration.days || duration.day || duration.d || 0, + hours = duration.hours || duration.hour || duration.h || 0, + minutes = duration.minutes || duration.minute || duration.m || 0, + seconds = duration.seconds || duration.second || duration.s || 0, + milliseconds = duration.milliseconds || duration.millisecond || duration.ms || 0; + + // store reference to input for deterministic cloning + this._input = duration; + + // representation for dateAddRemove + this._milliseconds = milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 36e5; // 1000 * 60 * 60 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = months + + years * 12; + + this._data = {}; + + this._bubble(); + } + + + /************************************ + Helpers + ************************************/ + + + function extend(a, b) { + for (var i in b) { + if (b.hasOwnProperty(i)) { + a[i] = b[i]; + } + } + return a; + } + + function absRound(number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } + } + + // left zero fill a number + // see http://jsperf.com/left-zero-filling for performance comparison + function leftZeroFill(number, targetLength) { + var output = number + ''; + while (output.length < targetLength) { + output = '0' + output; + } + return output; + } + + // helper function for _.addTime and _.subtractTime + function addOrSubtractDurationFromMoment(mom, duration, isAdding, ignoreUpdateOffset) { + var milliseconds = duration._milliseconds, + days = duration._days, + months = duration._months, + minutes, + hours, + currentDate; + + if (milliseconds) { + mom._d.setTime(+mom._d + milliseconds * isAdding); + } + // store the minutes and hours so we can restore them + if (days || months) { + minutes = mom.minute(); + hours = mom.hour(); + } + if (days) { + mom.date(mom.date() + days * isAdding); + } + if (months) { + mom.month(mom.month() + months * isAdding); + } + if (milliseconds && !ignoreUpdateOffset) { + moment.updateOffset(mom); + } + // restore the minutes and hours after possibly changing dst + if (days || months) { + mom.minute(minutes); + mom.hour(hours); + } + } + + // check if is an array + function isArray(input) { + return Object.prototype.toString.call(input) === '[object Array]'; + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if (~~array1[i] !== ~~array2[i]) { + diffs++; + } + } + return diffs + lengthDiff; + } + + function normalizeUnits(units) { + return units ? unitAliases[units] || units.toLowerCase().replace(/(.)s$/, '$1') : units; + } + + + /************************************ + Languages + ************************************/ + + + Language.prototype = { + set : function (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (typeof prop === 'function') { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + }, + + _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), + months : function (m) { + return this._months[m.month()]; + }, + + _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), + monthsShort : function (m) { + return this._monthsShort[m.month()]; + }, + + monthsParse : function (monthName) { + var i, mom, regex; + + if (!this._monthsParse) { + this._monthsParse = []; + } + + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + if (!this._monthsParse[i]) { + mom = moment([2000, i]); + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._monthsParse[i].test(monthName)) { + return i; + } + } + }, + + _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), + weekdays : function (m) { + return this._weekdays[m.day()]; + }, + + _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysShort : function (m) { + return this._weekdaysShort[m.day()]; + }, + + _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), + weekdaysMin : function (m) { + return this._weekdaysMin[m.day()]; + }, + + weekdaysParse : function (weekdayName) { + var i, mom, regex; + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + if (!this._weekdaysParse[i]) { + mom = moment([2000, 1]).day(i); + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + }, + + _longDateFormat : { + LT : "h:mm A", + L : "MM/DD/YYYY", + LL : "MMMM D YYYY", + LLL : "MMMM D YYYY LT", + LLLL : "dddd, MMMM D YYYY LT" + }, + longDateFormat : function (key) { + var output = this._longDateFormat[key]; + if (!output && this._longDateFormat[key.toUpperCase()]) { + output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + this._longDateFormat[key] = output; + } + return output; + }, + + isPM : function (input) { + return ((input + '').toLowerCase()[0] === 'p'); + }, + + _meridiemParse : /[ap]\.?m?\.?/i, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + }, + + _calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + calendar : function (key, mom) { + var output = this._calendar[key]; + return typeof output === 'function' ? output.apply(mom) : output; + }, + + _relativeTime : { + future : "in %s", + past : "%s ago", + s : "a few seconds", + m : "a minute", + mm : "%d minutes", + h : "an hour", + hh : "%d hours", + d : "a day", + dd : "%d days", + M : "a month", + MM : "%d months", + y : "a year", + yy : "%d years" + }, + relativeTime : function (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (typeof output === 'function') ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + }, + pastFuture : function (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); + }, + + ordinal : function (number) { + return this._ordinal.replace("%d", number); + }, + _ordinal : "%d", + + preparse : function (string) { + return string; + }, + + postformat : function (string) { + return string; + }, + + week : function (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + }, + _week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } + }; + + // Loads a language definition into the `languages` cache. The function + // takes a key and optionally values. If not in the browser and no values + // are provided, it will load the language file module. As a convenience, + // this function also returns the language values. + function loadLang(key, values) { + values.abbr = key; + if (!languages[key]) { + languages[key] = new Language(); + } + languages[key].set(values); + return languages[key]; + } + + // Determines which language definition to use and returns it. + // + // With no parameters, it will return the global language. If you + // pass in a language key, such as 'en', it will return the + // definition for 'en', so long as 'en' has already been loaded using + // moment.lang. + function getLangDefinition(key) { + if (!key) { + return moment.fn._lang; + } + if (!languages[key] && hasModule) { + try { + require('./lang/' + key); + } catch (e) { + // call with no params to set to default + return moment.fn._lang; + } + } + return languages[key]; + } + + + /************************************ + Formatting + ************************************/ + + + function removeFormattingTokens(input) { + if (input.match(/\[.*\]/)) { + return input.replace(/^\[|\]$/g, ""); + } + return input.replace(/\\/g, ""); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = ""; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return m.lang().longDateFormat(input) || input; + } + + while (i-- && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + } + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + + return formatFunctions[format](m); + } + + + /************************************ + Parsing + ************************************/ + + + // get the regex to find the next token + function getParseRegexForToken(token, config) { + switch (token) { + case 'DDDD': + return parseTokenThreeDigits; + case 'YYYY': + return parseTokenFourDigits; + case 'YYYYY': + return parseTokenSixDigits; + case 'S': + case 'SS': + case 'SSS': + case 'DDD': + return parseTokenOneToThreeDigits; + case 'MMM': + case 'MMMM': + case 'dd': + case 'ddd': + case 'dddd': + return parseTokenWord; + case 'a': + case 'A': + return getLangDefinition(config._l)._meridiemParse; + case 'X': + return parseTokenTimestampMs; + case 'Z': + case 'ZZ': + return parseTokenTimezone; + case 'T': + return parseTokenT; + case 'MM': + case 'DD': + case 'YY': + case 'HH': + case 'hh': + case 'mm': + case 'ss': + case 'M': + case 'D': + case 'd': + case 'H': + case 'h': + case 'm': + case 's': + return parseTokenOneOrTwoDigits; + default : + return new RegExp(token.replace('\\', '')); + } + } + + function timezoneMinutesFromString(string) { + var tzchunk = (parseTokenTimezone.exec(string) || [])[0], + parts = (tzchunk + '').match(parseTimezoneChunker) || ['-', 0, 0], + minutes = +(parts[1] * 60) + ~~parts[2]; + + return parts[0] === '+' ? -minutes : minutes; + } + + // function to convert string input to date + function addTimeToArrayFromToken(token, input, config) { + var a, datePartArray = config._a; + + switch (token) { + // MONTH + case 'M' : // fall through to MM + case 'MM' : + datePartArray[1] = (input == null) ? 0 : ~~input - 1; + break; + case 'MMM' : // fall through to MMMM + case 'MMMM' : + a = getLangDefinition(config._l).monthsParse(input); + // if we didn't find a month name, mark the date as invalid. + if (a != null) { + datePartArray[1] = a; + } else { + config._isValid = false; + } + break; + // DAY OF MONTH + case 'D' : // fall through to DDDD + case 'DD' : // fall through to DDDD + case 'DDD' : // fall through to DDDD + case 'DDDD' : + if (input != null) { + datePartArray[2] = ~~input; + } + break; + // YEAR + case 'YY' : + datePartArray[0] = ~~input + (~~input > 68 ? 1900 : 2000); + break; + case 'YYYY' : + case 'YYYYY' : + datePartArray[0] = ~~input; + break; + // AM / PM + case 'a' : // fall through to A + case 'A' : + config._isPm = getLangDefinition(config._l).isPM(input); + break; + // 24 HOUR + case 'H' : // fall through to hh + case 'HH' : // fall through to hh + case 'h' : // fall through to hh + case 'hh' : + datePartArray[3] = ~~input; + break; + // MINUTE + case 'm' : // fall through to mm + case 'mm' : + datePartArray[4] = ~~input; + break; + // SECOND + case 's' : // fall through to ss + case 'ss' : + datePartArray[5] = ~~input; + break; + // MILLISECOND + case 'S' : + case 'SS' : + case 'SSS' : + datePartArray[6] = ~~ (('0.' + input) * 1000); + break; + // UNIX TIMESTAMP WITH MS + case 'X': + config._d = new Date(parseFloat(input) * 1000); + break; + // TIMEZONE + case 'Z' : // fall through to ZZ + case 'ZZ' : + config._useUTC = true; + config._tzm = timezoneMinutesFromString(input); + break; + } + + // if the input is null, the date is not valid + if (input == null) { + config._isValid = false; + } + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function dateFromArray(config) { + var i, date, input = []; + + if (config._d) { + return; + } + + for (i = 0; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // add the offsets to the time to be parsed so that we can have a clean array for checking isValid + input[3] += ~~((config._tzm || 0) / 60); + input[4] += ~~((config._tzm || 0) % 60); + + date = new Date(0); + + if (config._useUTC) { + date.setUTCFullYear(input[0], input[1], input[2]); + date.setUTCHours(input[3], input[4], input[5], input[6]); + } else { + date.setFullYear(input[0], input[1], input[2]); + date.setHours(input[3], input[4], input[5], input[6]); + } + + config._d = date; + } + + // date from string and format string + function makeDateFromStringAndFormat(config) { + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var tokens = config._f.match(formattingTokens), + string = config._i, + i, parsedInput; + + config._a = []; + + for (i = 0; i < tokens.length; i++) { + parsedInput = (getParseRegexForToken(tokens[i], config).exec(string) || [])[0]; + if (parsedInput) { + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + } + // don't parse if its not a known token + if (formatTokenFunctions[tokens[i]]) { + addTimeToArrayFromToken(tokens[i], parsedInput, config); + } + } + + // add remaining unparsed input to the string + if (string) { + config._il = string; + } + + // handle am pm + if (config._isPm && config._a[3] < 12) { + config._a[3] += 12; + } + // if is 12 am, change hours to 0 + if (config._isPm === false && config._a[3] === 12) { + config._a[3] = 0; + } + // return + dateFromArray(config); + } + + // date from string and array of format strings + function makeDateFromStringAndArray(config) { + var tempConfig, + tempMoment, + bestMoment, + + scoreToBeat = 99, + i, + currentScore; + + for (i = 0; i < config._f.length; i++) { + tempConfig = extend({}, config); + tempConfig._f = config._f[i]; + makeDateFromStringAndFormat(tempConfig); + tempMoment = new Moment(tempConfig); + + currentScore = compareArrays(tempConfig._a, tempMoment.toArray()); + + // if there is any input that was not parsed + // add a penalty for that format + if (tempMoment._il) { + currentScore += tempMoment._il.length; + } + + if (currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempMoment; + } + } + + extend(config, bestMoment); + } + + // date from iso format + function makeDateFromString(config) { + var i, + string = config._i, + match = isoRegex.exec(string); + + if (match) { + // match[2] should be "T" or undefined + config._f = 'YYYY-MM-DD' + (match[2] || " "); + for (i = 0; i < 4; i++) { + if (isoTimes[i][1].exec(string)) { + config._f += isoTimes[i][0]; + break; + } + } + if (parseTokenTimezone.exec(string)) { + config._f += " Z"; + } + makeDateFromStringAndFormat(config); + } else { + config._d = new Date(string); + } + } + + function makeDateFromInput(config) { + var input = config._i, + matched = aspNetJsonRegex.exec(input); + + if (input === undefined) { + config._d = new Date(); + } else if (matched) { + config._d = new Date(+matched[1]); + } else if (typeof input === 'string') { + makeDateFromString(config); + } else if (isArray(input)) { + config._a = input.slice(0); + dateFromArray(config); + } else { + config._d = input instanceof Date ? new Date(+input) : new Date(input); + } + } + + + /************************************ + Relative Time + ************************************/ + + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { + return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime(milliseconds, withoutSuffix, lang) { + var seconds = round(Math.abs(milliseconds) / 1000), + minutes = round(seconds / 60), + hours = round(minutes / 60), + days = round(hours / 24), + years = round(days / 365), + args = seconds < 45 && ['s', seconds] || + minutes === 1 && ['m'] || + minutes < 45 && ['mm', minutes] || + hours === 1 && ['h'] || + hours < 22 && ['hh', hours] || + days === 1 && ['d'] || + days <= 25 && ['dd', days] || + days <= 45 && ['M'] || + days < 345 && ['MM', round(days / 30)] || + years === 1 && ['y'] || ['yy', years]; + args[2] = withoutSuffix; + args[3] = milliseconds > 0; + args[4] = lang; + return substituteTimeAgo.apply({}, args); + } + + + /************************************ + Week of Year + ************************************/ + + + // firstDayOfWeek 0 = sun, 6 = sat + // the day of the week that starts the week + // (usually sunday or monday) + // firstDayOfWeekOfYear 0 = sun, 6 = sat + // the first week is the week that contains the first + // of this day of the week + // (eg. ISO weeks use thursday (4)) + function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { + var end = firstDayOfWeekOfYear - firstDayOfWeek, + daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), + adjustedMoment; + + + if (daysToDayOfWeek > end) { + daysToDayOfWeek -= 7; + } + + if (daysToDayOfWeek < end - 7) { + daysToDayOfWeek += 7; + } + + adjustedMoment = moment(mom).add('d', daysToDayOfWeek); + return { + week: Math.ceil(adjustedMoment.dayOfYear() / 7), + year: adjustedMoment.year() + }; + } + + + /************************************ + Top Level Functions + ************************************/ + + function makeMoment(config) { + var input = config._i, + format = config._f; + + if (input === null || input === '') { + return null; + } + + if (typeof input === 'string') { + config._i = input = getLangDefinition().preparse(input); + } + + if (moment.isMoment(input)) { + config = extend({}, input); + config._d = new Date(+input._d); + } else if (format) { + if (isArray(format)) { + makeDateFromStringAndArray(config); + } else { + makeDateFromStringAndFormat(config); + } + } else { + makeDateFromInput(config); + } + + return new Moment(config); + } + + moment = function (input, format, lang) { + return makeMoment({ + _i : input, + _f : format, + _l : lang, + _isUTC : false + }); + }; + + // creating with utc + moment.utc = function (input, format, lang) { + return makeMoment({ + _useUTC : true, + _isUTC : true, + _l : lang, + _i : input, + _f : format + }); + }; + + // creating with unix timestamp (in seconds) + moment.unix = function (input) { + return moment(input * 1000); + }; + + // duration + moment.duration = function (input, key) { + var isDuration = moment.isDuration(input), + isNumber = (typeof input === 'number'), + duration = (isDuration ? input._input : (isNumber ? {} : input)), + matched = aspNetTimeSpanJsonRegex.exec(input), + sign, + ret; + + if (isNumber) { + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (matched) { + sign = (matched[1] === "-") ? -1 : 1; + duration = { + y: 0, + d: ~~matched[2] * sign, + h: ~~matched[3] * sign, + m: ~~matched[4] * sign, + s: ~~matched[5] * sign, + ms: ~~matched[6] * sign + }; + } + + ret = new Duration(duration); + + if (isDuration && input.hasOwnProperty('_lang')) { + ret._lang = input._lang; + } + + return ret; + }; + + // version number + moment.version = VERSION; + + // default format + moment.defaultFormat = isoFormat; + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + moment.updateOffset = function () {}; + + // This function will load languages and then set the global language. If + // no arguments are passed in, it will simply return the current global + // language key. + moment.lang = function (key, values) { + if (!key) { + return moment.fn._lang._abbr; + } + if (values) { + loadLang(key, values); + } else if (!languages[key]) { + getLangDefinition(key); + } + moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); + }; + + // returns language data + moment.langData = function (key) { + if (key && key._lang && key._lang._abbr) { + key = key._lang._abbr; + } + return getLangDefinition(key); + }; + + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment; + }; + + // for typechecking Duration objects + moment.isDuration = function (obj) { + return obj instanceof Duration; + }; + + + /************************************ + Moment Prototype + ************************************/ + + + moment.fn = Moment.prototype = { + + clone : function () { + return moment(this); + }, + + valueOf : function () { + return +this._d + ((this._offset || 0) * 60000); + }, + + unix : function () { + return Math.floor(+this / 1000); + }, + + toString : function () { + return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); + }, + + toDate : function () { + return this._offset ? new Date(+this) : this._d; + }, + + toISOString : function () { + return formatMoment(moment(this).utc(), 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + }, + + toArray : function () { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hours(), + m.minutes(), + m.seconds(), + m.milliseconds() + ]; + }, + + isValid : function () { + if (this._isValid == null) { + if (this._a) { + this._isValid = !compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()); + } else { + this._isValid = !isNaN(this._d.getTime()); + } + } + return !!this._isValid; + }, + + utc : function () { + return this.zone(0); + }, + + local : function () { + this.zone(0); + this._isUTC = false; + return this; + }, + + format : function (inputString) { + var output = formatMoment(this, inputString || moment.defaultFormat); + return this.lang().postformat(output); + }, + + add : function (input, val) { + var dur; + // switch args to support add('s', 1) and add(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, 1); + return this; + }, + + subtract : function (input, val) { + var dur; + // switch args to support subtract('s', 1) and subtract(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, -1); + return this; + }, + + diff : function (input, units, asFloat) { + var that = this._isUTC ? moment(input).zone(this._offset || 0) : moment(input).local(), + zoneDiff = (this.zone() - that.zone()) * 6e4, + diff, output; + + units = normalizeUnits(units); + + if (units === 'year' || units === 'month') { + // average number of days in the months in the given dates + diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 + // difference in months + output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); + // adjust by taking difference in days, average number of days + // and dst in the given months. + output += ((this - moment(this).startOf('month')) - + (that - moment(that).startOf('month'))) / diff; + // same as above but with zones, to negate all dst + output -= ((this.zone() - moment(this).startOf('month').zone()) - + (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff; + if (units === 'year') { + output = output / 12; + } + } else { + diff = (this - that); + output = units === 'second' ? diff / 1e3 : // 1000 + units === 'minute' ? diff / 6e4 : // 1000 * 60 + units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + diff; + } + return asFloat ? output : absRound(output); + }, + + from : function (time, withoutSuffix) { + return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); + }, + + fromNow : function (withoutSuffix) { + return this.from(moment(), withoutSuffix); + }, + + calendar : function () { + var diff = this.diff(moment().startOf('day'), 'days', true), + format = diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + return this.format(this.lang().calendar(format, this)); + }, + + isLeapYear : function () { + var year = this.year(); + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + }, + + isDST : function () { + return (this.zone() < this.clone().month(0).zone() || + this.zone() < this.clone().month(5).zone()); + }, + + day : function (input) { + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + if (typeof input === 'string') { + input = this.lang().weekdaysParse(input); + if (typeof input !== 'number') { + return this; + } + } + return this.add({ d : input - day }); + } else { + return day; + } + }, + + month : function (input) { + var utc = this._isUTC ? 'UTC' : '', + dayOfMonth, + daysInMonth; + + if (input != null) { + if (typeof input === 'string') { + input = this.lang().monthsParse(input); + if (typeof input !== 'number') { + return this; + } + } + + dayOfMonth = this.date(); + this.date(1); + this._d['set' + utc + 'Month'](input); + this.date(Math.min(dayOfMonth, this.daysInMonth())); + + moment.updateOffset(this); + return this; + } else { + return this._d['get' + utc + 'Month'](); + } + }, + + startOf: function (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + /* falls through */ + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } + + return this; + }, + + endOf: function (units) { + return this.startOf(units).add(units, 1).subtract('ms', 1); + }, + + isAfter: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) > +moment(input).startOf(units); + }, + + isBefore: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) < +moment(input).startOf(units); + }, + + isSame: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) === +moment(input).startOf(units); + }, + + min: function (other) { + other = moment.apply(null, arguments); + return other < this ? this : other; + }, + + max: function (other) { + other = moment.apply(null, arguments); + return other > this ? this : other; + }, + + zone : function (input) { + var offset = this._offset || 0; + if (input != null) { + if (typeof input === "string") { + input = timezoneMinutesFromString(input); + } + if (Math.abs(input) < 16) { + input = input * 60; + } + this._offset = input; + this._isUTC = true; + if (offset !== input) { + addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, true); + } + } else { + return this._isUTC ? offset : this._d.getTimezoneOffset(); + } + return this; + }, + + zoneAbbr : function () { + return this._isUTC ? "UTC" : ""; + }, + + zoneName : function () { + return this._isUTC ? "Coordinated Universal Time" : ""; + }, + + daysInMonth : function () { + return moment.utc([this.year(), this.month() + 1, 0]).date(); + }, + + dayOfYear : function (input) { + var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); + }, + + weekYear : function (input) { + var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year; + return input == null ? year : this.add("y", (input - year)); + }, + + isoWeekYear : function (input) { + var year = weekOfYear(this, 1, 4).year; + return input == null ? year : this.add("y", (input - year)); + }, + + week : function (input) { + var week = this.lang().week(this); + return input == null ? week : this.add("d", (input - week) * 7); + }, + + isoWeek : function (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add("d", (input - week) * 7); + }, + + weekday : function (input) { + var weekday = (this._d.getDay() + 7 - this.lang()._week.dow) % 7; + return input == null ? weekday : this.add("d", input - weekday); + }, + + isoWeekday : function (input) { + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); + }, + + // If passed a language key, it will set the language for this + // instance. Otherwise, it will return the language configuration + // variables for this instance. + lang : function (key) { + if (key === undefined) { + return this._lang; + } else { + this._lang = getLangDefinition(key); + return this; + } + } + }; + + // helper for adding shortcuts + function makeGetterAndSetter(name, key) { + moment.fn[name] = moment.fn[name + 's'] = function (input) { + var utc = this._isUTC ? 'UTC' : ''; + if (input != null) { + this._d['set' + utc + key](input); + moment.updateOffset(this); + return this; + } else { + return this._d['get' + utc + key](); + } + }; + } + + // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds) + for (i = 0; i < proxyGettersAndSetters.length; i ++) { + makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]); + } + + // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') + makeGetterAndSetter('year', 'FullYear'); + + // add plural methods + moment.fn.days = moment.fn.day; + moment.fn.months = moment.fn.month; + moment.fn.weeks = moment.fn.week; + moment.fn.isoWeeks = moment.fn.isoWeek; + + // add aliased format methods + moment.fn.toJSON = moment.fn.toISOString; + + /************************************ + Duration Prototype + ************************************/ + + + moment.duration.fn = Duration.prototype = { + _bubble : function () { + var milliseconds = this._milliseconds, + days = this._days, + months = this._months, + data = this._data, + seconds, minutes, hours, years; + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absRound(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absRound(seconds / 60); + data.minutes = minutes % 60; + + hours = absRound(minutes / 60); + data.hours = hours % 24; + + days += absRound(hours / 24); + data.days = days % 30; + + months += absRound(days / 30); + data.months = months % 12; + + years = absRound(months / 12); + data.years = years; + }, + + weeks : function () { + return absRound(this.days() / 7); + }, + + valueOf : function () { + return this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + ~~(this._months / 12) * 31536e6; + }, + + humanize : function (withSuffix) { + var difference = +this, + output = relativeTime(difference, !withSuffix, this.lang()); + + if (withSuffix) { + output = this.lang().pastFuture(difference, output); + } + + return this.lang().postformat(output); + }, + + add : function (input, val) { + // supports only 2.0-style add(1, 's') or add(moment) + var dur = moment.duration(input, val); + + this._milliseconds += dur._milliseconds; + this._days += dur._days; + this._months += dur._months; + + this._bubble(); + + return this; + }, + + subtract : function (input, val) { + var dur = moment.duration(input, val); + + this._milliseconds -= dur._milliseconds; + this._days -= dur._days; + this._months -= dur._months; + + this._bubble(); + + return this; + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units.toLowerCase() + 's'](); + }, + + as : function (units) { + units = normalizeUnits(units); + return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's'](); + }, + + lang : moment.fn.lang + }; + + function makeDurationGetter(name) { + moment.duration.fn[name] = function () { + return this._data[name]; + }; + } + + function makeDurationAsGetter(name, factor) { + moment.duration.fn['as' + name] = function () { + return +this / factor; + }; + } + + for (i in unitMillisecondFactors) { + if (unitMillisecondFactors.hasOwnProperty(i)) { + makeDurationAsGetter(i, unitMillisecondFactors[i]); + makeDurationGetter(i.toLowerCase()); + } + } + + makeDurationAsGetter('Weeks', 6048e5); + moment.duration.fn.asMonths = function () { + return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12; + }; + + + /************************************ + Default Lang + ************************************/ + + + // Set default language, other languages will inherit from English. + moment.lang('en', { + ordinal : function (number) { + var b = number % 10, + output = (~~ (number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + + /************************************ + Exposing Moment + ************************************/ + + + // CommonJS module is defined + if (hasModule) { + module.exports = moment; + } + /*global ender:false */ + if (typeof ender === 'undefined') { + // here, `this` means `window` in the browser, or `global` on the server + // add `moment` as a global object via a string identifier, + // for Closure Compiler "advanced" mode + this['moment'] = moment; + } + /*global define:false */ + if (typeof define === "function" && define.amd) { + define("moment", [], function () { + return moment; + }); + } +}).call(this); diff --git a/activities/Clock.activity/lib/mustache.js b/activities/Clock.activity/lib/mustache.js new file mode 100644 index 000000000..932052b4a --- /dev/null +++ b/activities/Clock.activity/lib/mustache.js @@ -0,0 +1,610 @@ +/*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + */ + +/*global define: false*/ + +(function (root, factory) { + if (typeof exports === "object" && exports) { + module.exports = factory; // CommonJS + } else if (typeof define === "function" && define.amd) { + define(factory); // AMD + } else { + root.Mustache = factory; // + + + +
+ + + +
+ +
+ + + +
+ + +
+ + + + diff --git a/activities/ConnecttheDots.activity/js/activity-works.js b/activities/ConnecttheDots.activity/js/activity-works.js new file mode 100644 index 000000000..3010e44fb --- /dev/null +++ b/activities/ConnecttheDots.activity/js/activity-works.js @@ -0,0 +1,271 @@ +define(function (require) { + var activity = require("sugar-web/activity/activity"); + var icon = require("sugar-web/graphics/icon"); + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + // Initialize the activity. + activity.setup(); + + // Colorize the activity icon. + var activityButton = document.getElementById("activity-button"); + activity.getXOColor(function (error, colors) { + icon.colorize(activityButton, colors); + }); + + var newButton = document.getElementById("new-button"); + newButton.onclick = function () { + new_positions(); + } + + // Make the activity stop with the stop button. + var stopButton = document.getElementById("stop-button"); + stopButton.addEventListener('click', function (e) { + activity.close(); + }); + + var canvas, stage; + + // the display object currently under the mouse, or being dragged + var mouseTarget; + // indicates whether we are currently in a drag operation + var dragStarted; + var offset; + var update = true; + var drawingCanvas; + var oldPt; + var midPt; + var oldMidPt; + var color; + var stroke; + var colors; + var index; + var imagepos = new Array(); + var myimages = new Array(); + var bitmaps = new Array(); + var pen_bitmap; + + var star = [[400, 100], [200, 500], [650, 300], [200, 100], [400, 550], + [600, 100], [150, 300], [600, 500], [400, 50]]; + var hand = [[175, 150], [200, 125], [280, 220], [330, 200], [425, 25], + [440, 175], [540, 50], [480, 210], [620, 100], [520, 280], + [680, 270], [520, 330], [420, 420], [400, 450], [280, 450], + [260, 340], [200, 200]]; + var shapes = [star, hand]; + var shape = 0 + + function init() { + if (window.top != window) { + document.getElementById("header").style.display = "none"; + } + document.getElementById("loader").className = "loader"; + // create stage and point it to the canvas: + canvas = document.getElementById("testCanvas"); + + index = 0; + colors = ["#828b20", "#b0ac31", "#cbc53d", "#fad779", "#f9e4ad", + "#faf2db", "#563512", "#9b4a0b", "#d36600", "#fe8a00", + "#f9a71f"]; + oldPt = new createjs.Point(400, 300); + midPt = oldPt; + oldMidPt = oldPt; + + //check to see if we are running in a browser with touch support + stage = new createjs.Stage(canvas); + + // enable touch interactions if supported on the current device: + createjs.Touch.enable(stage); + + // keep tracking the mouse even when it leaves the canvas + stage.mouseMoveOutside = true; + + // enabled mouse over / out events + stage.enableMouseOver(10); + + // load the source images: 20 dots, a star and a turtle + var ImageNames = new Array(); + for (i = 0; i < 20; i++) { + ImageNames[i] = "images/dots-" + i + ".svg"; + } + for (i = 0; i < ImageNames.length; i++) { + if (i < shapes[0].length) { + imagepos[i] = shapes[0][i]; + } else { + imagepos[i] = [-100, -100]; + } + } + for (i = 0; i < ImageNames.length; i++) { + myimages[i] = new Image(); + myimages[i].src = ImageNames[i]; + myimages[i].onload = handleImageLoad; + } + + pen = new Image(); + pen.src = "images/pen.svg" + pen.onload = handlePenLoad; + + drawingCanvas = new createjs.Shape(); + stage.addChild(drawingCanvas); + stage.update(); + } + + function stop() { + createjs.Ticker.removeEventListener("tick", tick); + } + + function handleImageLoad(event) { + var image = event.target; + var imgW = image.width; + var imgH = image.height; + var bitmap; + var container = new createjs.Container(); + stage.addChild(container); + + // create a shape that represents the center of the icon: + var hitArea = new createjs.Shape(); + hitArea.graphics.beginFill("#FFF").drawEllipse(-11, -14, 24, 18); + // position hitArea relative to the internal coordinate system + // of the target (bitmap instances): + hitArea.x = imgW / 2; + hitArea.y = imgH / 2; + + i = myimages.indexOf(image) + // Create and populate the screen with number icons. + bitmap = new createjs.Bitmap(image); + bitmaps[i] = bitmap // Save now so we can reposition later. + container.addChild(bitmap); + bitmap.x = imagepos[i][0] + bitmap.y = imagepos[i][1] + bitmap.regX = imgW / 2 | 0; + bitmap.regY = imgH / 2 | 0; + bitmap.scaleX = bitmap.scaleY = bitmap.scale = 0.67 + bitmap.name = "bmp_" + i; + + bitmap.cursor = "pointer"; + + // Eventually, we can check this to make sure the number + // has been touched. + bitmap.hitArea = hitArea; + + (function (target) { + })(bitmap); + + document.getElementById("loader").className = ""; + createjs.Ticker.addEventListener("tick", tick); + } + + function handlePenLoad(event) { + var image = event.target; + var imgW = image.width; + var imgH = image.height; + var bitmap; + var container = new createjs.Container(); + stage.addChild(container); + + // create a shape that represents the center of the icon + var hitArea = new createjs.Shape(); + hitArea.graphics.beginFill("#FFF").drawEllipse(-11, -14, 24, 18); + // position hitArea relative to the internal coordinate system + // of the target (bitmap instances): + hitArea.x = imgW / 2; + hitArea.y = imgH / 2; + + // create a pen + pen_bitmap = new createjs.Bitmap(image); + container.addChild(pen_bitmap); + pen_bitmap.x = imagepos[0][0] + pen_bitmap.y = imagepos[0][1] + pen_bitmap.regX = imgW / 2 | 0; + pen_bitmap.regY = imgH / 2 | 0; + pen_bitmap.scaleX = pen_bitmap.scaleY = pen_bitmap.scale = 1 + pen_bitmap.name = "bmp_pen"; + + pen_bitmap.cursor = "pointer"; + + // assign the hitArea to each bitmap to use it for hit tests: + pen_bitmap.hitArea = hitArea; + + // wrapper function to provide scope for the event handlers: + (function (target) { + pen_bitmap.onPress = function (evt) { + // bump the target in front of its siblings: + container.addChild(target); + var offset = { + x: target.x - evt.stageX, + y: target.y - evt.stageY + }; + + evt.onMouseMove = function (ev) { + target.x = ev.stageX + offset.x; + target.y = ev.stageY + offset.y; + // indicate that the stage should be updated + // on the next tick: + update = true; + var midPt = new createjs.Point(oldPt.x + stage.mouseX >> 1, + oldPt.y + stage.mouseY >> 1); + + drawingCanvas.graphics.setStrokeStyle(stroke, 'round', 'round').beginStroke(color).moveTo(midPt.x, midPt.y).curveTo(oldPt.x, oldPt.y, oldMidPt.x, oldMidPt.y); + + oldPt.x = stage.mouseX; + oldPt.y = stage.mouseY; + + oldMidPt.x = midPt.x; + oldMidPt.y = midPt.y; + } + } + pen_bitmap.onMouseOver = function () { + target.scaleX = target.scaleY = target.scale * 1.2; + update = true; + color = colors[(index++) % colors.length]; + stroke = Math.random() * 30 + 10 | 0; + oldPt = new createjs.Point(stage.mouseX, stage.mouseY); + oldMidPt = oldPt; + } + pen_bitmap.onMouseOut = function () { + target.scaleX = target.scaleY = target.scale; + update = true; + } + })(pen_bitmap); + + document.getElementById("loader").className = ""; + createjs.Ticker.addEventListener("tick", tick); + } + + function tick(event) { + // this set makes it so the stage only re-renders when an event handler indicates a change has happened. + if (update) { + update = false; // only update once + stage.update(event); + } + } + + function new_positions() { + shape = shape + 1; + for (i = 0; i < bitmaps.length; i++) { + if (shape < shapes.length) { + if (i < shapes[shape].length) { + bitmaps[i].x = shapes[shape][i][0]; + bitmaps[i].y = shapes[shape][i][1]; + } else { + bitmaps[i].x = -100; + bitmaps[i].y = -100; + } + } else { + bitmaps[i].x = canvas.width * Math.random() | 0; + bitmaps[i].y = canvas.height * Math.random() | 0; + } + } + pen_bitmap.x = bitmaps[0].x + pen_bitmap.y = bitmaps[0].y + + drawingCanvas.graphics.clear(); + + update = true; + } + + // Get things started + init(); + }); + +}); diff --git a/activities/ConnecttheDots.activity/js/activity.js b/activities/ConnecttheDots.activity/js/activity.js new file mode 100644 index 000000000..4e49a3b14 --- /dev/null +++ b/activities/ConnecttheDots.activity/js/activity.js @@ -0,0 +1,316 @@ +define(function (require) { + var activity = require("sugar-web/activity/activity"); + var icon = require("sugar-web/graphics/icon"); + require("easel"); + require("handlebars") + var shapes = require("activity/shapes") + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + // Initialize the activity. + activity.setup(); + + // Colorize the activity icon. + var activityButton = document.getElementById("activity-button"); + activity.getXOColor(function (error, colors) { + icon.colorize(activityButton, colors); + }); + + var newButton = document.getElementById("new-button"); + newButton.onclick = function () { + new_positions(); + } + + // Make the activity stop with the stop button. + var stopButton = document.getElementById("stop-button"); + stopButton.addEventListener('click', function (e) { + activity.close(); + }); + + // A handlebars template for generating the dot labels + var labelSource = + "
" + + "{{#each labels}}" + + "

{{this}}

" + + "{{/each}}" + + "
" + + // Create label elements for each of our dots + template = Handlebars.compile(labelSource); + var arrLabels = []; + for (i = 0; i < 21; i++) { + arrLabels[i] = i; + } + var labelElem = document.getElementById("labelDiv"); + var html = template({labels:arrLabels}); + labelElem.innerHTML = html; + + // Then create a list of the label elements + nlabels = []; + for (i = 0; i < arrLabels.length; i++) { + nlabels[i] = document.getElementById("n" + i.toString()); + } + + // Stage is an Easel construct + var canvas, stage; + + // The display object currently under the mouse, or being dragged + var mouseTarget; + // Indicates whether we are currently in a drag operation + var dragStarted; + var offset; + var update = true; + var drawingCanvas; + var oldPt; + var midPt; + var oldMidPt; + var color; + var stroke; + var colors; + var index; + + var imagepos = new Array(); + var myimages = new Array(); + var bitmaps = new Array(); + var pen_bitmap; + + var Star = "images/star.svg"; + var Dot = "images/dot.svg"; + var Pen = "images/pen.svg"; + + var shape = -1; + + // Get things started + init(); + + function init() { + if (window.top != window) { + document.getElementById("header").style.display = "none"; + } + document.getElementById("loader").className = "loader"; + // Create the stage and point it to the canvas: + canvas = document.getElementById("myCanvas"); + + index = 0; + colors = ["#828b20", "#b0ac31", "#cbc53d", "#fad779", "#f9e4ad", + "#faf2db", "#563512", "#9b4a0b", "#d36600", "#fe8a00", + "#f9a71f"]; + oldPt = new createjs.Point(400, 300); + midPt = oldPt; + oldMidPt = oldPt; + + // Check to see if we are running in a browser with touch support + stage = new createjs.Stage(canvas); + + // Enable touch interactions if supported on the current device: + createjs.Touch.enable(stage); + + // Keep tracking the mouse even when it leaves the canvas + stage.mouseMoveOutside = true; + + // Enabled mouse over and mouse out events + stage.enableMouseOver(10); + + // Load the source images: a dot, a star and a turtle + for (i = 0; i < nlabels.length; i++) { + imagepos[i] = [-100, -100]; + } + for (i = 0; i < nlabels.length; i++) { + myimages[i] = new Image(); + if (i == 0) { + myimages[i].src = Star; + } else { + myimages[i].src = Dot; + } + myimages[i].onload = handleImageLoad; + } + + pen = new Image(); + pen.src = Pen; + pen.onload = handlePenLoad; + + // Create a drawing canvas + drawingCanvas = new createjs.Shape(); + stage.addChild(drawingCanvas); + new_positions(); + stage.update(); + } + + function stop() { + createjs.Ticker.removeEventListener("tick", tick); + } + + function handleImageLoad(event) { + var image = event.target; + var imgW = image.width; + var imgH = image.height; + var bitmap; + var container = new createjs.Container(); + stage.addChild(container); + + // Create a shape that represents the center of the icon: + var hitArea = new createjs.Shape(); + hitArea.graphics.beginFill("#FFF").drawEllipse(-11, -14, 24, 18); + // Position hitArea relative to the internal coordinate system + // of the target (bitmap instances): + hitArea.x = imgW / 2; + hitArea.y = imgH / 2; + + i = myimages.indexOf(image) + // Create and populate the screen with number icons. + bitmap = new createjs.Bitmap(image); + bitmaps[i] = bitmap // Save now so we can reposition later. + container.addChild(bitmap); + bitmap.x = imagepos[i][0] + bitmap.y = imagepos[i][1] + bitmap.regX = imgW / 2 | 0; + bitmap.regY = imgH / 2 | 0; + if (i == 0) { + bitmap.scaleX = bitmap.scaleY = bitmap.scale = 0.5 + } else { + bitmap.scaleX = bitmap.scaleY = bitmap.scale = 1.5 + } + bitmap.name = "bmp_" + i; + + bitmap.cursor = "pointer"; + + // Eventually, we can check this to make sure the number + // has been touched. + bitmap.hitArea = hitArea; + + (function (target) {})(bitmap); + + document.getElementById("loader").className = ""; + createjs.Ticker.addEventListener("tick", tick); + } + + function handlePenLoad(event) { + var image = event.target; + var imgW = image.width; + var imgH = image.height; + var bitmap; + var container = new createjs.Container(); + stage.addChild(container); + + // Create a shape that represents the center of the icon + var hitArea = new createjs.Shape(); + hitArea.graphics.beginFill("#FFF").drawEllipse(-22, -28, 48, 36); + // Position hitArea relative to the internal coordinate system + // of the target (bitmap instances): + hitArea.x = imgW / 2; + hitArea.y = imgH / 2; + + // Create a pen + bitmap = new createjs.Bitmap(image); + pen_bitmap = bitmap + container.addChild(bitmap); + bitmap.x = imagepos[0][0] + bitmap.y = imagepos[0][1] + bitmap.regX = imgW / 2 | 0; + bitmap.regY = imgH / 2 | 0; + bitmap.scaleX = bitmap.scaleY = bitmap.scale = 1 + bitmap.name = "bmp_pen"; + + bitmap.cursor = "pointer"; + + // Assign the hitArea to bitmap to use it for hit tests: + bitmap.hitArea = hitArea; + + // Wrapper function to provide scope for the event handlers: + (function (target) { + bitmap.onPress = function (evt) { + // Bump the target in front of its siblings: + container.addChild(target); + var offset = { + x: target.x - evt.stageX, + y: target.y - evt.stageY + }; + + evt.onMouseMove = function (ev) { + target.x = ev.stageX + offset.x; + target.y = ev.stageY + offset.y; + // Indicate that the stage should be updated + // on the next tick: + update = true; + var midPt = new createjs.Point(oldPt.x + stage.mouseX >> 1, + oldPt.y + stage.mouseY >> 1); + + drawingCanvas.graphics.setStrokeStyle(stroke, 'round', 'round').beginStroke(color).moveTo(midPt.x, midPt.y).curveTo(oldPt.x, oldPt.y, oldMidPt.x, oldMidPt.y); + + oldPt.x = stage.mouseX; + oldPt.y = stage.mouseY; + + oldMidPt.x = midPt.x; + oldMidPt.y = midPt.y; + } + } + bitmap.onMouseOver = function () { + target.scaleX = target.scaleY = target.scale * 1.2; + update = true; + color = colors[(index++) % colors.length]; + stroke = Math.random() * 30 + 10 | 0; + oldPt = new createjs.Point(stage.mouseX, stage.mouseY); + oldMidPt = oldPt; + } + bitmap.onMouseOut = function () { + target.scaleX = target.scaleY = target.scale; + update = true; + } + })(bitmap); + + document.getElementById("loader").className = ""; + createjs.Ticker.addEventListener("tick", tick); + } + + function tick(event) { + // This set makes it so the stage only re-renders when + // an event handler indicates a change has happened. + if (update) { + update = false; // Only update once + stage.update(event); + } + } + + function new_positions() { + if( shape == -1 ) { + shape = 0 + return + } + + for (i = 0; i < bitmaps.length; i++) { + if (shape < shapes.length) { + if (i < shapes[shape].length) { + bitmaps[i].x = shapes[shape][i][0]; + bitmaps[i].y = shapes[shape][i][1]; + } else { + bitmaps[i].x = -100; + bitmaps[i].y = -100; + } + } else { + bitmaps[i].x = canvas.width * Math.random() | 0; + bitmaps[i].y = canvas.height * Math.random() | 0; + } + } + for (i = 0; i < bitmaps.length; i++) { + // Put the label in the dot + if (i < 10) { + nlabels[i].style.left = Math.round(bitmaps[i].x + canvas.offsetLeft - 5) + "px"; + } else { + nlabels[i].style.left = Math.round(bitmaps[i].x + canvas.offsetLeft - 11) + "px"; + } + nlabels[i].style.top = Math.round(bitmaps[i].y + canvas.offsetTop - 30) + "px"; + } + + pen_bitmap.x = bitmaps[0].x; + pen_bitmap.y = bitmaps[0].y; + + drawingCanvas.graphics.clear(); + + update = true; + shape = shape + 1; + } + + }); + +}); diff --git a/activities/ConnecttheDots.activity/js/loader.js b/activities/ConnecttheDots.activity/js/loader.js new file mode 100644 index 000000000..c7c5a5235 --- /dev/null +++ b/activities/ConnecttheDots.activity/js/loader.js @@ -0,0 +1,15 @@ +requirejs.config({ + baseUrl: "lib", + shim: { + easel: { + exports: "createjs" + } + }, + paths: { + activity: "../js", + easel: "../lib/easeljs", + handlebars: "../lib/handlebars", + } +}); + +requirejs(["activity/activity"]); diff --git a/activities/ConnecttheDots.activity/js/shapes.js b/activities/ConnecttheDots.activity/js/shapes.js new file mode 100644 index 000000000..24e5e294b --- /dev/null +++ b/activities/ConnecttheDots.activity/js/shapes.js @@ -0,0 +1,44 @@ +define(function (require) { + + var triangle = [[400, 150], [550, 400], [250, 400], [400, 100]]; + var square = [[250, 100], [600, 100], [600, 400], [200, 400], + [200, 100]]; + var circle = [[175, 400], [150, 300], [200, 100], [400, 50], [600, 100], + [650, 300], [600, 500], [400, 550], [200, 500]]; + var star = [[400, 100], [200, 500], [650, 200], [150, 200], [600, 500], + [400, 50]]; + var flower = [[293, 16], [352, 101], [422, 20], [462, 120], [436, 257], + [334, 316], [324, 476], [550, 268], [555, 395], + [479, 530], [343, 558], [242, 558], [60, 487], [40, 263], + [252, 471], [264, 321], [155, 258], [131, 121], + [171, 26], [243, 91]]; + var cat = [[420, 93], [450, 104], [492, 63], [513, 152], [488, 203], + [500, 275], [445, 361], [447, 430], [478, 490], [392, 405], + [333, 461], [379, 483], [210, 499], [ 98, 526], [106, 571], + [ 54, 518], [178, 446], [210, 340], [282, 239], [401, 198], + [385, 83]]; + var hand = [[175, 150], [200, 125], [280, 220], [330, 200], [425, 25], + [440, 175], [540, 50], [480, 210], [620, 100], [520, 280], + [680, 270], [520, 330], [420, 420], [400, 450], [280, 450], + [260, 340], [200, 200]]; + var baby = [[300, 0], [345, 50], [313, 175], [358, 206], [475, 367], + [370, 313], [370, 408], [434, 501], [311, 600], [348, 504], + [300, 463], [210, 504], [248, 600], [124, 487], [191, 401], + [191, 303], [ 76, 367], [216, 205], [248, 175], [213, 50]]; + var moon = [[479, 530], [300, 419], [254, 286], [300, 137], [476, 45], + [293, 16], [160, 78], [76, 233], [122, 447], [294, 552]]; + var bird = [[557, 127], [492, 165], [493, 287], [444, 388], [317, 448], + [311, 496], [332, 536], [384, 558], [284, 558], [277, 436], + [165, 425], [25, 475], [75, 436], [25, 435], [212, 309], + [377, 183], [419, 111], [460, 87], [505, 106]]; + var fish = [[354, 25], [412, 80], [515, 130], [571, 202], [523, 220], + [549, 255], [418, 301], [322, 369], [353, 311], [243, 297], + [99, 224], [25, 319], [55, 182], [25, 52], [99, 147], + [234, 75], [356, 70]]; + var lightning = [[391, 25], [247, 50], [152, 299], [281, 293], + [209, 554], [430, 195], [285, 210], [363, 73]]; + var shapes = [triangle, square, circle, star, lightning, + moon, flower, fish, bird, cat, hand, baby]; + + return shapes; +}); diff --git a/activities/ConnecttheDots.activity/lib/domReady.js b/activities/ConnecttheDots.activity/lib/domReady.js new file mode 100644 index 000000000..2b5412209 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/domReady.js @@ -0,0 +1,129 @@ +/** + * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/domReady for details + */ +/*jslint */ +/*global require: false, define: false, requirejs: false, + window: false, clearInterval: false, document: false, + self: false, setInterval: false */ + + +define(function () { + 'use strict'; + + var isTop, testDiv, scrollIntervalId, + isBrowser = typeof window !== "undefined" && window.document, + isPageLoaded = !isBrowser, + doc = isBrowser ? document : null, + readyCalls = []; + + function runCallbacks(callbacks) { + var i; + for (i = 0; i < callbacks.length; i += 1) { + callbacks[i](doc); + } + } + + function callReady() { + var callbacks = readyCalls; + + if (isPageLoaded) { + //Call the DOM ready callbacks + if (callbacks.length) { + readyCalls = []; + runCallbacks(callbacks); + } + } + } + + /** + * Sets the page as loaded. + */ + function pageLoaded() { + if (!isPageLoaded) { + isPageLoaded = true; + if (scrollIntervalId) { + clearInterval(scrollIntervalId); + } + + callReady(); + } + } + + if (isBrowser) { + if (document.addEventListener) { + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. + document.addEventListener("DOMContentLoaded", pageLoaded, false); + window.addEventListener("load", pageLoaded, false); + } else if (window.attachEvent) { + window.attachEvent("onload", pageLoaded); + + testDiv = document.createElement('div'); + try { + isTop = window.frameElement === null; + } catch (e) {} + + //DOMContentLoaded approximation that uses a doScroll, as found by + //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, + //but modified by other contributors, including jdalton + if (testDiv.doScroll && isTop && window.external) { + scrollIntervalId = setInterval(function () { + try { + testDiv.doScroll(); + pageLoaded(); + } catch (e) {} + }, 30); + } + } + + //Check if document already complete, and if so, just trigger page load + //listeners. Latest webkit browsers also use "interactive", and + //will fire the onDOMContentLoaded before "interactive" but not after + //entering "interactive" or "complete". More details: + //http://dev.w3.org/html5/spec/the-end.html#the-end + //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded + //Hmm, this is more complicated on further use, see "firing too early" + //bug: https://github.com/requirejs/domReady/issues/1 + //so removing the || document.readyState === "interactive" test. + //There is still a window.onload binding that should get fired if + //DOMContentLoaded is missed. + if (document.readyState === "complete") { + pageLoaded(); + } + } + + /** START OF PUBLIC API **/ + + /** + * Registers a callback for DOM ready. If DOM is already ready, the + * callback is called immediately. + * @param {Function} callback + */ + function domReady(callback) { + if (isPageLoaded) { + callback(doc); + } else { + readyCalls.push(callback); + } + return domReady; + } + + domReady.version = '2.0.1'; + + /** + * Loader Plugin API method + */ + domReady.load = function (name, req, onLoad, config) { + if (config.isBuild) { + onLoad(null); + } else { + domReady(onLoad); + } + }; + + /** END OF PUBLIC API **/ + + return domReady; +}); diff --git a/activities/ConnecttheDots.activity/lib/easeljs.js b/activities/ConnecttheDots.activity/lib/easeljs.js new file mode 100644 index 000000000..9c20189e9 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/easeljs.js @@ -0,0 +1,135 @@ +/* +* EaselJS +* Visit http://createjs.com/ for documentation, updates and examples. +* +* Copyright (c) 2011 gskinner.com, inc. +* +* Distributed under the terms of the MIT license. +* http://www.opensource.org/licenses/mit-license.html +* +* This notice shall be included in all copies or substantial portions of the Software. +*/ +this.createjs=this.createjs||{};(function(){var c=function(){throw"UID cannot be instantiated";};c._nextID=0;c.get=function(){return c._nextID++};createjs.UID=c})();this.createjs=this.createjs||{}; +(function(){var c=function(){this.initialize()},b=c.prototype;c.initialize=function(a){a.addEventListener=b.addEventListener;a.removeEventListener=b.removeEventListener;a.removeAllEventListeners=b.removeAllEventListeners;a.hasEventListener=b.hasEventListener;a.dispatchEvent=b.dispatchEvent};b._listeners=null;b.initialize=function(){};b.addEventListener=function(a,m){var b=this._listeners;b?this.removeEventListener(a,m):b=this._listeners={};var d=b[a];d||(d=b[a]=[]);d.push(m);return m};b.removeEventListener= +function(a,m){var b=this._listeners;if(b){var d=b[a];if(d)for(var e=0,c=d.length;ec._times.length)return-1;null==a&&(a=c.getFPS()|0);a=Math.min(c._times.length-1,a);return 1E3/((c._times[0]-c._times[a])/a)};c.setPaused=function(a){c._paused=a};c.getPaused=function(){return c._paused};c.getTime=function(a){return c._getTime()-c._startTime-(a?c._pausedTime:0)};c.getTicks=function(a){return c._ticks-(a? +c._pausedTicks:0)};c._handleAF=function(){c._rafActive=!1;c._setupTick();c._getTime()-c._lastTime>=0.97*(c._interval-1)&&c._tick()};c._handleTimeout=function(){c.timeoutID=null;c._setupTick();c._tick()};c._setupTick=function(){if(!(c._rafActive||null!=c.timeoutID)){if(c.useRAF){var a=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame;if(a){a(c._handleAF);c._rafActive=!0;return}}c.timeoutID= +setTimeout(c._handleTimeout,c._interval)}};c._tick=function(){var a=c._getTime();c._ticks++;var m=a-c._lastTime,b=c._paused;b&&(c._pausedTicks++,c._pausedTime+=m);c._lastTime=a;for(var d=c._pauseable,e=c._listeners.slice(),f=e?e.length:0,h=0;hthis.a&&0<=this.d&&(a.rotation+=0>=a.rotation?180:-180),a.skewX=a.skewY= +0):(a.skewX=b/c.DEG_TO_RAD,a.skewY=g/c.DEG_TO_RAD);return a};b.reinitialize=function(a,b,g,d,c,f,h,k,j){this.initialize(a,b,g,d,c,f);this.alpha=h||1;this.shadow=k;this.compositeOperation=j;return this};b.appendProperties=function(a,b,g){this.alpha*=a;this.shadow=b||this.shadow;this.compositeOperation=g||this.compositeOperation;return this};b.prependProperties=function(a,b,g){this.alpha*=a;this.shadow=this.shadow||b;this.compositeOperation=this.compositeOperation||g;return this};b.clone=function(){var a= +new c(this.a,this.b,this.c,this.d,this.tx,this.ty);a.shadow=this.shadow;a.alpha=this.alpha;a.compositeOperation=this.compositeOperation;return a};b.toString=function(){return"[Matrix2D (a="+this.a+" b="+this.b+" c="+this.c+" d="+this.d+" tx="+this.tx+" ty="+this.ty+")]"};c.identity=new c(1,0,0,1,0,0);createjs.Matrix2D=c})();this.createjs=this.createjs||{};(function(){var c=function(a,b){this.initialize(a,b)},b=c.prototype;b.x=0;b.y=0;b.initialize=function(a,b){this.x=null==a?0:a;this.y=null==b?0:b};b.clone=function(){return new c(this.x,this.y)};b.toString=function(){return"[Point (x="+this.x+" y="+this.y+")]"};createjs.Point=c})();this.createjs=this.createjs||{};(function(){var c=function(a,b,g,d){this.initialize(a,b,g,d)},b=c.prototype;b.x=0;b.y=0;b.width=0;b.height=0;b.initialize=function(a,b,g,d){this.x=null==a?0:a;this.y=null==b?0:b;this.width=null==g?0:g;this.height=null==d?0:d};b.clone=function(){return new c(this.x,this.y,this.width,this.height)};b.toString=function(){return"[Rectangle (x="+this.x+" y="+this.y+" width="+this.width+" height="+this.height+")]"};createjs.Rectangle=c})();this.createjs=this.createjs||{}; +(function(){var c=function(a,b,g,d,c,f,h){this.initialize(a,b,g,d,c,f,h)},b=c.prototype;b.target=null;b.overLabel=null;b.outLabel=null;b.downLabel=null;b.play=!1;b._isPressed=!1;b._isOver=!1;b.initialize=function(a,b,g,d,c,f,h){a.addEventListener&&(this.target=a,a.cursor="pointer",this.overLabel=null==g?"over":g,this.outLabel=null==b?"out":b,this.downLabel=null==d?"down":d,this.play=c,this.setEnabled(!0),this.handleEvent({}),f&&(h&&(f.actionsEnabled=!1,f.gotoAndStop&&f.gotoAndStop(h)),a.hitArea=f))}; +b.setEnabled=function(a){var b=this.target;a?(b.addEventListener("mouseover",this),b.addEventListener("mouseout",this),b.addEventListener("mousedown",this)):(b.removeEventListener("mouseover",this),b.removeEventListener("mouseout",this),b.removeEventListener("mousedown",this))};b.toString=function(){return"[ButtonHelper]"};b.handleEvent=function(a){var b=this.target,g=a.type;"mousedown"==g?(a.addEventListener("mouseup",this),this._isPressed=!0,a=this.downLabel):"mouseup"==g?(this._isPressed=!1,a= +this._isOver?this.overLabel:this.outLabel):"mouseover"==g?(this._isOver=!0,a=this._isPressed?this.downLabel:this.overLabel):(this._isOver=!1,a=this._isPressed?this.overLabel:this.outLabel);this.play?b.gotoAndPlay&&b.gotoAndPlay(a):b.gotoAndStop&&b.gotoAndStop(a)};createjs.ButtonHelper=c})();this.createjs=this.createjs||{};(function(){var c=function(a,b,g,d){this.initialize(a,b,g,d)},b=c.prototype;c.identity=null;b.color=null;b.offsetX=0;b.offsetY=0;b.blur=0;b.initialize=function(a,b,g,d){this.color=a;this.offsetX=b;this.offsetY=g;this.blur=d};b.toString=function(){return"[Shadow]"};b.clone=function(){return new c(this.color,this.offsetX,this.offsetY,this.blur)};c.identity=new c("transparent",0,0,0);createjs.Shadow=c})();this.createjs=this.createjs||{}; +(function(){var c=function(a){this.initialize(a)},b=c.prototype;b.complete=!0;b.onComplete=null;b.addEventListener=null;b.removeEventListener=null;b.removeAllEventListeners=null;b.dispatchEvent=null;b.hasEventListener=null;b._listeners=null;createjs.EventDispatcher.initialize(b);b._animations=null;b._frames=null;b._images=null;b._data=null;b._loadCount=0;b._frameHeight=0;b._frameWidth=0;b._numFrames=0;b._regX=0;b._regY=0;b.initialize=function(a){var b,g,d;if(null!=a){if(a.images&&0<(g=a.images.length)){d= +this._images=[];for(b=0;bd.length||!1==a.next?null:null==a.next||!0==a.next?h:a.next;a.frequency||(a.frequency=1);this._animations.push(h);this._data[h]=a}}}};b.getNumFrames=function(a){if(null==a)return this._frames?this._frames.length:this._numFrames;a=this._data[a];return null==a?0:a.frames.length};b.getAnimations=function(){return this._animations.slice(0)};b.getAnimation=function(a){return this._data[a]};b.getFrame=function(a){var b;return this.complete&&this._frames&&(b=this._frames[a])?b:null}; +b.getFrameBounds=function(a){return(a=this.getFrame(a))?new createjs.Rectangle(-a.regX,-a.regY,a.rect.width,a.rect.height):null};b.toString=function(){return"[SpriteSheet]"};b.clone=function(){var a=new c;a.complete=this.complete;a._animations=this._animations;a._frames=this._frames;a._images=this._images;a._data=this._data;a._frameHeight=this._frameHeight;a._frameWidth=this._frameWidth;a._numFrames=this._numFrames;a._loadCount=this._loadCount;return a};b._handleImageLoad=function(){0==--this._loadCount&& +(this._calculateFrames(),this.complete=!0,this.onComplete&&this.onComplete(),this.dispatchEvent("complete"))};b._calculateFrames=function(){if(!(this._frames||0==this._frameWidth)){this._frames=[];for(var a=0,b=this._frameWidth,g=this._frameHeight,d=0,c=this._images;d>8&255,a=a>>16&255);return null==c?"rgb("+a+","+b+","+d+")":"rgba("+a+","+b+","+d+","+c+")"};b.getHSL=function(a,b,d,c){return null==c?"hsl("+a%360+","+b+"%,"+d+"%)":"hsla("+a%360+","+b+"%,"+d+"%,"+c+")"};b.BASE_64={A:0,B:1,C:2,D:3,E:4,F:5,G:6,H:7,I:8,J:9, +K:10,L:11,M:12,N:13,O:14,P:15,Q:16,R:17,S:18,T:19,U:20,V:21,W:22,X:23,Y:24,Z:25,a:26,b:27,c:28,d:29,e:30,f:31,g:32,h:33,i:34,j:35,k:36,l:37,m:38,n:39,o:40,p:41,q:42,r:43,s:44,t:45,u:46,v:47,w:48,x:49,y:50,z:51,"0":52,1:53,2:54,3:55,4:56,5:57,6:58,7:59,8:60,9:61,"+":62,"/":63};b.STROKE_CAPS_MAP=["butt","round","square"];b.STROKE_JOINTS_MAP=["miter","round","bevel"];b._ctx=(createjs.createCanvas?createjs.createCanvas():document.createElement("canvas")).getContext("2d");b.beginCmd=new c(b._ctx.beginPath, +[],!1);b.fillCmd=new c(b._ctx.fill,[],!1);b.strokeCmd=new c(b._ctx.stroke,[],!1);a._strokeInstructions=null;a._strokeStyleInstructions=null;a._ignoreScaleStroke=!1;a._fillInstructions=null;a._instructions=null;a._oldInstructions=null;a._activeInstructions=null;a._active=!1;a._dirty=!1;a.initialize=function(){this.clear();this._ctx=b._ctx};a.isEmpty=function(){return!(this._instructions.length||this._oldInstructions.length||this._activeInstructions.length)};a.draw=function(a){this._dirty&&this._updateInstructions(); +for(var b=this._instructions,d=0,c=b.length;df&&(f*=n=-1);f>l&&(f=l);0>h&&(h*=q=-1);h>l&&(h=l);0>k&&(k*=p=-1);k>l&&(k=l);0>j&&(j*=s=-1);j>l&&(j=l);this._dirty=this._active=!0;var l=this._ctx.arcTo,r=this._ctx.lineTo;this._activeInstructions.push(new c(this._ctx.moveTo,[a+d-h,b]),new c(l,[a+d+h*q,b-h*q,a+d,b+h,h]),new c(r,[a+d,b+e-k]),new c(l,[a+d+k*p,b+e+k*p,a+d-k,b+e,k]),new c(r,[a+j,b+e]),new c(l,[a-j*s,b+e+j*s,a,b+e-j,j]),new c(r,[a,b+f]),new c(l,[a-f*n,b-f*n,a+f,b,f]),new c(this._ctx.closePath));return this};a.drawCircle= +function(a,b,d){this.arc(a,b,d,0,2*Math.PI);return this};a.drawEllipse=function(a,b,d,e){this._dirty=this._active=!0;var f=0.5522848*(d/2),h=0.5522848*(e/2),k=a+d,j=b+e;d=a+d/2;e=b+e/2;this._activeInstructions.push(new c(this._ctx.moveTo,[a,e]),new c(this._ctx.bezierCurveTo,[a,e-h,d-f,b,d,b]),new c(this._ctx.bezierCurveTo,[d+f,b,k,e-h,k,e]),new c(this._ctx.bezierCurveTo,[k,e+h,d+f,j,d,j]),new c(this._ctx.bezierCurveTo,[d-f,j,a,e+h,a,e]));return this};a.drawPolyStar=function(a,b,d,e,f,h){this._dirty= +this._active=!0;null==f&&(f=0);f=1-f;h=null==h?0:h/(180/Math.PI);var k=Math.PI/e;this._activeInstructions.push(new c(this._ctx.moveTo,[a+Math.cos(h)*d,b+Math.sin(h)*d]));for(var j=0;j>3,s=g[p];if(!s||q&3)throw"bad path data (@"+c+"): "+n;n=d[p];p||(k=j=0);h.length=0;c++;q=(q>>2&1)+2;for(p=0;p>5?-1:1,r=(r&31)<<6|l[a.charAt(c+1)];3==q&&(r=r<<6|l[a.charAt(c+2)]);r=u*r/10;p%2?k=r+=k:j=r+=j;h[p]=r;c+=q}s.apply(this,h)}return this};a.clone=function(){var a=new b;a._instructions=this._instructions.slice();a._activeInstructions=this._activeInstructions.slice(); +a._oldInstructions=this._oldInstructions.slice();this._fillInstructions&&(a._fillInstructions=this._fillInstructions.slice());this._strokeInstructions&&(a._strokeInstructions=this._strokeInstructions.slice());this._strokeStyleInstructions&&(a._strokeStyleInstructions=this._strokeStyleInstructions.slice());a._active=this._active;a._dirty=this._dirty;return a};a.toString=function(){return"[Graphics]"};a.mt=a.moveTo;a.lt=a.lineTo;a.at=a.arcTo;a.bt=a.bezierCurveTo;a.qt=a.quadraticCurveTo;a.a=a.arc;a.r= +a.rect;a.cp=a.closePath;a.c=a.clear;a.f=a.beginFill;a.lf=a.beginLinearGradientFill;a.rf=a.beginRadialGradientFill;a.bf=a.beginBitmapFill;a.ef=a.endFill;a.ss=a.setStrokeStyle;a.s=a.beginStroke;a.ls=a.beginLinearGradientStroke;a.rs=a.beginRadialGradientStroke;a.bs=a.beginBitmapStroke;a.es=a.endStroke;a.dr=a.drawRect;a.rr=a.drawRoundRect;a.rc=a.drawRoundRectComplex;a.dc=a.drawCircle;a.de=a.drawEllipse;a.dp=a.drawPolyStar;a.p=a.decodePath;a._updateInstructions=function(){this._instructions=this._oldInstructions.slice(); +this._instructions.push(b.beginCmd);this._instructions.push.apply(this._instructions,this._activeInstructions);this._fillInstructions&&this._instructions.push.apply(this._instructions,this._fillInstructions);this._strokeInstructions&&(this._strokeStyleInstructions&&this._instructions.push.apply(this._instructions,this._strokeStyleInstructions),this._instructions.push.apply(this._instructions,this._strokeInstructions),this._ignoreScaleStroke?this._instructions.push(new c(this._ctx.save,[],!1),new c(this._ctx.setTransform, +[1,0,0,1,0,0],!1),b.strokeCmd,new c(this._ctx.restore,[],!1)):this._instructions.push(b.strokeCmd))};a._newPath=function(){this._dirty&&this._updateInstructions();this._oldInstructions=this._instructions;this._activeInstructions=[];this._active=this._dirty=!1};a._setProp=function(a,b){this[a]=b};createjs.Graphics=b})();this.createjs=this.createjs||{}; +(function(){var c=function(){this.initialize()},b=c.prototype;c.suppressCrossDomainErrors=!1;c._hitTestCanvas=createjs.createCanvas?createjs.createCanvas():document.createElement("canvas");c._hitTestCanvas.width=c._hitTestCanvas.height=1;c._hitTestContext=c._hitTestCanvas.getContext("2d");c._nextCacheID=1;b.alpha=1;b.cacheCanvas=null;b.id=-1;b.mouseEnabled=!0;b.name=null;b.parent=null;b.regX=0;b.regY=0;b.rotation=0;b.scaleX=1;b.scaleY=1;b.skewX=0;b.skewY=0;b.shadow=null;b.visible=!0;b.x=0;b.y=0;b.compositeOperation= +null;b.snapToPixel=!1;b.onPress=null;b.onClick=null;b.onDoubleClick=null;b.onMouseOver=null;b.onMouseOut=null;b.onTick=null;b.filters=null;b.cacheID=0;b.mask=null;b.hitArea=null;b.cursor=null;b.addEventListener=null;b.removeEventListener=null;b.removeAllEventListeners=null;b.dispatchEvent=null;b.hasEventListener=null;b._listeners=null;createjs.EventDispatcher.initialize(b);b._cacheOffsetX=0;b._cacheOffsetY=0;b._cacheScale=1;b._cacheDataURLID=0;b._cacheDataURL=null;b._matrix=null;b.initialize=function(){this.id= +createjs.UID.get();this._matrix=new createjs.Matrix2D};b.isVisible=function(){return!(!this.visible||!(0d||d>this.children.length)return arguments[c-2];if(2a||a>this.children.length- +1)return!1;if(b=this.children[a])b.parent=null;this.children.splice(a,1);return!0};b.removeAllChildren=function(){for(var a=this.children;a.length;)a.pop().parent=null};b.getChildAt=function(a){return this.children[a]};b.getChildByName=function(a){for(var b=this.children,c=0,d=b.length;cb||b>=d)){for(var e=0;e=a)return;var b=this;this._mouseOverIntervalID=setInterval(function(){b._testMouseOver()},1E3/Math.min(50,a))};b.enableDOMEvents=function(a){null==a&&(a=!0);var b,c=this._eventListeners;if(!a&&c){for(b in c)a=c[b],a.t.removeEventListener(b,a.f);this._eventListeners=null}else if(a&&!c&&this.canvas){a=window.addEventListener?window:document;var d=this,c=this._eventListeners={};c.mouseup={t:a,f:function(a){d._handleMouseUp(a)}}; +c.mousemove={t:a,f:function(a){d._handleMouseMove(a)}};c.dblclick={t:a,f:function(a){d._handleDoubleClick(a)}};c.mousedown={t:this.canvas,f:function(a){d._handleMouseDown(a)}};for(b in c)a=c[b],a.t.addEventListener(b,a.f)}};b.clone=function(){var a=new c(null);this.cloneProps(a);return a};b.toString=function(){return"[Stage (name="+this.name+")]"};b._getPointerData=function(a){var b=this._pointerData[a];if(!b&&(b=this._pointerData[a]={x:0,y:0},null==this._primaryPointerID||-1==this._primaryPointerID))this._primaryPointerID= +a;return b};b._handleMouseMove=function(a){a||(a=window.event);this._handlePointerMove(-1,a,a.pageX,a.pageY)};b._handlePointerMove=function(a,b,c,d){if(this.canvas){var e=this._getPointerData(a),f=e.inBounds;this._updatePointerPosition(a,c,d);if(f||e.inBounds||this.mouseMoveOutside){if(this.onMouseMove||this.hasEventListener("stagemousemove"))c=new createjs.MouseEvent("stagemousemove",e.x,e.y,this,b,a,a==this._primaryPointerID,e.rawX,e.rawY),this.onMouseMove&&this.onMouseMove(c),this.dispatchEvent(c); +if((d=e.event)&&(d.onMouseMove||d.hasEventListener("mousemove")))c=new createjs.MouseEvent("mousemove",e.x,e.y,d.target,b,a,a==this._primaryPointerID,e.rawX,e.rawY),d.onMouseMove&&d.onMouseMove(c),d.dispatchEvent(c,d.target)}}};b._updatePointerPosition=function(a,b,c){var d=this._getElementRect(this.canvas);b-=d.left;c-=d.top;var e=this.canvas.width,f=this.canvas.height;b/=(d.right-d.left)/e;c/=(d.bottom-d.top)/f;d=this._getPointerData(a);(d.inBounds=0<=b&&0<=c&&b<=e-1&&c<=f-1)?(d.x=b,d.y=c):this.mouseMoveOutside&& +(d.x=0>b?0:b>e-1?e-1:b,d.y=0>c?0:c>f-1?f-1:c);d.rawX=b;d.rawY=c;a==this._primaryPointerID&&(this.mouseX=d.x,this.mouseY=d.y,this.mouseInBounds=d.inBounds)};b._getElementRect=function(a){var b;try{b=a.getBoundingClientRect()}catch(c){b={top:a.offsetTop,left:a.offsetLeft,width:a.offsetWidth,height:a.offsetHeight}}var d=(window.pageXOffset||document.scrollLeft||0)-(document.clientLeft||document.body.clientLeft||0),e=(window.pageYOffset||document.scrollTop||0)-(document.clientTop||document.body.clientTop|| +0),f=window.getComputedStyle?getComputedStyle(a):a.currentStyle;a=parseInt(f.paddingLeft)+parseInt(f.borderLeftWidth);var h=parseInt(f.paddingTop)+parseInt(f.borderTopWidth),k=parseInt(f.paddingRight)+parseInt(f.borderRightWidth),f=parseInt(f.paddingBottom)+parseInt(f.borderBottomWidth);return{left:b.left+d+a,right:b.right+d-k,top:b.top+e+h,bottom:b.bottom+e-f}};b._handleMouseUp=function(a){this._handlePointerUp(-1,a,!1)};b._handlePointerUp=function(a,b,c){var d=this._getPointerData(a),e;if(this.onMouseMove|| +this.hasEventListener("stagemouseup"))e=new createjs.MouseEvent("stagemouseup",d.x,d.y,this,b,a,a==this._primaryPointerID,d.rawX,d.rawY),this.onMouseUp&&this.onMouseUp(e),this.dispatchEvent(e);var f=d.event;if(f&&(f.onMouseUp||f.hasEventListener("mouseup")))e=new createjs.MouseEvent("mouseup",d.x,d.y,f.target,b,a,a==this._primaryPointerID,d.rawX,d.rawY),f.onMouseUp&&f.onMouseUp(e),f.dispatchEvent(e,f.target);if((f=d.target)&&(f.onClick||f.hasEventListener("click"))&&this._getObjectsUnderPoint(d.x, +d.y,null,!0,this._mouseOverIntervalID?3:1)==f)e=new createjs.MouseEvent("click",d.x,d.y,f,b,a,a==this._primaryPointerID,d.rawX,d.rawY),f.onClick&&f.onClick(e),f.dispatchEvent(e);c?(a==this._primaryPointerID&&(this._primaryPointerID=null),delete this._pointerData[a]):d.event=d.target=null};b._handleMouseDown=function(a){this._handlePointerDown(-1,a,!1)};b._handlePointerDown=function(a,b,c,d){var e=this._getPointerData(a);null!=d&&this._updatePointerPosition(a,c,d);if(this.onMouseDown||this.hasEventListener("stagemousedown"))c= +new createjs.MouseEvent("stagemousedown",e.x,e.y,this,b,a,a==this._primaryPointerID,e.rawX,e.rawY),this.onMouseDown&&this.onMouseDown(c),this.dispatchEvent(c);if(d=this._getObjectsUnderPoint(e.x,e.y,null,this._mouseOverIntervalID?3:1))if(e.target=d,d.onPress||d.hasEventListener("mousedown"))if(c=new createjs.MouseEvent("mousedown",e.x,e.y,d,b,a,a==this._primaryPointerID,e.rawX,e.rawY),d.onPress&&d.onPress(c),d.dispatchEvent(c),c.onMouseMove||c.onMouseUp||c.hasEventListener("mousemove")||c.hasEventListener("mouseup"))e.event= +c};b._testMouseOver=function(){if(-1==this._primaryPointerID&&!(this.mouseX==this._mouseOverX&&this.mouseY==this._mouseOverY&&this.mouseInBounds)){var a=null;this.mouseInBounds&&(a=this._getObjectsUnderPoint(this.mouseX,this.mouseY,null,3),this._mouseOverX=this.mouseX,this._mouseOverY=this.mouseY);var b=this._mouseOverTarget;if(b!=a){var c=this._getPointerData(-1);if(b&&(b.onMouseOut||b.hasEventListener("mouseout"))){var d=new createjs.MouseEvent("mouseout",c.x,c.y,b,null,-1,c.rawX,c.rawY);b.onMouseOut&& +b.onMouseOut(d);b.dispatchEvent(d)}b&&(this.canvas.style.cursor="");if(a&&(a.onMouseOver||a.hasEventListener("mouseover")))d=new createjs.MouseEvent("mouseover",c.x,c.y,a,null,-1,c.rawX,c.rawY),a.onMouseOver&&a.onMouseOver(d),a.dispatchEvent(d);a&&(this.canvas.style.cursor=a.cursor||"");this._mouseOverTarget=a}}};b._handleDoubleClick=function(a){var b=this._getPointerData(-1),c=this._getObjectsUnderPoint(b.x,b.y,null,this._mouseOverIntervalID?3:1);if(c&&(c.onDoubleClick||c.hasEventListener("dblclick")))evt= +new createjs.MouseEvent("dblclick",b.x,b.y,c,a,-1,!0,b.rawX,b.rawY),c.onDoubleClick&&c.onDoubleClick(evt),c.dispatchEvent(evt)};createjs.Stage=c})();this.createjs=this.createjs||{}; +(function(){var c=function(a){this.initialize(a)},b=c.prototype=new createjs.DisplayObject;b.image=null;b.snapToPixel=!0;b.sourceRect=null;b.DisplayObject_initialize=b.initialize;b.initialize=function(a){this.DisplayObject_initialize();"string"==typeof a?(this.image=new Image,this.image.src=a):this.image=a};b.isVisible=function(){var a=this.cacheCanvas||this.image&&(this.image.complete||this.image.getContext||2<=this.image.readyState);return!(!this.visible||!(0=d){var e=a.next;this._dispatchAnimationEnd(a,b,c,e,d-1)||(e?this._goto(e):(this.paused=!0,this.currentAnimationFrame=a.frames.length-1,this.currentFrame= +a.frames[this.currentAnimationFrame]))}else this.currentFrame=a.frames[this.currentAnimationFrame];else d=this.spriteSheet.getNumFrames(),b>=d&&!this._dispatchAnimationEnd(a,b,c,d-1)&&(this.currentFrame=0)};b._dispatchAnimationEnd=function(a,b,c,d,e){var f=a?a.name:null;this.onAnimationEnd&&this.onAnimationEnd(this,f,d);this.dispatchEvent({type:"animationend",name:f,next:d});!c&&this.paused&&(this.currentAnimationFrame=e);return this.paused!=c||this._animation!=a||this.currentFrame!=b};b.DisplayObject_cloneProps= +b.cloneProps;b.cloneProps=function(a){this.DisplayObject_cloneProps(a);a.onAnimationEnd=this.onAnimationEnd;a.currentFrame=this.currentFrame;a.currentAnimation=this.currentAnimation;a.paused=this.paused;a.offset=this.offset;a._animation=this._animation;a.currentAnimationFrame=this.currentAnimationFrame};b._goto=function(a){if(isNaN(a)){var b=this.spriteSheet.getAnimation(a);b&&(this.currentAnimationFrame=0,this._animation=b,this.currentAnimation=a,this._normalizeFrame())}else this.currentAnimation= +this._animation=null,this.currentFrame=a};createjs.BitmapAnimation=c})();this.createjs=this.createjs||{}; +(function(){var c=function(a){this.initialize(a)},b=c.prototype=new createjs.DisplayObject;b.graphics=null;b.DisplayObject_initialize=b.initialize;b.initialize=function(a){this.DisplayObject_initialize();this.graphics=a?a:new createjs.Graphics};b.isVisible=function(){var a=this.cacheCanvas||this.graphics&&!this.graphics.isEmpty();return!(!this.visible||!(0this.lineWidth?(b&&this._drawTextLine(a,j,e*d),e++,j=k[l+1]):j+=k[l]+k[l+1];b&&this._drawTextLine(a,j,e*d)}e++}return e};b._drawTextLine=function(a,b,c){this.outline?a.strokeText(b,0,c,this.maxWidth||65535):a.fillText(b,0,c,this.maxWidth||65535)};createjs.Text=c})();this.createjs=this.createjs||{}; +(function(){var c=function(){throw"SpriteSheetUtils cannot be instantiated";};c._workingCanvas=createjs.createCanvas?createjs.createCanvas():document.createElement("canvas");c._workingContext=c._workingCanvas.getContext("2d");c.addFlippedFrames=function(b,a,m,g){if(a||m||g){var d=0;a&&c._flip(b,++d,!0,!1);m&&c._flip(b,++d,!1,!0);g&&c._flip(b,++d,!0,!0)}};c.extractFrame=function(b,a){isNaN(a)&&(a=b.getAnimation(a).frames[0]);var m=b.getFrame(a);if(!m)return null;var g=m.rect,d=c._workingCanvas;d.width= +g.width;d.height=g.height;c._workingContext.drawImage(m.image,g.x,g.y,g.width,g.height,0,0,g.width,g.height);m=new Image;m.src=d.toDataURL("image/png");return m};c.mergeAlpha=function(b,a,c){c||(c=createjs.createCanvas?createjs.createCanvas():document.createElement("canvas"));c.width=Math.max(a.width,b.width);c.height=Math.max(a.height,b.height);var g=c.getContext("2d");g.save();g.drawImage(b,0,0);g.globalCompositeOperation="destination-in";g.drawImage(a,0,0);g.restore();return c};c._flip=function(b, +a,m,g){for(var d=b._images,e=c._workingCanvas,f=c._workingContext,h=d.length/a,k=0;kthis.maxHeight)throw c.ERR_DIMENSIONS;for(var d=0,e=0,f=0;g.length;){var h=this._fillRow(g,d,f,b,a);h.w>e&&(e=h.w);d+=h.h;if(!h.h||!g.length){var k=createjs.createCanvas?createjs.createCanvas():document.createElement("canvas");k.width=this._getSize(e,this.maxWidth);k.height=this._getSize(d,this.maxHeight);this._data.images[f]=k;h.h||(e=d=0,f++)}}};b._getSize=function(a,b){for(var c=4;Math.pow(2,++c)f)throw c.ERR_DIMENSIONS;t>h||k+p>f||(n.img=g,n.rect=new createjs.Rectangle(k,b,p,t),j=j||t,a.splice(l,1),d[n.index]=[k,b,p,t,g,Math.round(-r+q*s.regX-e),Math.round(-u+q*s.regY-e)],k+=p)}return{w:k,h:j}};b._endBuild=function(){this.spriteSheet=new createjs.SpriteSheet(this._data); +this._data=null;this.progress=1;this.onComplete&&this.onComplete(this);this.dispatchEvent("complete")};b._run=function(){for(var a=50*Math.max(0.01,Math.min(0.99,this.timeSlice||0.3)),b=(new Date).getTime()+a,c=!1;b>(new Date).getTime();)if(!this._drawNext()){c=!0;break}if(c)this._endBuild();else{var d=this;this._timerID=setTimeout(function(){d._run()},50-a)}a=this.progress=this._index/this._frames.length;this.onProgress&&this.onProgress(this,a);this.dispatchEvent({type:"progress",progress:a})};b._drawNext= +function(){var a=this._frames[this._index],b=a.scale*this._scale,c=a.rect,d=a.sourceRect,e=this._data.images[a.img].getContext("2d");a.funct&&a.funct.apply(a.scope,a.params);e.save();e.beginPath();e.rect(c.x,c.y,c.width,c.height);e.clip();e.translate(Math.ceil(c.x-d.x*b),Math.ceil(c.y-d.y*b));e.scale(b,b);a.source.draw(e);e.restore();return++this._index= 1.0.0' +}; + +Handlebars.helpers = {}; +Handlebars.partials = {}; + +var toString = Object.prototype.toString, + functionType = '[object Function]', + objectType = '[object Object]'; + +Handlebars.registerHelper = function(name, fn, inverse) { + if (toString.call(name) === objectType) { + if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } + Handlebars.Utils.extend(this.helpers, name); + } else { + if (inverse) { fn.not = inverse; } + this.helpers[name] = fn; + } +}; + +Handlebars.registerPartial = function(name, str) { + if (toString.call(name) === objectType) { + Handlebars.Utils.extend(this.partials, name); + } else { + this.partials[name] = str; + } +}; + +Handlebars.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; + } else { + throw new Error("Missing helper: '" + arg + "'"); + } +}); + +Handlebars.registerHelper('blockHelperMissing', function(context, options) { + var inverse = options.inverse || function() {}, fn = options.fn; + + var type = toString.call(context); + + if(type === functionType) { context = context.call(this); } + + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if(type === "[object Array]") { + if(context.length > 0) { + return Handlebars.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + return fn(context); + } +}); + +Handlebars.K = function() {}; + +Handlebars.createFrame = Object.create || function(object) { + Handlebars.K.prototype = object; + var obj = new Handlebars.K(); + Handlebars.K.prototype = null; + return obj; +}; + +Handlebars.logger = { + DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, + + methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, + + // can be overridden in the host environment + log: function(level, obj) { + if (Handlebars.logger.level <= level) { + var method = Handlebars.logger.methodMap[level]; + if (typeof console !== 'undefined' && console[method]) { + console[method].call(console, obj); + } + } + } +}; + +Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; + +Handlebars.registerHelper('each', function(context, options) { + var fn = options.fn, inverse = options.inverse; + var i = 0, ret = "", data; + + var type = toString.call(context); + if(type === functionType) { context = context.call(this); } + + if (options.data) { + data = Handlebars.createFrame(options.data); + } + + if(context && typeof context === 'object') { + if(context instanceof Array){ + for(var j = context.length; i 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; +} +}; +/* Jison generated lexer */ +var lexer = (function(){ +var lexer = ({EOF:1, +parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, +setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + if (this.options.ranges) this.yylloc.range = [0,0]; + this.offset = 0; + return this; + }, +input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, +unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length-len-1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length-1); + this.matched = this.matched.substr(0, this.matched.length-1); + + if (lines.length-1) this.yylineno -= lines.length-1; + var r = this.yylloc.range; + + this.yylloc = {first_line: this.yylloc.first_line, + last_line: this.yylineno+1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: + this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, +more:function () { + this._more = true; + return this; + }, +less:function (n) { + this.unput(this.match.slice(n)); + }, +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + tempMatch, + index, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); + if (this.done && this._input) this.done = false; + if (token) return token; + else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, +lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, +begin:function begin(condition) { + this.conditionStack.push(condition); + }, +popState:function popState() { + return this.conditionStack.pop(); + }, +_currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, +topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, +pushState:function begin(condition) { + this.begin(condition); + }}); +lexer.options = {}; +lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + +var YYSTATE=YY_START +switch($avoiding_name_collisions) { +case 0: yy_.yytext = "\\"; return 14; +break; +case 1: + if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); + if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); + if(yy_.yytext) return 14; + +break; +case 2: return 14; +break; +case 3: + if(yy_.yytext.slice(-1) !== "\\") this.popState(); + if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); + return 14; + +break; +case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; +break; +case 5: return 25; +break; +case 6: return 16; +break; +case 7: return 20; +break; +case 8: return 19; +break; +case 9: return 19; +break; +case 10: return 23; +break; +case 11: return 22; +break; +case 12: this.popState(); this.begin('com'); +break; +case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; +break; +case 14: return 22; +break; +case 15: return 37; +break; +case 16: return 36; +break; +case 17: return 36; +break; +case 18: return 40; +break; +case 19: /*ignore whitespace*/ +break; +case 20: this.popState(); return 24; +break; +case 21: this.popState(); return 18; +break; +case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 31; +break; +case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 31; +break; +case 24: return 38; +break; +case 25: return 33; +break; +case 26: return 33; +break; +case 27: return 32; +break; +case 28: return 36; +break; +case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 36; +break; +case 30: return 'INVALID'; +break; +case 31: return 5; +break; +} +}; +lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; +lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"INITIAL":{"rules":[0,1,2,31],"inclusive":true}}; +return lexer;})() +parser.lexer = lexer; +function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; +return new Parser; +})();; +// lib/handlebars/compiler/base.js + +Handlebars.Parser = handlebars; + +Handlebars.parse = function(input) { + + // Just return if an already-compile AST was passed in. + if(input.constructor === Handlebars.AST.ProgramNode) { return input; } + + Handlebars.Parser.yy = Handlebars.AST; + return Handlebars.Parser.parse(input); +}; +; +// lib/handlebars/compiler/ast.js +Handlebars.AST = {}; + +Handlebars.AST.ProgramNode = function(statements, inverse) { + this.type = "program"; + this.statements = statements; + if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } +}; + +Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { + this.type = "mustache"; + this.escaped = !unescaped; + this.hash = hash; + + var id = this.id = rawParams[0]; + var params = this.params = rawParams.slice(1); + + // a mustache is an eligible helper if: + // * its id is simple (a single part, not `this` or `..`) + var eligibleHelper = this.eligibleHelper = id.isSimple; + + // a mustache is definitely a helper if: + // * it is an eligible helper, and + // * it has at least one parameter or hash segment + this.isHelper = eligibleHelper && (params.length || hash); + + // if a mustache is an eligible helper but not a definite + // helper, it is ambiguous, and will be resolved in a later + // pass or at runtime. +}; + +Handlebars.AST.PartialNode = function(partialName, context) { + this.type = "partial"; + this.partialName = partialName; + this.context = context; +}; + +Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { + var verifyMatch = function(open, close) { + if(open.original !== close.original) { + throw new Handlebars.Exception(open.original + " doesn't match " + close.original); + } + }; + + verifyMatch(mustache.id, close); + this.type = "block"; + this.mustache = mustache; + this.program = program; + this.inverse = inverse; + + if (this.inverse && !this.program) { + this.isInverse = true; + } +}; + +Handlebars.AST.ContentNode = function(string) { + this.type = "content"; + this.string = string; +}; + +Handlebars.AST.HashNode = function(pairs) { + this.type = "hash"; + this.pairs = pairs; +}; + +Handlebars.AST.IdNode = function(parts) { + this.type = "ID"; + + var original = "", + dig = [], + depth = 0; + + for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + original); } + else if (part === "..") { depth++; } + else { this.isScoped = true; } + } + else { dig.push(part); } + } + + this.original = original; + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; + + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; + + this.stringModeValue = this.string; +}; + +Handlebars.AST.PartialNameNode = function(name) { + this.type = "PARTIAL_NAME"; + this.name = name.original; +}; + +Handlebars.AST.DataNode = function(id) { + this.type = "DATA"; + this.id = id; +}; + +Handlebars.AST.StringNode = function(string) { + this.type = "STRING"; + this.original = + this.string = + this.stringModeValue = string; +}; + +Handlebars.AST.IntegerNode = function(integer) { + this.type = "INTEGER"; + this.original = + this.integer = integer; + this.stringModeValue = Number(integer); +}; + +Handlebars.AST.BooleanNode = function(bool) { + this.type = "BOOLEAN"; + this.bool = bool; + this.stringModeValue = bool === "true"; +}; + +Handlebars.AST.CommentNode = function(comment) { + this.type = "comment"; + this.comment = comment; +}; +; +// lib/handlebars/utils.js + +var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + +Handlebars.Exception = function(message) { + var tmp = Error.prototype.constructor.apply(this, arguments); + + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; + } +}; +Handlebars.Exception.prototype = new Error(); + +// Build out our basic SafeString type +Handlebars.SafeString = function(string) { + this.string = string; +}; +Handlebars.SafeString.prototype.toString = function() { + return this.string.toString(); +}; + +var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" +}; + +var badChars = /[&<>"'`]/g; +var possible = /[&<>"'`]/; + +var escapeChar = function(chr) { + return escape[chr] || "&"; +}; + +Handlebars.Utils = { + extend: function(obj, value) { + for(var key in value) { + if(value.hasOwnProperty(key)) { + obj[key] = value[key]; + } + } + }, + + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = string.toString(); + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, + + isEmpty: function(value) { + if (!value && value !== 0) { + return true; + } else if(toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; + } + } +}; +; +// lib/handlebars/compiler/compiler.js + +/*jshint eqnull:true*/ +var Compiler = Handlebars.Compiler = function() {}; +var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; + +// the foundHelper register will disambiguate helper lookup from finding a +// function in a context. This is necessary for mustache compatibility, which +// requires that context functions in blocks are evaluated by blockHelperMissing, +// and then proceed as if the resulting value was provided to blockHelperMissing. + +Compiler.prototype = { + compiler: Compiler, + + disassemble: function() { + var opcodes = this.opcodes, opcode, out = [], params, param; + + for (var i=0, l=opcodes.length; i 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } + + // Generate minimizer alias mappings + if (!this.isChild) { + for (var alias in this.context.aliases) { + if (this.context.aliases.hasOwnProperty(alias)) { + this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; + } + } + } + + if (this.source[1]) { + this.source[1] = "var " + this.source[1].substring(2) + ";"; + } + + // Merge children + if (!this.isChild) { + this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; + } + + if (!this.environment.isSimple) { + this.source.push("return buffer;"); + } + + var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + + for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); + } + } + } + }, + isInline: function() { + return this.inlineStack.length; + }, + + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + this.stackSlot--; + } + return item; + } + }, + + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + return item; + } + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 + .replace(/\u2029/g, '\\u2029') + '"'; + }, + + setupHelper: function(paramSize, name, missingParams) { + var params = []; + this.setupParams(paramSize, params, missingParams); + var foundHelper = this.nameLookup('helpers', name, 'helper'); + + return { + params: params, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, + + // the params and contexts arguments are passed in arrays + // to fill in + setupParams: function(paramSize, params, useRegister) { + var options = [], contexts = [], types = [], param, inverse, program; + + options.push("hash:" + this.popStack()); + + inverse = this.popStack(); + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; + } + + if (!inverse) { + this.context.aliases.self = "this"; + inverse = "self.noop"; + } + + options.push("inverse:" + inverse); + options.push("fn:" + program); + } + + for(var i=0; i -1; i -= 1) { + if (ary[i] && func(ary[i], i, ary)) { + break; + } + } + } + } + + function hasProp(obj, prop) { + return hasOwn.call(obj, prop); + } + + function getOwn(obj, prop) { + return hasProp(obj, prop) && obj[prop]; + } + + /** + * Cycles over properties in an object and calls a function for each + * property value. If the function returns a truthy value, then the + * iteration is stopped. + */ + function eachProp(obj, func) { + var prop; + for (prop in obj) { + if (hasProp(obj, prop)) { + if (func(obj[prop], prop)) { + break; + } + } + } + } + + /** + * Simple function to mix in properties from source into target, + * but only if target does not already have a property of the same name. + */ + function mixin(target, source, force, deepStringMixin) { + if (source) { + eachProp(source, function (value, prop) { + if (force || !hasProp(target, prop)) { + if (deepStringMixin && typeof value !== 'string') { + if (!target[prop]) { + target[prop] = {}; + } + mixin(target[prop], value, force, deepStringMixin); + } else { + target[prop] = value; + } + } + }); + } + return target; + } + + //Similar to Function.prototype.bind, but the 'this' object is specified + //first, since it is easier to read/figure out what 'this' will be. + function bind(obj, fn) { + return function () { + return fn.apply(obj, arguments); + }; + } + + function scripts() { + return document.getElementsByTagName('script'); + } + + //Allow getting a global that expressed in + //dot notation, like 'a.b.c'. + function getGlobal(value) { + if (!value) { + return value; + } + var g = global; + each(value.split('.'), function (part) { + g = g[part]; + }); + return g; + } + + /** + * Constructs an error with a pointer to an URL with more information. + * @param {String} id the error ID that maps to an ID on a web page. + * @param {String} message human readable error. + * @param {Error} [err] the original error, if there is one. + * + * @returns {Error} + */ + function makeError(id, msg, err, requireModules) { + var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id); + e.requireType = id; + e.requireModules = requireModules; + if (err) { + e.originalError = err; + } + return e; + } + + if (typeof define !== 'undefined') { + //If a define is already in play via another AMD loader, + //do not overwrite. + return; + } + + if (typeof requirejs !== 'undefined') { + if (isFunction(requirejs)) { + //Do not overwrite and existing requirejs instance. + return; + } + cfg = requirejs; + requirejs = undefined; + } + + //Allow for a require config object + if (typeof require !== 'undefined' && !isFunction(require)) { + //assume it is a config object. + cfg = require; + require = undefined; + } + + function newContext(contextName) { + var inCheckLoaded, Module, context, handlers, + checkLoadedTimeoutId, + config = { + waitSeconds: 7, + baseUrl: './', + paths: {}, + pkgs: {}, + shim: {}, + map: {}, + config: {} + }, + registry = {}, + undefEvents = {}, + defQueue = [], + defined = {}, + urlFetched = {}, + requireCounter = 1, + unnormalizedCounter = 1; + + /** + * Trims the . and .. from an array of path segments. + * It will keep a leading path segment if a .. will become + * the first path segment, to help with module name lookups, + * which act like paths, but can be remapped. But the end result, + * all paths that use this function should look normalized. + * NOTE: this method MODIFIES the input array. + * @param {Array} ary the array of path segments. + */ + function trimDots(ary) { + var i, part; + for (i = 0; ary[i]; i += 1) { + part = ary[i]; + if (part === '.') { + ary.splice(i, 1); + i -= 1; + } else if (part === '..') { + if (i === 1 && (ary[2] === '..' || ary[0] === '..')) { + //End of the line. Keep at least one non-dot + //path segment at the front so it can be mapped + //correctly to disk. Otherwise, there is likely + //no path mapping for a path starting with '..'. + //This can still fail, but catches the most reasonable + //uses of .. + break; + } else if (i > 0) { + ary.splice(i - 1, 2); + i -= 2; + } + } + } + } + + /** + * Given a relative module name, like ./something, normalize it to + * a real name that can be mapped to a path. + * @param {String} name the relative name + * @param {String} baseName a real name that the name arg is relative + * to. + * @param {Boolean} applyMap apply the map config to the value. Should + * only be done if this normalization is for a dependency ID. + * @returns {String} normalized name + */ + function normalize(name, baseName, applyMap) { + var pkgName, pkgConfig, mapValue, nameParts, i, j, nameSegment, + foundMap, foundI, foundStarMap, starI, + baseParts = baseName && baseName.split('/'), + normalizedBaseParts = baseParts, + map = config.map, + starMap = map && map['*']; + + //Adjust any relative paths. + if (name && name.charAt(0) === '.') { + //If have a base name, try to normalize against it, + //otherwise, assume it is a top-level require that will + //be relative to baseUrl in the end. + if (baseName) { + if (getOwn(config.pkgs, baseName)) { + //If the baseName is a package name, then just treat it as one + //name to concat the name with. + normalizedBaseParts = baseParts = [baseName]; + } else { + //Convert baseName to array, and lop off the last part, + //so that . matches that 'directory' and not name of the baseName's + //module. For instance, baseName of 'one/two/three', maps to + //'one/two/three.js', but we want the directory, 'one/two' for + //this normalization. + normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); + } + + name = normalizedBaseParts.concat(name.split('/')); + trimDots(name); + + //Some use of packages may use a . path to reference the + //'main' module name, so normalize for that. + pkgConfig = getOwn(config.pkgs, (pkgName = name[0])); + name = name.join('/'); + if (pkgConfig && name === pkgName + '/' + pkgConfig.main) { + name = pkgName; + } + } else if (name.indexOf('./') === 0) { + // No baseName, so this is ID is resolved relative + // to baseUrl, pull off the leading dot. + name = name.substring(2); + } + } + + //Apply map config if available. + if (applyMap && (baseParts || starMap) && map) { + nameParts = name.split('/'); + + for (i = nameParts.length; i > 0; i -= 1) { + nameSegment = nameParts.slice(0, i).join('/'); + + if (baseParts) { + //Find the longest baseName segment match in the config. + //So, do joins on the biggest to smallest lengths of baseParts. + for (j = baseParts.length; j > 0; j -= 1) { + mapValue = getOwn(map, baseParts.slice(0, j).join('/')); + + //baseName segment has config, find if it has one for + //this name. + if (mapValue) { + mapValue = getOwn(mapValue, nameSegment); + if (mapValue) { + //Match, update name to the new value. + foundMap = mapValue; + foundI = i; + break; + } + } + } + } + + if (foundMap) { + break; + } + + //Check for a star map match, but just hold on to it, + //if there is a shorter segment match later in a matching + //config, then favor over this star map. + if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) { + foundStarMap = getOwn(starMap, nameSegment); + starI = i; + } + } + + if (!foundMap && foundStarMap) { + foundMap = foundStarMap; + foundI = starI; + } + + if (foundMap) { + nameParts.splice(0, foundI, foundMap); + name = nameParts.join('/'); + } + } + + return name; + } + + function removeScript(name) { + if (isBrowser) { + each(scripts(), function (scriptNode) { + if (scriptNode.getAttribute('data-requiremodule') === name && + scriptNode.getAttribute('data-requirecontext') === context.contextName) { + scriptNode.parentNode.removeChild(scriptNode); + return true; + } + }); + } + } + + function hasPathFallback(id) { + var pathConfig = getOwn(config.paths, id); + if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) { + removeScript(id); + //Pop off the first array value, since it failed, and + //retry + pathConfig.shift(); + context.require.undef(id); + context.require([id]); + return true; + } + } + + //Turns a plugin!resource to [plugin, resource] + //with the plugin being undefined if the name + //did not have a plugin prefix. + function splitPrefix(name) { + var prefix, + index = name ? name.indexOf('!') : -1; + if (index > -1) { + prefix = name.substring(0, index); + name = name.substring(index + 1, name.length); + } + return [prefix, name]; + } + + /** + * Creates a module mapping that includes plugin prefix, module + * name, and path. If parentModuleMap is provided it will + * also normalize the name via require.normalize() + * + * @param {String} name the module name + * @param {String} [parentModuleMap] parent module map + * for the module name, used to resolve relative names. + * @param {Boolean} isNormalized: is the ID already normalized. + * This is true if this call is done for a define() module ID. + * @param {Boolean} applyMap: apply the map config to the ID. + * Should only be true if this map is for a dependency. + * + * @returns {Object} + */ + function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) { + var url, pluginModule, suffix, nameParts, + prefix = null, + parentName = parentModuleMap ? parentModuleMap.name : null, + originalName = name, + isDefine = true, + normalizedName = ''; + + //If no name, then it means it is a require call, generate an + //internal name. + if (!name) { + isDefine = false; + name = '_@r' + (requireCounter += 1); + } + + nameParts = splitPrefix(name); + prefix = nameParts[0]; + name = nameParts[1]; + + if (prefix) { + prefix = normalize(prefix, parentName, applyMap); + pluginModule = getOwn(defined, prefix); + } + + //Account for relative paths if there is a base name. + if (name) { + if (prefix) { + if (pluginModule && pluginModule.normalize) { + //Plugin is loaded, use its normalize method. + normalizedName = pluginModule.normalize(name, function (name) { + return normalize(name, parentName, applyMap); + }); + } else { + normalizedName = normalize(name, parentName, applyMap); + } + } else { + //A regular module. + normalizedName = normalize(name, parentName, applyMap); + + //Normalized name may be a plugin ID due to map config + //application in normalize. The map config values must + //already be normalized, so do not need to redo that part. + nameParts = splitPrefix(normalizedName); + prefix = nameParts[0]; + normalizedName = nameParts[1]; + isNormalized = true; + + url = context.nameToUrl(normalizedName); + } + } + + //If the id is a plugin id that cannot be determined if it needs + //normalization, stamp it with a unique ID so two matching relative + //ids that may conflict can be separate. + suffix = prefix && !pluginModule && !isNormalized ? + '_unnormalized' + (unnormalizedCounter += 1) : + ''; + + return { + prefix: prefix, + name: normalizedName, + parentMap: parentModuleMap, + unnormalized: !!suffix, + url: url, + originalName: originalName, + isDefine: isDefine, + id: (prefix ? + prefix + '!' + normalizedName : + normalizedName) + suffix + }; + } + + function getModule(depMap) { + var id = depMap.id, + mod = getOwn(registry, id); + + if (!mod) { + mod = registry[id] = new context.Module(depMap); + } + + return mod; + } + + function on(depMap, name, fn) { + var id = depMap.id, + mod = getOwn(registry, id); + + if (hasProp(defined, id) && + (!mod || mod.defineEmitComplete)) { + if (name === 'defined') { + fn(defined[id]); + } + } else { + getModule(depMap).on(name, fn); + } + } + + function onError(err, errback) { + var ids = err.requireModules, + notified = false; + + if (errback) { + errback(err); + } else { + each(ids, function (id) { + var mod = getOwn(registry, id); + if (mod) { + //Set error on module, so it skips timeout checks. + mod.error = err; + if (mod.events.error) { + notified = true; + mod.emit('error', err); + } + } + }); + + if (!notified) { + req.onError(err); + } + } + } + + /** + * Internal method to transfer globalQueue items to this context's + * defQueue. + */ + function takeGlobalQueue() { + //Push all the globalDefQueue items into the context's defQueue + if (globalDefQueue.length) { + //Array splice in the values since the context code has a + //local var ref to defQueue, so cannot just reassign the one + //on context. + apsp.apply(defQueue, + [defQueue.length - 1, 0].concat(globalDefQueue)); + globalDefQueue = []; + } + } + + handlers = { + 'require': function (mod) { + if (mod.require) { + return mod.require; + } else { + return (mod.require = context.makeRequire(mod.map)); + } + }, + 'exports': function (mod) { + mod.usingExports = true; + if (mod.map.isDefine) { + if (mod.exports) { + return mod.exports; + } else { + return (mod.exports = defined[mod.map.id] = {}); + } + } + }, + 'module': function (mod) { + if (mod.module) { + return mod.module; + } else { + return (mod.module = { + id: mod.map.id, + uri: mod.map.url, + config: function () { + return (config.config && getOwn(config.config, mod.map.id)) || {}; + }, + exports: defined[mod.map.id] + }); + } + } + }; + + function cleanRegistry(id) { + //Clean up machinery used for waiting modules. + delete registry[id]; + } + + function breakCycle(mod, traced, processed) { + var id = mod.map.id; + + if (mod.error) { + mod.emit('error', mod.error); + } else { + traced[id] = true; + each(mod.depMaps, function (depMap, i) { + var depId = depMap.id, + dep = getOwn(registry, depId); + + //Only force things that have not completed + //being defined, so still in the registry, + //and only if it has not been matched up + //in the module already. + if (dep && !mod.depMatched[i] && !processed[depId]) { + if (getOwn(traced, depId)) { + mod.defineDep(i, defined[depId]); + mod.check(); //pass false? + } else { + breakCycle(dep, traced, processed); + } + } + }); + processed[id] = true; + } + } + + function checkLoaded() { + var map, modId, err, usingPathFallback, + waitInterval = config.waitSeconds * 1000, + //It is possible to disable the wait interval by using waitSeconds of 0. + expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(), + noLoads = [], + reqCalls = [], + stillLoading = false, + needCycleCheck = true; + + //Do not bother if this call was a result of a cycle break. + if (inCheckLoaded) { + return; + } + + inCheckLoaded = true; + + //Figure out the state of all the modules. + eachProp(registry, function (mod) { + map = mod.map; + modId = map.id; + + //Skip things that are not enabled or in error state. + if (!mod.enabled) { + return; + } + + if (!map.isDefine) { + reqCalls.push(mod); + } + + if (!mod.error) { + //If the module should be executed, and it has not + //been inited and time is up, remember it. + if (!mod.inited && expired) { + if (hasPathFallback(modId)) { + usingPathFallback = true; + stillLoading = true; + } else { + noLoads.push(modId); + removeScript(modId); + } + } else if (!mod.inited && mod.fetched && map.isDefine) { + stillLoading = true; + if (!map.prefix) { + //No reason to keep looking for unfinished + //loading. If the only stillLoading is a + //plugin resource though, keep going, + //because it may be that a plugin resource + //is waiting on a non-plugin cycle. + return (needCycleCheck = false); + } + } + } + }); + + if (expired && noLoads.length) { + //If wait time expired, throw error of unloaded modules. + err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads); + err.contextName = context.contextName; + return onError(err); + } + + //Not expired, check for a cycle. + if (needCycleCheck) { + each(reqCalls, function (mod) { + breakCycle(mod, {}, {}); + }); + } + + //If still waiting on loads, and the waiting load is something + //other than a plugin resource, or there are still outstanding + //scripts, then just try back later. + if ((!expired || usingPathFallback) && stillLoading) { + //Something is still waiting to load. Wait for it, but only + //if a timeout is not already in effect. + if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) { + checkLoadedTimeoutId = setTimeout(function () { + checkLoadedTimeoutId = 0; + checkLoaded(); + }, 50); + } + } + + inCheckLoaded = false; + } + + Module = function (map) { + this.events = getOwn(undefEvents, map.id) || {}; + this.map = map; + this.shim = getOwn(config.shim, map.id); + this.depExports = []; + this.depMaps = []; + this.depMatched = []; + this.pluginMaps = {}; + this.depCount = 0; + + /* this.exports this.factory + this.depMaps = [], + this.enabled, this.fetched + */ + }; + + Module.prototype = { + init: function (depMaps, factory, errback, options) { + options = options || {}; + + //Do not do more inits if already done. Can happen if there + //are multiple define calls for the same module. That is not + //a normal, common case, but it is also not unexpected. + if (this.inited) { + return; + } + + this.factory = factory; + + if (errback) { + //Register for errors on this module. + this.on('error', errback); + } else if (this.events.error) { + //If no errback already, but there are error listeners + //on this module, set up an errback to pass to the deps. + errback = bind(this, function (err) { + this.emit('error', err); + }); + } + + //Do a copy of the dependency array, so that + //source inputs are not modified. For example + //"shim" deps are passed in here directly, and + //doing a direct modification of the depMaps array + //would affect that config. + this.depMaps = depMaps && depMaps.slice(0); + + this.errback = errback; + + //Indicate this module has be initialized + this.inited = true; + + this.ignore = options.ignore; + + //Could have option to init this module in enabled mode, + //or could have been previously marked as enabled. However, + //the dependencies are not known until init is called. So + //if enabled previously, now trigger dependencies as enabled. + if (options.enabled || this.enabled) { + //Enable this module and dependencies. + //Will call this.check() + this.enable(); + } else { + this.check(); + } + }, + + defineDep: function (i, depExports) { + //Because of cycles, defined callback for a given + //export can be called more than once. + if (!this.depMatched[i]) { + this.depMatched[i] = true; + this.depCount -= 1; + this.depExports[i] = depExports; + } + }, + + fetch: function () { + if (this.fetched) { + return; + } + this.fetched = true; + + context.startTime = (new Date()).getTime(); + + var map = this.map; + + //If the manager is for a plugin managed resource, + //ask the plugin to load it now. + if (this.shim) { + context.makeRequire(this.map, { + enableBuildCallback: true + })(this.shim.deps || [], bind(this, function () { + return map.prefix ? this.callPlugin() : this.load(); + })); + } else { + //Regular dependency. + return map.prefix ? this.callPlugin() : this.load(); + } + }, + + load: function () { + var url = this.map.url; + + //Regular dependency. + if (!urlFetched[url]) { + urlFetched[url] = true; + context.load(this.map.id, url); + } + }, + + /** + * Checks is the module is ready to define itself, and if so, + * define it. + */ + check: function () { + if (!this.enabled || this.enabling) { + return; + } + + var err, cjsModule, + id = this.map.id, + depExports = this.depExports, + exports = this.exports, + factory = this.factory; + + if (!this.inited) { + this.fetch(); + } else if (this.error) { + this.emit('error', this.error); + } else if (!this.defining) { + //The factory could trigger another require call + //that would result in checking this module to + //define itself again. If already in the process + //of doing that, skip this work. + this.defining = true; + + if (this.depCount < 1 && !this.defined) { + if (isFunction(factory)) { + //If there is an error listener, favor passing + //to that instead of throwing an error. + if (this.events.error) { + try { + exports = context.execCb(id, factory, depExports, exports); + } catch (e) { + err = e; + } + } else { + exports = context.execCb(id, factory, depExports, exports); + } + + if (this.map.isDefine) { + //If setting exports via 'module' is in play, + //favor that over return value and exports. After that, + //favor a non-undefined return value over exports use. + cjsModule = this.module; + if (cjsModule && + cjsModule.exports !== undefined && + //Make sure it is not already the exports value + cjsModule.exports !== this.exports) { + exports = cjsModule.exports; + } else if (exports === undefined && this.usingExports) { + //exports already set the defined value. + exports = this.exports; + } + } + + if (err) { + err.requireMap = this.map; + err.requireModules = [this.map.id]; + err.requireType = 'define'; + return onError((this.error = err)); + } + + } else { + //Just a literal value + exports = factory; + } + + this.exports = exports; + + if (this.map.isDefine && !this.ignore) { + defined[id] = exports; + + if (req.onResourceLoad) { + req.onResourceLoad(context, this.map, this.depMaps); + } + } + + //Clean up + delete registry[id]; + + this.defined = true; + } + + //Finished the define stage. Allow calling check again + //to allow define notifications below in the case of a + //cycle. + this.defining = false; + + if (this.defined && !this.defineEmitted) { + this.defineEmitted = true; + this.emit('defined', this.exports); + this.defineEmitComplete = true; + } + + } + }, + + callPlugin: function () { + var map = this.map, + id = map.id, + //Map already normalized the prefix. + pluginMap = makeModuleMap(map.prefix); + + //Mark this as a dependency for this plugin, so it + //can be traced for cycles. + this.depMaps.push(pluginMap); + + on(pluginMap, 'defined', bind(this, function (plugin) { + var load, normalizedMap, normalizedMod, + name = this.map.name, + parentName = this.map.parentMap ? this.map.parentMap.name : null, + localRequire = context.makeRequire(map.parentMap, { + enableBuildCallback: true + }); + + //If current map is not normalized, wait for that + //normalized name to load instead of continuing. + if (this.map.unnormalized) { + //Normalize the ID if the plugin allows it. + if (plugin.normalize) { + name = plugin.normalize(name, function (name) { + return normalize(name, parentName, true); + }) || ''; + } + + //prefix and name should already be normalized, no need + //for applying map config again either. + normalizedMap = makeModuleMap(map.prefix + '!' + name, + this.map.parentMap); + on(normalizedMap, + 'defined', bind(this, function (value) { + this.init([], function () { return value; }, null, { + enabled: true, + ignore: true + }); + })); + + normalizedMod = getOwn(registry, normalizedMap.id); + if (normalizedMod) { + //Mark this as a dependency for this plugin, so it + //can be traced for cycles. + this.depMaps.push(normalizedMap); + + if (this.events.error) { + normalizedMod.on('error', bind(this, function (err) { + this.emit('error', err); + })); + } + normalizedMod.enable(); + } + + return; + } + + load = bind(this, function (value) { + this.init([], function () { return value; }, null, { + enabled: true + }); + }); + + load.error = bind(this, function (err) { + this.inited = true; + this.error = err; + err.requireModules = [id]; + + //Remove temp unnormalized modules for this module, + //since they will never be resolved otherwise now. + eachProp(registry, function (mod) { + if (mod.map.id.indexOf(id + '_unnormalized') === 0) { + cleanRegistry(mod.map.id); + } + }); + + onError(err); + }); + + //Allow plugins to load other code without having to know the + //context or how to 'complete' the load. + load.fromText = bind(this, function (text, textAlt) { + /*jslint evil: true */ + var moduleName = map.name, + moduleMap = makeModuleMap(moduleName), + hasInteractive = useInteractive; + + //As of 2.1.0, support just passing the text, to reinforce + //fromText only being called once per resource. Still + //support old style of passing moduleName but discard + //that moduleName in favor of the internal ref. + if (textAlt) { + text = textAlt; + } + + //Turn off interactive script matching for IE for any define + //calls in the text, then turn it back on at the end. + if (hasInteractive) { + useInteractive = false; + } + + //Prime the system by creating a module instance for + //it. + getModule(moduleMap); + + //Transfer any config to this other module. + if (hasProp(config.config, id)) { + config.config[moduleName] = config.config[id]; + } + + try { + req.exec(text); + } catch (e) { + return onError(makeError('fromtexteval', + 'fromText eval for ' + id + + ' failed: ' + e, + e, + [id])); + } + + if (hasInteractive) { + useInteractive = true; + } + + //Mark this as a dependency for the plugin + //resource + this.depMaps.push(moduleMap); + + //Support anonymous modules. + context.completeLoad(moduleName); + + //Bind the value of that module to the value for this + //resource ID. + localRequire([moduleName], load); + }); + + //Use parentName here since the plugin's name is not reliable, + //could be some weird string with no path that actually wants to + //reference the parentName's path. + plugin.load(map.name, localRequire, load, config); + })); + + context.enable(pluginMap, this); + this.pluginMaps[pluginMap.id] = pluginMap; + }, + + enable: function () { + this.enabled = true; + + //Set flag mentioning that the module is enabling, + //so that immediate calls to the defined callbacks + //for dependencies do not trigger inadvertent load + //with the depCount still being zero. + this.enabling = true; + + //Enable each dependency + each(this.depMaps, bind(this, function (depMap, i) { + var id, mod, handler; + + if (typeof depMap === 'string') { + //Dependency needs to be converted to a depMap + //and wired up to this module. + depMap = makeModuleMap(depMap, + (this.map.isDefine ? this.map : this.map.parentMap), + false, + !this.skipMap); + this.depMaps[i] = depMap; + + handler = getOwn(handlers, depMap.id); + + if (handler) { + this.depExports[i] = handler(this); + return; + } + + this.depCount += 1; + + on(depMap, 'defined', bind(this, function (depExports) { + this.defineDep(i, depExports); + this.check(); + })); + + if (this.errback) { + on(depMap, 'error', this.errback); + } + } + + id = depMap.id; + mod = registry[id]; + + //Skip special modules like 'require', 'exports', 'module' + //Also, don't call enable if it is already enabled, + //important in circular dependency cases. + if (!hasProp(handlers, id) && mod && !mod.enabled) { + context.enable(depMap, this); + } + })); + + //Enable each plugin that is used in + //a dependency + eachProp(this.pluginMaps, bind(this, function (pluginMap) { + var mod = getOwn(registry, pluginMap.id); + if (mod && !mod.enabled) { + context.enable(pluginMap, this); + } + })); + + this.enabling = false; + + this.check(); + }, + + on: function (name, cb) { + var cbs = this.events[name]; + if (!cbs) { + cbs = this.events[name] = []; + } + cbs.push(cb); + }, + + emit: function (name, evt) { + each(this.events[name], function (cb) { + cb(evt); + }); + if (name === 'error') { + //Now that the error handler was triggered, remove + //the listeners, since this broken Module instance + //can stay around for a while in the registry. + delete this.events[name]; + } + } + }; + + function callGetModule(args) { + //Skip modules already defined. + if (!hasProp(defined, args[0])) { + getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]); + } + } + + function removeListener(node, func, name, ieName) { + //Favor detachEvent because of IE9 + //issue, see attachEvent/addEventListener comment elsewhere + //in this file. + if (node.detachEvent && !isOpera) { + //Probably IE. If not it will throw an error, which will be + //useful to know. + if (ieName) { + node.detachEvent(ieName, func); + } + } else { + node.removeEventListener(name, func, false); + } + } + + /** + * Given an event from a script node, get the requirejs info from it, + * and then removes the event listeners on the node. + * @param {Event} evt + * @returns {Object} + */ + function getScriptData(evt) { + //Using currentTarget instead of target for Firefox 2.0's sake. Not + //all old browsers will be supported, but this one was easy enough + //to support and still makes sense. + var node = evt.currentTarget || evt.srcElement; + + //Remove the listeners once here. + removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange'); + removeListener(node, context.onScriptError, 'error'); + + return { + node: node, + id: node && node.getAttribute('data-requiremodule') + }; + } + + function intakeDefines() { + var args; + + //Any defined modules in the global queue, intake them now. + takeGlobalQueue(); + + //Make sure any remaining defQueue items get properly processed. + while (defQueue.length) { + args = defQueue.shift(); + if (args[0] === null) { + return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1])); + } else { + //args are id, deps, factory. Should be normalized by the + //define() function. + callGetModule(args); + } + } + } + + context = { + config: config, + contextName: contextName, + registry: registry, + defined: defined, + urlFetched: urlFetched, + defQueue: defQueue, + Module: Module, + makeModuleMap: makeModuleMap, + nextTick: req.nextTick, + + /** + * Set a configuration for the context. + * @param {Object} cfg config object to integrate. + */ + configure: function (cfg) { + //Make sure the baseUrl ends in a slash. + if (cfg.baseUrl) { + if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') { + cfg.baseUrl += '/'; + } + } + + //Save off the paths and packages since they require special processing, + //they are additive. + var pkgs = config.pkgs, + shim = config.shim, + objs = { + paths: true, + config: true, + map: true + }; + + eachProp(cfg, function (value, prop) { + if (objs[prop]) { + if (prop === 'map') { + mixin(config[prop], value, true, true); + } else { + mixin(config[prop], value, true); + } + } else { + config[prop] = value; + } + }); + + //Merge shim + if (cfg.shim) { + eachProp(cfg.shim, function (value, id) { + //Normalize the structure + if (isArray(value)) { + value = { + deps: value + }; + } + if ((value.exports || value.init) && !value.exportsFn) { + value.exportsFn = context.makeShimExports(value); + } + shim[id] = value; + }); + config.shim = shim; + } + + //Adjust packages if necessary. + if (cfg.packages) { + each(cfg.packages, function (pkgObj) { + var location; + + pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj; + location = pkgObj.location; + + //Create a brand new object on pkgs, since currentPackages can + //be passed in again, and config.pkgs is the internal transformed + //state for all package configs. + pkgs[pkgObj.name] = { + name: pkgObj.name, + location: location || pkgObj.name, + //Remove leading dot in main, so main paths are normalized, + //and remove any trailing .js, since different package + //envs have different conventions: some use a module name, + //some use a file name. + main: (pkgObj.main || 'main') + .replace(currDirRegExp, '') + .replace(jsSuffixRegExp, '') + }; + }); + + //Done with modifications, assing packages back to context config + config.pkgs = pkgs; + } + + //If there are any "waiting to execute" modules in the registry, + //update the maps for them, since their info, like URLs to load, + //may have changed. + eachProp(registry, function (mod, id) { + //If module already has init called, since it is too + //late to modify them, and ignore unnormalized ones + //since they are transient. + if (!mod.inited && !mod.map.unnormalized) { + mod.map = makeModuleMap(id); + } + }); + + //If a deps array or a config callback is specified, then call + //require with those args. This is useful when require is defined as a + //config object before require.js is loaded. + if (cfg.deps || cfg.callback) { + context.require(cfg.deps || [], cfg.callback); + } + }, + + makeShimExports: function (value) { + function fn() { + var ret; + if (value.init) { + ret = value.init.apply(global, arguments); + } + return ret || (value.exports && getGlobal(value.exports)); + } + return fn; + }, + + makeRequire: function (relMap, options) { + options = options || {}; + + function localRequire(deps, callback, errback) { + var id, map, requireMod; + + if (options.enableBuildCallback && callback && isFunction(callback)) { + callback.__requireJsBuild = true; + } + + if (typeof deps === 'string') { + if (isFunction(callback)) { + //Invalid call + return onError(makeError('requireargs', 'Invalid require call'), errback); + } + + //If require|exports|module are requested, get the + //value for them from the special handlers. Caveat: + //this only works while module is being defined. + if (relMap && hasProp(handlers, deps)) { + return handlers[deps](registry[relMap.id]); + } + + //Synchronous access to one module. If require.get is + //available (as in the Node adapter), prefer that. + if (req.get) { + return req.get(context, deps, relMap); + } + + //Normalize module name, if it contains . or .. + map = makeModuleMap(deps, relMap, false, true); + id = map.id; + + if (!hasProp(defined, id)) { + return onError(makeError('notloaded', 'Module name "' + + id + + '" has not been loaded yet for context: ' + + contextName + + (relMap ? '' : '. Use require([])'))); + } + return defined[id]; + } + + //Grab defines waiting in the global queue. + intakeDefines(); + + //Mark all the dependencies as needing to be loaded. + context.nextTick(function () { + //Some defines could have been added since the + //require call, collect them. + intakeDefines(); + + requireMod = getModule(makeModuleMap(null, relMap)); + + //Store if map config should be applied to this require + //call for dependencies. + requireMod.skipMap = options.skipMap; + + requireMod.init(deps, callback, errback, { + enabled: true + }); + + checkLoaded(); + }); + + return localRequire; + } + + mixin(localRequire, { + isBrowser: isBrowser, + + /** + * Converts a module name + .extension into an URL path. + * *Requires* the use of a module name. It does not support using + * plain URLs like nameToUrl. + */ + toUrl: function (moduleNamePlusExt) { + var ext, url, + index = moduleNamePlusExt.lastIndexOf('.'), + segment = moduleNamePlusExt.split('/')[0], + isRelative = segment === '.' || segment === '..'; + + //Have a file extension alias, and it is not the + //dots from a relative path. + if (index !== -1 && (!isRelative || index > 1)) { + ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length); + moduleNamePlusExt = moduleNamePlusExt.substring(0, index); + } + + url = context.nameToUrl(normalize(moduleNamePlusExt, + relMap && relMap.id, true), ext || '.fake'); + return ext ? url : url.substring(0, url.length - 5); + }, + + defined: function (id) { + return hasProp(defined, makeModuleMap(id, relMap, false, true).id); + }, + + specified: function (id) { + id = makeModuleMap(id, relMap, false, true).id; + return hasProp(defined, id) || hasProp(registry, id); + } + }); + + //Only allow undef on top level require calls + if (!relMap) { + localRequire.undef = function (id) { + //Bind any waiting define() calls to this context, + //fix for #408 + takeGlobalQueue(); + + var map = makeModuleMap(id, relMap, true), + mod = getOwn(registry, id); + + delete defined[id]; + delete urlFetched[map.url]; + delete undefEvents[id]; + + if (mod) { + //Hold on to listeners in case the + //module will be attempted to be reloaded + //using a different config. + if (mod.events.defined) { + undefEvents[id] = mod.events; + } + + cleanRegistry(id); + } + }; + } + + return localRequire; + }, + + /** + * Called to enable a module if it is still in the registry + * awaiting enablement. A second arg, parent, the parent module, + * is passed in for context, when this method is overriden by + * the optimizer. Not shown here to keep code compact. + */ + enable: function (depMap) { + var mod = getOwn(registry, depMap.id); + if (mod) { + getModule(depMap).enable(); + } + }, + + /** + * Internal method used by environment adapters to complete a load event. + * A load event could be a script load or just a load pass from a synchronous + * load call. + * @param {String} moduleName the name of the module to potentially complete. + */ + completeLoad: function (moduleName) { + var found, args, mod, + shim = getOwn(config.shim, moduleName) || {}, + shExports = shim.exports; + + takeGlobalQueue(); + + while (defQueue.length) { + args = defQueue.shift(); + if (args[0] === null) { + args[0] = moduleName; + //If already found an anonymous module and bound it + //to this name, then this is some other anon module + //waiting for its completeLoad to fire. + if (found) { + break; + } + found = true; + } else if (args[0] === moduleName) { + //Found matching define call for this script! + found = true; + } + + callGetModule(args); + } + + //Do this after the cycle of callGetModule in case the result + //of those calls/init calls changes the registry. + mod = getOwn(registry, moduleName); + + if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) { + if (config.enforceDefine && (!shExports || !getGlobal(shExports))) { + if (hasPathFallback(moduleName)) { + return; + } else { + return onError(makeError('nodefine', + 'No define call for ' + moduleName, + null, + [moduleName])); + } + } else { + //A script that does not call define(), so just simulate + //the call for it. + callGetModule([moduleName, (shim.deps || []), shim.exportsFn]); + } + } + + checkLoaded(); + }, + + /** + * Converts a module name to a file path. Supports cases where + * moduleName may actually be just an URL. + * Note that it **does not** call normalize on the moduleName, + * it is assumed to have already been normalized. This is an + * internal API, not a public one. Use toUrl for the public API. + */ + nameToUrl: function (moduleName, ext) { + var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url, + parentPath; + + //If a colon is in the URL, it indicates a protocol is used and it is just + //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?) + //or ends with .js, then assume the user meant to use an url and not a module id. + //The slash is important for protocol-less URLs as well as full paths. + if (req.jsExtRegExp.test(moduleName)) { + //Just a plain path, not module name lookup, so just return it. + //Add extension if it is included. This is a bit wonky, only non-.js things pass + //an extension, this method probably needs to be reworked. + url = moduleName + (ext || ''); + } else { + //A module that needs to be converted to a path. + paths = config.paths; + pkgs = config.pkgs; + + syms = moduleName.split('/'); + //For each module name segment, see if there is a path + //registered for it. Start with most specific name + //and work up from it. + for (i = syms.length; i > 0; i -= 1) { + parentModule = syms.slice(0, i).join('/'); + pkg = getOwn(pkgs, parentModule); + parentPath = getOwn(paths, parentModule); + if (parentPath) { + //If an array, it means there are a few choices, + //Choose the one that is desired + if (isArray(parentPath)) { + parentPath = parentPath[0]; + } + syms.splice(0, i, parentPath); + break; + } else if (pkg) { + //If module name is just the package name, then looking + //for the main module. + if (moduleName === pkg.name) { + pkgPath = pkg.location + '/' + pkg.main; + } else { + pkgPath = pkg.location; + } + syms.splice(0, i, pkgPath); + break; + } + } + + //Join the path parts together, then figure out if baseUrl is needed. + url = syms.join('/'); + url += (ext || (/\?/.test(url) ? '' : '.js')); + url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url; + } + + return config.urlArgs ? url + + ((url.indexOf('?') === -1 ? '?' : '&') + + config.urlArgs) : url; + }, + + //Delegates to req.load. Broken out as a separate function to + //allow overriding in the optimizer. + load: function (id, url) { + req.load(context, id, url); + }, + + /** + * Executes a module callack function. Broken out as a separate function + * solely to allow the build system to sequence the files in the built + * layer in the right sequence. + * + * @private + */ + execCb: function (name, callback, args, exports) { + return callback.apply(exports, args); + }, + + /** + * callback for script loads, used to check status of loading. + * + * @param {Event} evt the event from the browser for the script + * that was loaded. + */ + onScriptLoad: function (evt) { + //Using currentTarget instead of target for Firefox 2.0's sake. Not + //all old browsers will be supported, but this one was easy enough + //to support and still makes sense. + if (evt.type === 'load' || + (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) { + //Reset interactive script so a script node is not held onto for + //to long. + interactiveScript = null; + + //Pull out the name of the module and the context. + var data = getScriptData(evt); + context.completeLoad(data.id); + } + }, + + /** + * Callback for script errors. + */ + onScriptError: function (evt) { + var data = getScriptData(evt); + if (!hasPathFallback(data.id)) { + return onError(makeError('scripterror', 'Script error', evt, [data.id])); + } + } + }; + + context.require = context.makeRequire(); + return context; + } + + /** + * Main entry point. + * + * If the only argument to require is a string, then the module that + * is represented by that string is fetched for the appropriate context. + * + * If the first argument is an array, then it will be treated as an array + * of dependency string names to fetch. An optional function callback can + * be specified to execute when all of those dependencies are available. + * + * Make a local req variable to help Caja compliance (it assumes things + * on a require that are not standardized), and to give a short + * name for minification/local scope use. + */ + req = requirejs = function (deps, callback, errback, optional) { + + //Find the right context, use default + var context, config, + contextName = defContextName; + + // Determine if have config object in the call. + if (!isArray(deps) && typeof deps !== 'string') { + // deps is a config object + config = deps; + if (isArray(callback)) { + // Adjust args if there are dependencies + deps = callback; + callback = errback; + errback = optional; + } else { + deps = []; + } + } + + if (config && config.context) { + contextName = config.context; + } + + context = getOwn(contexts, contextName); + if (!context) { + context = contexts[contextName] = req.s.newContext(contextName); + } + + if (config) { + context.configure(config); + } + + return context.require(deps, callback, errback); + }; + + /** + * Support require.config() to make it easier to cooperate with other + * AMD loaders on globally agreed names. + */ + req.config = function (config) { + return req(config); + }; + + /** + * Execute something after the current tick + * of the event loop. Override for other envs + * that have a better solution than setTimeout. + * @param {Function} fn function to execute later. + */ + req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) { + setTimeout(fn, 4); + } : function (fn) { fn(); }; + + /** + * Export require as a global, but only if it does not already exist. + */ + if (!require) { + require = req; + } + + req.version = version; + + //Used to filter out dependencies that are already paths. + req.jsExtRegExp = /^\/|:|\?|\.js$/; + req.isBrowser = isBrowser; + s = req.s = { + contexts: contexts, + newContext: newContext + }; + + //Create default context. + req({}); + + //Exports some context-sensitive methods on global require. + each([ + 'toUrl', + 'undef', + 'defined', + 'specified' + ], function (prop) { + //Reference from contexts instead of early binding to default context, + //so that during builds, the latest instance of the default context + //with its config gets used. + req[prop] = function () { + var ctx = contexts[defContextName]; + return ctx.require[prop].apply(ctx, arguments); + }; + }); + + if (isBrowser) { + head = s.head = document.getElementsByTagName('head')[0]; + //If BASE tag is in play, using appendChild is a problem for IE6. + //When that browser dies, this can be removed. Details in this jQuery bug: + //http://dev.jquery.com/ticket/2709 + baseElement = document.getElementsByTagName('base')[0]; + if (baseElement) { + head = s.head = baseElement.parentNode; + } + } + + /** + * Any errors that require explicitly generates will be passed to this + * function. Intercept/override it if you want custom error handling. + * @param {Error} err the error object. + */ + req.onError = function (err) { + throw err; + }; + + /** + * Does the request to load a module for the browser case. + * Make this a separate function to allow other environments + * to override it. + * + * @param {Object} context the require context to find state. + * @param {String} moduleName the name of the module. + * @param {Object} url the URL to the module. + */ + req.load = function (context, moduleName, url) { + var config = (context && context.config) || {}, + node; + if (isBrowser) { + //In the browser so use a script tag + node = config.xhtml ? + document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') : + document.createElement('script'); + node.type = config.scriptType || 'text/javascript'; + node.charset = 'utf-8'; + node.async = true; + + node.setAttribute('data-requirecontext', context.contextName); + node.setAttribute('data-requiremodule', moduleName); + + //Set up load listener. Test attachEvent first because IE9 has + //a subtle issue in its addEventListener and script onload firings + //that do not match the behavior of all other browsers with + //addEventListener support, which fire the onload event for a + //script right after the script execution. See: + //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution + //UNFORTUNATELY Opera implements attachEvent but does not follow the script + //script execution mode. + if (node.attachEvent && + //Check if node.attachEvent is artificially added by custom script or + //natively supported by browser + //read https://github.com/jrburke/requirejs/issues/187 + //if we can NOT find [native code] then it must NOT natively supported. + //in IE8, node.attachEvent does not have toString() + //Note the test for "[native code" with no closing brace, see: + //https://github.com/jrburke/requirejs/issues/273 + !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) && + !isOpera) { + //Probably IE. IE (at least 6-8) do not fire + //script onload right after executing the script, so + //we cannot tie the anonymous define call to a name. + //However, IE reports the script as being in 'interactive' + //readyState at the time of the define call. + useInteractive = true; + + node.attachEvent('onreadystatechange', context.onScriptLoad); + //It would be great to add an error handler here to catch + //404s in IE9+. However, onreadystatechange will fire before + //the error handler, so that does not help. If addEvenListener + //is used, then IE will fire error before load, but we cannot + //use that pathway given the connect.microsoft.com issue + //mentioned above about not doing the 'script execute, + //then fire the script load event listener before execute + //next script' that other browsers do. + //Best hope: IE10 fixes the issues, + //and then destroys all installs of IE 6-9. + //node.attachEvent('onerror', context.onScriptError); + } else { + node.addEventListener('load', context.onScriptLoad, false); + node.addEventListener('error', context.onScriptError, false); + } + node.src = url; + + //For some cache cases in IE 6-8, the script executes before the end + //of the appendChild execution, so to tie an anonymous define + //call to the module name (which is stored on the node), hold on + //to a reference to this node, but clear after the DOM insertion. + currentlyAddingScript = node; + if (baseElement) { + head.insertBefore(node, baseElement); + } else { + head.appendChild(node); + } + currentlyAddingScript = null; + + return node; + } else if (isWebWorker) { + //In a web worker, use importScripts. This is not a very + //efficient use of importScripts, importScripts will block until + //its script is downloaded and evaluated. However, if web workers + //are in play, the expectation that a build has been done so that + //only one script needs to be loaded anyway. This may need to be + //reevaluated if other use cases become common. + importScripts(url); + + //Account for anonymous modules + context.completeLoad(moduleName); + } + }; + + function getInteractiveScript() { + if (interactiveScript && interactiveScript.readyState === 'interactive') { + return interactiveScript; + } + + eachReverse(scripts(), function (script) { + if (script.readyState === 'interactive') { + return (interactiveScript = script); + } + }); + return interactiveScript; + } + + //Look for a data-main script attribute, which could also adjust the baseUrl. + if (isBrowser) { + //Figure out baseUrl. Get it from the script tag with require.js in it. + eachReverse(scripts(), function (script) { + //Set the 'head' where we can append children by + //using the script's parent. + if (!head) { + head = script.parentNode; + } + + //Look for a data-main attribute to set main script for the page + //to load. If it is there, the path to data main becomes the + //baseUrl, if it is not already set. + dataMain = script.getAttribute('data-main'); + if (dataMain) { + //Set final baseUrl if there is not already an explicit one. + if (!cfg.baseUrl) { + //Pull off the directory of data-main for use as the + //baseUrl. + src = dataMain.split('/'); + mainScript = src.pop(); + subPath = src.length ? src.join('/') + '/' : './'; + + cfg.baseUrl = subPath; + dataMain = mainScript; + } + + //Strip off any trailing .js since dataMain is now + //like a module name. + dataMain = dataMain.replace(jsSuffixRegExp, ''); + + //Put the data-main script in the files to load. + cfg.deps = cfg.deps ? cfg.deps.concat(dataMain) : [dataMain]; + + return true; + } + }); + } + + /** + * The function that handles definitions of modules. Differs from + * require() in that a string for the module should be the first argument, + * and the function to execute after dependencies are loaded should + * return a value to define the module corresponding to the first argument's + * name. + */ + define = function (name, deps, callback) { + var node, context; + + //Allow for anonymous modules + if (typeof name !== 'string') { + //Adjust args appropriately + callback = deps; + deps = name; + name = null; + } + + //This module may not have dependencies + if (!isArray(deps)) { + callback = deps; + deps = []; + } + + //If no name, and callback is a function, then figure out if it a + //CommonJS thing with dependencies. + if (!deps.length && isFunction(callback)) { + //Remove comments from the callback string, + //look for require calls, and pull them into the dependencies, + //but only if there are function args. + if (callback.length) { + callback + .toString() + .replace(commentRegExp, '') + .replace(cjsRequireRegExp, function (match, dep) { + deps.push(dep); + }); + + //May be a CommonJS thing even without require calls, but still + //could use exports, and module. Avoid doing exports and module + //work though if it just needs require. + //REQUIRES the function to expect the CommonJS variables in the + //order listed below. + deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps); + } + } + + //If in IE 6-8 and hit an anonymous define() call, do the interactive + //work. + if (useInteractive) { + node = currentlyAddingScript || getInteractiveScript(); + if (node) { + if (!name) { + name = node.getAttribute('data-requiremodule'); + } + context = contexts[node.getAttribute('data-requirecontext')]; + } + } + + //Always save off evaluating the def call until the script onload handler. + //This allows multiple modules to be in a file without prematurely + //tracing dependencies, and allows for anonymous module support, + //where the module name is not known until the script onload event + //occurs. If no context, use the global queue, and get it processed + //in the onscript load callback. + (context ? context.defQueue : globalDefQueue).push([name, deps, callback]); + }; + + define.amd = { + jQuery: true + }; + + + /** + * Executes the text. Normally just uses eval, but can be modified + * to use a better, environment-specific call. Only used for transpiling + * loader plugins, not for plain JS modules. + * @param {String} text the text to execute/evaluate. + */ + req.exec = function (text) { + /*jslint evil: true */ + return eval(text); + }; + + //Set up with config info. + req(cfg); +}(this)); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/LICENSE b/activities/ConnecttheDots.activity/lib/sugar-web/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/README.md b/activities/ConnecttheDots.activity/lib/sugar-web/README.md new file mode 100644 index 000000000..de87a7250 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/README.md @@ -0,0 +1,7 @@ +Sugar Web +========= + +These are the tools that a developer can use to make a web +activity. + +For details see: http://developer.sugarlabs.org/ diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/activity/activity.js b/activities/ConnecttheDots.activity/lib/sugar-web/activity/activity.js new file mode 100644 index 000000000..0695b62e8 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/activity/activity.js @@ -0,0 +1,85 @@ +define(["webL10n", + "sugar-web/activity/shortcut", + "sugar-web/bus", + "sugar-web/env", + "sugar-web/datastore", + "sugar-web/graphics/icon", + "sugar-web/graphics/activitypalette"], function ( + l10n, shortcut, bus, env, datastore, icon, activitypalette) { + + var datastoreObject = null; + + var activity = {}; + + activity.setup = function () { + bus.listen(); + + l10n.start(); + + var activityButton = document.getElementById("activity-button"); + + var activityPalette = new activitypalette.ActivityPalette(); + + // Colorize the activity icon. + activity.getXOColor(function (error, colors) { + icon.colorize(activityButton, colors); + invokerElem = + document.querySelector("#activity-palette .palette-invoker"); + icon.colorize(invokerElem, colors); + }); + + // Make the activity stop with the stop button. + var stopButton = document.getElementById("stop-button"); + stopButton.addEventListener('click', function (e) { + activity.close(); + }); + + shortcut.add("Ctrl", "Q", this.close); + + datastoreObject = new datastore.DatastoreObject(); + + env.getEnvironment(function (error, environment) { + datastoreObject.setMetadata({ + "activity": environment.bundleId, + "activity_id": environment.activityId + }); + datastoreObject.save(function () {}); + }); + }; + + activity.getDatastoreObject = function () { + return datastoreObject; + }; + + activity.getXOColor = function (callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null, { + stroke: result[0][0], + fill: result[0][1] + }); + } else { + callback(null, { + stroke: "#00A0FF", + fill: "#8BFF7A" + }); + } + } + + bus.sendMessage("activity.get_xo_color", [], onResponseReceived); + }; + + activity.close = function (callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null); + } else { + console.log("activity.close called"); + } + } + + bus.sendMessage("activity.close", [], onResponseReceived); + }; + + return activity; +}); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/activity/shortcut.js b/activities/ConnecttheDots.activity/lib/sugar-web/activity/shortcut.js new file mode 100644 index 000000000..403af03e5 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/activity/shortcut.js @@ -0,0 +1,57 @@ +define(function () { + var shortcut = {}; + + shortcut._allShortcuts = []; + + shortcut.add = function (modifiersString, key, callback) { + // Parse the modifiers. For example "Ctrl+Alt" will become + // {'ctrlKey': true, 'altKey': true, 'shiftKey': false} + var modifiersList = modifiersString.toLowerCase().split("+"); + var modifiers = { + 'ctrlKey': modifiersList.indexOf('ctrl') >= 0, + 'altKey': modifiersList.indexOf('alt') >= 0, + 'shiftKey': modifiersList.indexOf('shift') >= 0 + }; + + this._allShortcuts.push({ + 'modifiers': modifiers, + 'key': key.toLowerCase(), + 'callback': callback + }); + }; + + document.onkeypress = function (e) { + e = e || window.event; + + var modifiers = { + 'ctrlKey': e.ctrlKey, + 'altKey': e.altKey, + 'shiftKey': e.shiftKey + }; + + // Obtain the key + var charCode; + if (typeof e.which == "number") { + charCode = e.which; + } else { + charCode = e.keyCode; + } + var key = String.fromCharCode(charCode).toLowerCase(); + + // Search for a matching shortcut + for (i = 0; i < shortcut._allShortcuts.length; i += 1) { + var currentShortcut = shortcut._allShortcuts[i]; + + var match = currentShortcut.key == key && + currentShortcut.modifiers.ctrlKey == modifiers.ctrlKey && + currentShortcut.modifiers.altKey == modifiers.altKey && + currentShortcut.modifiers.shiftKey == modifiers.shiftKey; + if (match) { + currentShortcut.callback(); + return; + } + } + }; + + return shortcut; +}); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/bus.js b/activities/ConnecttheDots.activity/lib/sugar-web/bus.js new file mode 100644 index 000000000..c1b5cbb74 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/bus.js @@ -0,0 +1,229 @@ +define(["sugar-web/env"], function (env) { + var lastId = 0; + var callbacks = {}; + var notificationCallbacks = {}; + var client = null; + var inputStreams = []; + + function WebSocketClient(environment) { + this.queue = []; + this.socket = null; + + var that = this; + + env.getEnvironment(function (error, environment) { + var port = environment.apiSocketPort; + var socket = new WebSocket("ws://localhost:" + port); + + socket.binaryType = "arraybuffer"; + + socket.onopen = function () { + var params = [environment.activityId, + environment.apiSocketKey]; + + socket.send(JSON.stringify({ + "method": "authenticate", + "id": "authenticate", + "params": params + })); + + while (that.queue.length > 0) { + socket.send(that.queue.shift()); + } + }; + + socket.onmessage = function (message) { + that.onMessage(message); + }; + + that.socket = socket; + }); + } + + WebSocketClient.prototype.send = function (data) { + + if (this.socket && this.socket.readyState == WebSocket.OPEN) { + this.socket.send(data); + } else { + this.queue.push(data); + } + }; + + WebSocketClient.prototype.close = function () { + this.socket.close(); + }; + + var bus = {}; + + function InputStream() { + this.streamId = null; + this.readCallback = null; + } + + InputStream.prototype.open = function (callback) { + var that = this; + bus.sendMessage("open_stream", [], function (error, result) { + that.streamId = result[0]; + inputStreams[that.streamId] = that; + callback(error); + }); + }; + + InputStream.prototype.read = function (count, callback) { + if (this.readCallback) { + throw Error("Read already in progress"); + } + + this.readCallback = callback; + + var buffer = new ArrayBuffer(8); + + var headerView = new Uint8Array(buffer, 0, 1); + headerView[0] = this.streamId; + + var bodyView = new Uint32Array(buffer, 4, 1); + bodyView[0] = count; + + bus.sendBinary(buffer); + }; + + InputStream.prototype.gotData = function (buffer) { + var callback = this.readCallback; + + this.readCallback = null; + + callback(null, buffer); + }; + + InputStream.prototype.close = function (callback) { + var that = this; + + function onStreamClosed(error, result) { + if (callback) { + callback(error); + } + delete inputStreams[that.streamId]; + } + + bus.sendMessage("close_stream", [this.streamId], onStreamClosed); + }; + + function OutputStream() { + this.streamId = null; + } + + OutputStream.prototype.open = function (callback) { + var that = this; + bus.sendMessage("open_stream", [], function (error, result) { + that.streamId = result[0]; + callback(error); + }); + }; + + OutputStream.prototype.write = function (data) { + var buffer = new ArrayBuffer(data.byteLength + 1); + + var bufferView = new Uint8Array(buffer); + bufferView[0] = this.streamId; + bufferView.set(new Uint8Array(data), 1); + + bus.sendBinary(buffer); + }; + + OutputStream.prototype.close = function (callback) { + bus.sendMessage("close_stream", [this.streamId], callback); + }; + + bus.createInputStream = function (callback) { + return new InputStream(); + }; + + bus.createOutputStream = function (callback) { + return new OutputStream(); + }; + + bus.sendMessage = function (method, params, callback) { + + var message = { + "method": method, + "id": lastId, + "params": params + }; + + /*if (callback) { + callbacks[lastId] = callback; + } + lastId++; + client.send(JSON.stringify(message));*/ + + console.log("ACTIVITY CALL "+JSON.stringify(message)); + if (method == "activity.close") { + window.location = "../.."; + } else if (method == "activity.get_xo_color") { + var color = JSON.parse(window.localStorage.getItem("settings")).colorvalue; + callback(null, [[color.fill, color.stroke]]); + } + + + }; + + bus.onNotification = function (method, callback) { + notificationCallbacks[method] = callback; + }; + + bus.sendBinary = function (buffer, callback) { + client.send(buffer); + }; + + bus.listen = function (customClient) { + if (customClient) { + client = customClient; + } else { + client = new WebSocketClient(); + } + + client.onMessage = function (message) { + if (typeof message.data != "string") { + var dataView = new Uint8Array(message.data); + var streamId = dataView[0]; + + if (streamId in inputStreams) { + var inputStream = inputStreams[streamId]; + inputStream.gotData(message.data.slice(1)); + } + + return; + } + + var parsed = JSON.parse(message.data); + var responseId = parsed.id; + + if (parsed.method) { + var notificationCallback = notificationCallbacks[parsed.method]; + if (notificationCallback !== undefined) { + notificationCallback(parsed.params); + } + return; + } + + if (responseId in callbacks) { + var callback = callbacks[responseId]; + + if (parsed.error === null) { + callback(null, parsed.result); + } else { + callback(new Error(parsed.error), null); + } + + delete callbacks[responseId]; + } + }; + }; + + bus.close = function () { + client.close(); + client = null; + }; + + return bus; +}); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/datastore.js b/activities/ConnecttheDots.activity/lib/sugar-web/datastore.js new file mode 100644 index 000000000..ae54909cc --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/datastore.js @@ -0,0 +1,217 @@ +define(["sugar-web/bus", "sugar-web/env"], function (bus, env) { + var datastore = {}; + + function DatastoreObject(objectId) { + this.objectId = objectId; + this.newMetadata = {}; + + this.ensureObjectId = function (callback) { + var that = this; + + env.getEnvironment(function (error, environment) { + if (environment.objectId !== null) { + that.objectId = environment.objectId; + } + callback(); + }); + }; + + this.blobToText = function (blob, callback) { + var reader = new FileReader(); + reader.onload = function (e) { + callback(e.target.result); + }; + reader.readAsText(blob); + }; + + this.blobToArrayBuffer = function (blob, callback) { + var reader = new FileReader(); + reader.onload = function (e) { + callback(e.target.result); + }; + reader.readAsArrayBuffer(blob); + }; + + this.saveText = function (metadata, callback) { + var that = this; + + function onSaved(error, outputStream) { + var blob = new Blob([that.newDataAsText]); + + that.blobToArrayBuffer(blob, function (buffer) { + outputStream.write(buffer); + outputStream.close(callback); + }); + } + + datastore.save(this.objectId, metadata, onSaved); + }; + + this.applyChanges = function (metadata, callback) { + for (var key in this.newMetadata) { + metadata[key] = this.newMetadata[key]; + } + + if (this.newDataAsText !== undefined) { + this.saveText(metadata, callback); + } else { + datastore.setMetadata(this.objectId, metadata, callback); + } + }; + } + + DatastoreObject.prototype.getMetadata = function (callback) { + var that = this; + + this.ensureObjectId(function () { + datastore.getMetadata(that.objectId, callback); + }); + }; + + DatastoreObject.prototype.loadAsText = function (callback) { + var that = this; + var inputStream = null; + var arrayBuffers = []; + var metadata = null; + + function onRead(error, data) { + if (data.byteLength === 0) { + var blob = new Blob(arrayBuffers); + + that.blobToText(blob, function (text) { + callback(null, metadata, text); + }); + + inputStream.close(); + + return; + } + + arrayBuffers.push(data); + + inputStream.read(8192, onRead); + } + + function onLoad(error, loadedMetadata, loadedInputStream) { + metadata = loadedMetadata; + inputStream = loadedInputStream; + + inputStream.read(8192, onRead); + } + + this.ensureObjectId(function () { + datastore.load(that.objectId, onLoad); + }); + }; + + DatastoreObject.prototype.setMetadata = function (metadata) { + for (var key in metadata) { + this.newMetadata[key] = metadata[key]; + } + }; + + DatastoreObject.prototype.setDataAsText = function (text) { + this.newDataAsText = text; + }; + + DatastoreObject.prototype.save = function (callback) { + var that = this; + + function onCreated(error, objectId) { + that.objectId = objectId; + that.applyChanges({}, callback); + } + + function onGotMetadata(error, metadata) { + that.applyChanges(metadata, callback); + } + + this.ensureObjectId(function () { + if (that.objectId === undefined) { + datastore.create(that.newMetadata, onCreated); + } else { + datastore.getMetadata(that.objectId, onGotMetadata); + } + }); + }; + + datastore.DatastoreObject = DatastoreObject; + + + datastore.setMetadata = function (objectId, metadata, callback) { + function onResponseReceived(error, result) { + if (callback) { + if (error === null) { + callback(null); + } else { + callback(error); + } + } + } + + var params = [objectId, metadata]; + bus.sendMessage("datastore.set_metadata", params, onResponseReceived); + }; + + datastore.getMetadata = function (objectId, callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null, result[0]); + } else { + callback(error, null); + } + } + + var params = [objectId]; + bus.sendMessage("datastore.get_metadata", params, onResponseReceived); + }; + + datastore.load = function (objectId, callback) { + inputStream = bus.createInputStream(); + + inputStream.open(function (error) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, result[0], inputStream); + } else { + callback(responseError, null, null); + } + } + + var params = [objectId, inputStream.streamId]; + bus.sendMessage("datastore.load", params, onResponseReceived); + }); + }; + + datastore.create = function (metadata, callback) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, result[0]); + } else { + callback(responseError, null); + } + } + + var params = [metadata]; + bus.sendMessage("datastore.create", params, onResponseReceived); + }; + + datastore.save = function (objectId, metadata, callback) { + outputStream = bus.createOutputStream(); + + outputStream.open(function (error) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, outputStream); + } else { + callback(responseError, null); + } + } + + var params = [objectId, metadata, outputStream.streamId]; + bus.sendMessage("datastore.save", params, onResponseReceived); + }); + }; + + return datastore; +}); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/env.js b/activities/ConnecttheDots.activity/lib/sugar-web/env.js new file mode 100644 index 000000000..1a948eb30 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/env.js @@ -0,0 +1,27 @@ +define(function () { + + var env = {}; + + env.getEnvironment = function (callback) { + var sugar; + + if (window.top.sugar) { + sugar = window.top.sugar; + } else { + sugar = {}; + window.top.sugar = sugar; + } + + if (sugar.environment) { + setTimeout(function () { + callback(null, sugar.environment); + }, 0); + } else { + sugar.onEnvironmentSet = function () { + callback(null, sugar.environment); + }; + } + }; + + return env; +}); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/README.md b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/README.md new file mode 100644 index 000000000..e3b7091ba --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/README.md @@ -0,0 +1,39 @@ +Sugar Graphics +============== + +Sugar widgets and graphics, implementing the [Sugar Interface +Guidelines](http://wiki.sugarlabs.org/go/Human_Interface_Guidelines). + +Modifying the CSS +----------------- + +We use [LESS](http://lesscss.org) and then compile the CSS. This is +to be able to use calculations and variables for colors and measures. +And to be able to output different CSS files for different screen +resolutions, which is planned. + +To compile the CSS do: + + lessc graphics/css/sugar.less graphics/css/sugar.css + +Be sure to compile it before commit. + +The grid helper +--------------- + +The grid is a visual debug tool that allows you to check if the +elements have the correct size. To activate the grid, open the +inspector console and paste the following. + +If RequireJS is not in the page head (ie. in the sugar-web-samples), +load it: + + var script = document.createElement('script'); + script.src = 'lib/require.js'; + document.head.appendChild(script); + + requirejs.config({ baseUrl: "lib" }); + +Then do: + + require(["sugar-web/graphics/grid"], function (grid) { grid.addGrid(11) }); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/activitypalette.js b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/activitypalette.js new file mode 100644 index 000000000..0da5b9c32 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/activitypalette.js @@ -0,0 +1,105 @@ +define(["sugar-web/graphics/palette", + "sugar-web/datastore", + "sugar-web/env", + "sugar-web/bus"], function (palette, datastore, env, bus) { + + var activitypalette = {}; + + activitypalette.ActivityPalette = function () { + + var activityTitle; + var descriptionLabel; + var descriptionBox; + + this.getPalette().id = "activity-palette"; + + var titleContainer = document.createElement('div'); + titleContainer.className = "row"; + activityTitle = document.createElement('input'); + activityTitle.type = "text"; + activityTitle.id = "title"; + activityTitle.className = "expand"; + titleContainer.appendChild(activityTitle); + + var descLabelContainer = document.createElement('div'); + descLabelContainer.className = "row small"; + descriptionLabel = document.createElement('label'); + descriptionLabel.innerHTML = "Description:"; + descLabelContainer.appendChild(descriptionLabel); + + var descriptionContainer = document.createElement('div'); + descriptionContainer.className = "row expand"; + descriptionBox = document.createElement('textarea'); + descriptionBox.rows = "8"; + descriptionBox.id = "description"; + descriptionBox.className = "expand"; + descriptionContainer.appendChild(descriptionBox); + + this.setContent([titleContainer, descLabelContainer, + descriptionContainer]); + + var titleElem = document.getElementById("title"); + var descriptionElem = document.getElementById("description"); + var datastoreObject = new datastore.DatastoreObject(); + var mdata; + var title; + + bus.listen(); + + datastoreObject.getMetadata(function (error, metadata) { + mdata = metadata; + setTitleDescription(); + }); + + setTitleDescription(); + + function setTitleDescription() { + if ((mdata === undefined) || (mdata.title === undefined)) { + env.getEnvironment(function (error, environment) { + title = environment.activityName; + datastoreObject.setMetadata({ + "activity": environment.bundleId, + "activity_id": environment.activityId, + "title": title + }); + datastoreObject.save(function () {}); + titleElem.value = title; + }); + } else { + if (mdata.title !== undefined) { + titleElem.value = mdata.title; + datastoreObject.setMetadata({ + "activity": environment.bundleId, + "activity_id": environment.activityId, + "title": mdata.title + }); + datastoreObject.save(function () {}); + } + + if (mdata.description !== undefined) + descriptionElem.value = mdata.description; + } + } + + titleElem.onblur = function () { + datastoreObject.setMetadata({ + "title": this.value + }); + datastoreObject.save(function () {}); + }; + + descriptionElem.onblur = function () { + datastoreObject.setMetadata({ + "description": this.value + }); + datastoreObject.save(function () {}); + }; + }; + + var activityButton = document.getElementById("activity-button"); + + activitypalette.ActivityPalette.prototype = + new palette.Palette(activityButton); + + return activitypalette; +}); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/css/sugar.css b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/css/sugar.css new file mode 100644 index 000000000..324cf0f54 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/css/sugar.css @@ -0,0 +1,423 @@ +* { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +html { + height: 100%; +} +body { + margin: 0; + height: 100%; + background-color: #c0c0c0; + position: relative; + font-family: sans-serif; + font-size: 10pt; +} +.unselectable { + -moz-user-select: none; + -webkit-user-select: none; +} +.pull-right { + float: right; +} +a { + color: #0076c3; + text-decoration: none; +} +/* Toolbar */ +.toolbar { + color: white; + background-color: #282828; + padding: 0 57px; + height: 55px; + -moz-user-select: none; + -webkit-user-select: none; +} +/* Toolbar separator */ +.toolbar hr { + display: inline-block; + height: 33px; + border: 1px solid #808080; + margin: 2.5px; +} +/* Toolbar toolbutton */ +.toolbar .toolbutton { + background-color: transparent; + background-position: center; + background-size: contain; + background-repeat: no-repeat; + color: white; + color: transparent; + border: 0; + border-radius: 5.5px; + margin: 4px 2px; + width: 47px; + height: 47px; + position: relative; +} +.toolbar .toolbutton:hover { + background-color: black; +} +.toolbar .toolbutton:active, +.toolbar .toolbutton.active { + background-color: #808080; +} +.toolbar .toolbutton img { + width: 100%; + height: 100%; +} +.toolbar .toolbutton:before { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: 55px; + height: 11px; + bottom: -4px; + right: -4px; +} +.toolbar .toolbutton.invoker:before { + background-image: url('../icons/emblems/arrow-down.svg'); +} +.toolbar #stop-button { + background-image: url('../icons/actions/activity-stop.svg'); +} +/* Canvas */ +#canvas { + color: black; + background-color: #c0c0c0; + position: absolute; + bottom: 0; + top: 55px; + overflow-y: auto; + width: 100%; +} +/* Button */ +button { + background-color: #808080; + color: white; + border: 2px solid transparent; + border-radius: 22px; + line-height: 22px; + padding: 2px 4px; + -moz-user-select: none; + -webkit-user-select: none; +} +button:hover { + background-color: #a2a2a2; + border-color: #a2a2a2; +} +button:active { + background-color: white; + color: black; + border-color: #808080; +} +button:focus { + border-color: white; +} +.toolbar button { + margin-top: 12.5px; +} +/* Button with icon */ +button.icon { + position: relative; + padding-left: 26px; +} +button.icon span.ok { + background-image: url(../icons/actions/dialog-ok.svg); +} +button.icon:active span.ok { + background-image: url(../icons/actions/dialog-ok-active.svg); +} +button.icon span.cancel { + background-image: url(../icons/actions/dialog-cancel.svg); +} +button.icon:active span.cancel { + background-image: url(../icons/actions/dialog-cancel-active.svg); +} +button.icon span { + display: inline-block; + width: 22px; + height: 22px; + background-color: transparent; + background-position: center; + background-size: 22px 22px; + background-repeat: no-repeat; + position: absolute; +} +button.icon span { + top: 2px; + left: 2px; +} +/* One line text input */ +input[type='text'] { + background-color: #e5e5e5; + border: 2px solid #e5e5e5; + border-radius: 22px; + padding: 4px; + width: 165px; + line-height: 22px; + outline: 0; +} +input[type='text']:focus { + background-color: white; +} +input[type='text']:disabled { + border-color: #808080; + background-color: #808080; +} +.toolbar input[type='text'], +.palette .row input[type='text'] { + margin-top: 10.5px; +} +.palette .row input[type='text']:nth-last-child(1) { + margin-top: 8.5px; +} +input[type='text'].expand { + width: calc(100% - 12px); +} +/* One line text input with buttons inside */ +.icon-input { + display: inline-block; + position: relative; +} +.icon-input input[type='text'] { + width: 135px; + padding-right: 34px; +} +.icon-input.expand { + width: 100%; +} +.icon-input.expand input[type='text'] { + width: calc(100% - 42px); +} +.icon-input button { + width: 34px; + height: 34px; + padding: 0; + position: absolute; + background-size: 22px 22px; +} +.icon-input button.right { + margin: 0 0 0 -34px; + border-radius: 0 22px 22px 0; + right: 0; +} +.icon-input button { + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + border: 0; +} +.icon-input button { + top: 2px; +} +.toolbar .icon-input button:hover { + background-color: transparent; +} +.toolbar .icon-input button { + top: 10.5px; +} +button.cancel { + background-image: url(../icons/actions/entry-cancel.svg); +} +button.cancel:active { + background-image: url(../icons/actions/entry-cancel-active.svg); +} +button.cancel:disabled { + background-image: url(../icons/actions/entry-cancel-disabled.svg); +} +/* Slider */ +/* FIXME this is not fully Sugarized yet */ +input[type='range'] { + -webkit-appearance: none !important; + background-color: #808080; + border-radius: 22px; + height: 11px; + cursor: pointer; +} +input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none !important; + background-color: #c0c0c0; + border: 4px solid #808080; + border-radius: 11px; + height: 22px; + width: 22px; +} +input[type='range']::-webkit-slider-thumb:hover { + background-color: #e2e2e2; + border-color: #a2a2a2; +} +.toolbar input[type='range'] { + margin-top: 22px; +} +/* Label */ +label { + -moz-user-select: none; + -webkit-user-select: none; +} +/* Palette */ +.palette { + color: white; + background-color: transparent; + position: absolute; + pointer-events: none; +} +.palette-invoker { + width: 51px; + height: 53px; + background-color: black; + border: 2px solid #808080; + border-bottom: 0; + background-position: 2px 2px; + background-size: 47px; + background-repeat: no-repeat; +} +.palette-invoker:after { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: 55px; + height: 11px; + top: 44px; + left: 0; + background-image: url('../icons/emblems/arrow-up.svg'); +} +.palette-invoker:before { + content: ""; + background-color: black; + position: absolute; + display: block; + top: 55px; + width: 51px; + left: 2px; + height: 2px; +} +.palette .wrapper { + background-color: black; + border: 2px solid #808080; + max-width: 271px; + min-width: 161px; + min-height: 51px; + pointer-events: auto; +} +.palette .header { + height: 51px; + line-height: 51px; + -moz-user-select: none; + -webkit-user-select: none; + margin: 0 5.5px; + font-weight: bold; +} +.palette hr { + border: 1px solid #808080; +} +.palette hr.header-separator { + margin-top: 0; +} +.palette .row { + height: 55px; + margin: 0 5.5px; +} +.palette .row:nth-last-child(1) { + height: 51px; +} +.palette .row.small { + height: 22px; +} +.palette .row.expand { + height: auto; +} +/* Scrollbar */ +::-webkit-scrollbar { + background-color: #808080; + width: 11px; +} +::-webkit-scrollbar-thumb { + background-color: white; + border: 2px solid #dddddd; + border-radius: 11px; +} +/* Grid for visual debugging and layout */ +.grid { + background-color: transparent; + position: absolute; + top: 0; + left: 0; +} +/* Checkbox and radio */ +input[type='checkbox'], +input[type='radio'] { + background-position: center; + background-size: contain; + width: 22px; + height: 22px; + margin: 2px 2px 4px 2px; + vertical-align: middle; + -moz-appearance: none; + appearance: none; + -webkit-appearance: none; +} +.toolbar input[type='checkbox'], +.toolbar input[type='radio'] { + margin-top: 14.5px; + margin-bottom: 18.5px; +} +input[type='checkbox'] { + background-image: url(../icons/actions/checkbox-unchecked.svg); +} +input[type='checkbox']:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} +input[type='checkbox']:checked:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} +input[type='checkbox']:checked { + background-image: url(../icons/actions/checkbox-checked.svg); +} +input[type='radio'] { + background-image: url(../icons/actions/radio.svg); +} +input[type='radio']:active { + background-image: url(../icons/actions/radio-selected.svg); +} +input[type='radio']:checked:active { + background-image: url(../icons/actions/radio-active-selected.svg); +} +input[type='radio']:checked { + background-image: url(../icons/actions/radio-active.svg); +} +/* Textarea */ +textarea { + border: 2px solid #808080; + margin: 2px; +} +textarea.expand { + width: calc(100% - 12px); +} +/* Lists */ +ul.flat-list { + list-style-type: none; + padding: 0; + margin: 0; +} +ul.flat-list li { + border-bottom: 2px dotted #c0c0c0; + background-color: white; + height: 31px; + line-height: 31px; +} +ul.flat-list li:nth-last-child(1) { + border-bottom: none; +} +ul.flat-list.big li { + height: 42px; + line-height: 42px; +} +ul.flat-list.striped li:nth-child(odd) { + background-color: #e5e5e5; +} +/* ActivityPalette */ +#activity-palette .wrapper { + width: 271px; +} diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/css/sugar.less b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/css/sugar.less new file mode 100644 index 000000000..e33743a97 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/css/sugar.less @@ -0,0 +1,550 @@ +@toolbar-grey: #282828; +@button-grey: #808080; +@panel-grey: #C0C0C0; +@text-field-grey: #E5E5E5; +@link-blue: #0076C3; + +@line-width: 2px; +@subcell-size: 11px; +@cell-size: 5 * @subcell-size; +@font-size: 10pt; + +@toolbar-height: @cell-size; +@icon-small-size: 2 * @subcell-size; + +@toolbutton-size: @toolbar-height - (4 * @line-width); +@toolbar-button-margin-top: (@toolbar-height / 2) - (2 * @line-width) - + (@icon-small-size / 2); + +@input-text-padding: (2 * @line-width); +@input-text-line-height: 2 * @subcell-size; +@input-text-width: 3 * @cell-size; +@toolbar-input-height: (@toolbar-height / 2) - (@input-text-line-height / 2) - + (@input-text-padding + @line-width); + +@button-small-size: @input-text-line-height + (2 * @input-text-padding) + + (2 * @line-width); + +* { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +html { + height: 100%; +} + +body { + margin: 0; + height: 100%; + background-color: @panel-grey; + position: relative; + font-family: sans-serif; + font-size: @font-size; +} + +.unselectable { + -moz-user-select: none; + -webkit-user-select: none; +} + +.pull-right { + float: right; +} + +a { + color: @link-blue; + text-decoration: none; +} + +/* Toolbar */ + +.toolbar { + color: white; + background-color: @toolbar-grey; + padding: 0 @toolbutton-size + (5 * @line-width); + height: @toolbar-height; + .unselectable; +} + +/* Toolbar separator */ + +.toolbar hr { + display: inline-block; + height: 3 * @subcell-size; + border: (@line-width / 2) solid @button-grey; + margin: (@subcell-size - (@line-width * 3)) / 2; +} + +/* Toolbar toolbutton */ + +.toolbar .toolbutton { + background-color: transparent; + background-position: center; + background-size: contain; + background-repeat: no-repeat; + color: white; + color: transparent; + border: 0; + border-radius: @subcell-size / 2; + margin: (2 * @line-width) @line-width; + width: @toolbutton-size; + height: @toolbutton-size; + position: relative; +} + +.toolbar .toolbutton:hover { + background-color: black; +} + +.toolbar .toolbutton:active, .toolbar .toolbutton.active { + background-color: @button-grey; +} + +.toolbar .toolbutton img { + width: 100%; + height: 100%; +} + +.toolbar .toolbutton:before { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: @cell-size; + height: @subcell-size; + bottom: -2 * @line-width; + right: -2 * @line-width; +} + +.toolbar toolbutton.invoker:before{ + background-image: url('../icons/emblems/arrow-down.svg'); +} + +.toolbar #stop-button { + background-image: url('../icons/actions/activity-stop.svg'); +} + +/* Canvas */ + +#canvas { + color: black; + background-color: @panel-grey; + position: absolute; + bottom: 0; + top: @toolbar-height; + overflow-y: auto; + width: 100%; +} + +/* Button */ + +button { + background-color: @button-grey; + color: white; + border: @line-width solid transparent; + border-radius: 2 * @subcell-size; + line-height: @icon-small-size; + padding: @line-width (2 * @line-width); + .unselectable; +} + +button:hover { + background-color: @button-grey + #222; + border-color: @button-grey + #222; +} + +button:active { + background-color: white; + color: black; + border-color: @button-grey; +} + +button:focus { + border-color: white; +} + +.toolbar button { + margin-top: @toolbar-button-margin-top; +} + +/* Button with icon */ + +button.icon { + position: relative; + padding-left: @icon-small-size + (2 * @line-width); +} + +button.icon span.ok { + background-image: url(../icons/actions/dialog-ok.svg); +} + +button.icon:active span.ok { + background-image: url(../icons/actions/dialog-ok-active.svg); +} + +button.icon span.cancel { + background-image: url(../icons/actions/dialog-cancel.svg); +} + +button.icon:active span.cancel { + background-image: url(../icons/actions/dialog-cancel-active.svg); +} + +button.icon span { + display: inline-block; + width: @icon-small-size; + height: @icon-small-size; + background-color: transparent; + background-position: center; + background-size: @icon-small-size @icon-small-size; + background-repeat: no-repeat; + position: absolute; +} + +button.icon span { + top: @line-width; + left: @line-width; +} + +/* One line text input */ + +input[type='text'] { + background-color: @text-field-grey; + border: @line-width solid @text-field-grey; + border-radius: 2 * @subcell-size; + padding: @input-text-padding; + width: @input-text-width; + line-height: @input-text-line-height; + outline: 0; +} + +input[type='text']:focus { + background-color: white; +} + +input[type='text']:disabled { + border-color: @button-grey; + background-color: @button-grey; +} + +.toolbar input[type='text'], .palette .row input[type='text'] { + margin-top: @toolbar-input-height; +} + +.palette .row input[type='text']:nth-last-child(1) { + margin-top: @toolbar-input-height - @line-width; +} + +input[type='text'].expand { + width: calc(~"100%" - (2 * @input-text-padding + 2 * @line-width)); +} + +/* One line text input with buttons inside */ + +.icon-input { + display: inline-block; + position: relative; +} + +.icon-input input[type='text'] { + width: @input-text-width + @input-text-padding - @button-small-size; + padding-right: @button-small-size; +} + +.icon-input.expand { + width: 100%; +} + +.icon-input.expand input[type='text'] { + width: calc(~"100%" - (@input-text-padding + 2 * @line-width + + @button-small-size)); +} + +.icon-input button { + width: @button-small-size; + height: @button-small-size; + padding: 0; + position: absolute; + background-size: @icon-small-size @icon-small-size; +} + +.icon-input button.right { + margin: 0 0 0 -@button-small-size; + border-radius: 0 (2 * @subcell-size) (2 * @subcell-size) 0; + right: 0; +} + +.icon-input button { + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + border: 0; +} + +.icon-input button { + top: @line-width; +} + +.toolbar .icon-input button:hover { + background-color: transparent; +} + +.toolbar .icon-input button { + top: @toolbar-input-height; +} + +button.cancel { + background-image: url(../icons/actions/entry-cancel.svg); +} + +button.cancel:active { + background-image: url(../icons/actions/entry-cancel-active.svg); +} + +button.cancel:disabled { + background-image: url(../icons/actions/entry-cancel-disabled.svg); +} + +/* Slider */ +/* FIXME this is not fully Sugarized yet */ + +input[type='range'] { + -webkit-appearance: none !important; + background-color: @button-grey; + border-radius: 2 * @subcell-size; + height: @subcell-size; + cursor: pointer; +} + +input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none !important; + background-color: @panel-grey; + border: (2 * @line-width) solid @button-grey; + border-radius: @subcell-size; + height: @icon-small-size; + width: @icon-small-size; +} + +input[type='range']::-webkit-slider-thumb:hover { + background-color: @panel-grey + #222; + border-color: @button-grey + #222; +} + +.toolbar input[type='range'] { + margin-top: (@toolbar-height / 2) - (@subcell-size / 2); +} + +/* Label */ + +label { + .unselectable; +} + +/* Palette */ + +.palette { + color: white; + background-color: transparent; + position: absolute; + pointer-events: none; +} + +.palette-invoker { + width: @cell-size - 2 * @line-width; + height: @cell-size - @line-width; + background-color: black; + border: @line-width solid @button-grey; + border-bottom: 0; + background-position: @line-width @line-width; + background-size: @toolbutton-size; + background-repeat: no-repeat; +} + +.palette-invoker:after { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: @cell-size; + height: @subcell-size; + top: 4 * @subcell-size; + left: 0; + background-image: url('../icons/emblems/arrow-up.svg'); +} + +.palette-invoker:before { + content: ""; + background-color: black; + position: absolute; + display: block; + top: @cell-size; + width: @cell-size - 2 * @line-width; + left: @line-width; + height: @line-width; +} + +.palette .wrapper { + background-color: black; + border: @line-width solid @button-grey; + max-width: 5 * @cell-size - 2 * @line-width; + min-width: 3 * @cell-size - 2 * @line-width; + min-height: @cell-size - 2 * @line-width; + pointer-events: auto; +} + +.palette .header { + height: @cell-size - 2 * @line-width; + line-height: @cell-size - 2 * @line-width; + .unselectable; + margin: 0 (@subcell-size / 2); + font-weight: bold; +} + +.palette hr { + border: (@line-width / 2) solid @button-grey; +} + +.palette hr.header-separator { + margin-top: 0; +} + +.palette .row { + height: @cell-size; + margin: 0 (@subcell-size / 2); +} + +.palette .row:nth-last-child(1) { + height: @cell-size - 2 * @line-width; +} + +.palette .row.small { + height: 2 * @subcell-size; +} + +.palette .row.expand { + height: auto; +} + +/* Scrollbar */ + +::-webkit-scrollbar { + background-color: @button-grey; + width: @subcell-size; +} + +::-webkit-scrollbar-thumb { + background-color: white; + border: @line-width solid #ddd; + border-radius: @subcell-size; +} + +/* Grid for visual debugging and layout */ + +.grid { + background-color: transparent; + position: absolute; + top: 0; + left: 0; +} + +.appearance(@value) { + -moz-appearance: @value; + appearance: @value; + -webkit-appearance: @value; +} + +/* Checkbox and radio */ + +input[type='checkbox'], +input[type='radio'] { + background-position: center; + background-size: contain; + width: @icon-small-size; + height: @icon-small-size; + margin: @line-width @line-width (2 * @line-width) @line-width; + vertical-align: middle; + .appearance(none); +} + +.toolbar input[type='checkbox'], +.toolbar input[type='radio'] { + margin-top: (@toolbar-height - @icon-small-size) / 2 - @line-width; + margin-bottom: (@toolbar-height - @icon-small-size) / 2 + @line-width; +} + +input[type='checkbox'] { + background-image: url(../icons/actions/checkbox-unchecked.svg); +} + +input[type='checkbox']:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} + +input[type='checkbox']:checked:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} + +input[type='checkbox']:checked { + background-image: url(../icons/actions/checkbox-checked.svg); +} + +input[type='radio'] { + background-image: url(../icons/actions/radio.svg); +} + +input[type='radio']:active { + background-image: url(../icons/actions/radio-selected.svg); +} + +input[type='radio']:checked:active { + background-image: url(../icons/actions/radio-active-selected.svg); +} + +input[type='radio']:checked { + background-image: url(../icons/actions/radio-active.svg); +} + +/* Textarea */ + +textarea { + border: @line-width solid @button-grey; + margin: @line-width; +} + +textarea.expand { + width: calc(~"100%" - (6 * (@line-width))); +} + +/* Lists */ + +ul.flat-list { + list-style-type: none; + padding: 0; + margin: 0; +} + +ul.flat-list li { + border-bottom: @line-width dotted @panel-grey; + background-color: white; + height: 3 * @subcell-size - @line-width; + line-height: 3 * @subcell-size - @line-width; +} + +ul.flat-list li:nth-last-child(1) { + border-bottom: none; +} + +ul.flat-list.big li { + height: 4 * @subcell-size - @line-width; + line-height: 4 * @subcell-size - @line-width; +} + +ul.flat-list.striped li:nth-child(odd) { + background-color: @text-field-grey; +} + +/* ActivityPalette */ + +#activity-palette .wrapper { + width: 5 * @cell-size - 2 * @line-width; +} \ No newline at end of file diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/grid.js b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/grid.js new file mode 100644 index 000000000..503713ab7 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/grid.js @@ -0,0 +1,54 @@ +define(function () { + var grid = {}; + + // Add a grid overlay with lines spaced by subcellSize, for visual + // debugging. This is useful while doing the activity layout or + // while developing widgets. + grid.addGrid = function (subcellSize) { + var canvas = document.createElement('canvas'); + canvas.className = "grid"; + document.body.appendChild(canvas); + + var updateGrid = function () { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + var ctx = canvas.getContext("2d"); + ctx.strokeStyle = "#00FFFF"; + + var subcellsVertical = window.innerHeight / subcellSize; + for (i = 0; i < subcellsVertical; i++) { + if ((i + 1) % 5 === 0) { + ctx.lineWidth = 1; + } else { + ctx.lineWidth = 0.5; + } + ctx.beginPath(); + ctx.moveTo(0, subcellSize * (i + 1)); + ctx.lineTo(canvas.width, subcellSize * (i + 1)); + ctx.stroke(); + } + + var subcellsHorizontal = window.innerWidth / subcellSize; + for (i = 0; i < subcellsHorizontal; i++) { + if ((i + 1) % 5 === 0) { + ctx.lineWidth = 1; + } else { + ctx.lineWidth = 0.5; + } + ctx.beginPath(); + ctx.moveTo(subcellSize * (i + 1), 0); + ctx.lineTo(subcellSize * (i + 1), canvas.height); + ctx.stroke(); + } + }; + + updateGrid(); + + window.onresize = function (event) { + updateGrid(); + }; + }; + + return grid; +}); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icon.js b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icon.js new file mode 100644 index 000000000..f2c07915a --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icon.js @@ -0,0 +1,81 @@ +define(function () { + var icon = {}; + + function changeColors(iconData, fillColor, strokeColor) { + var re; + + if (fillColor) { + re = /()/; + iconData = iconData.replace(re, "$1" + fillColor + "$3"); + } + + if (strokeColor) { + re = /()/; + iconData = iconData.replace(re, "$1" + strokeColor + "$3"); + } + + return iconData; + } + + icon.load = function (iconInfo, callback) { + var source; + var dataHeader = "data:image/svg+xml,"; + + if ("uri" in iconInfo) { + source = iconInfo.uri; + } else if ("name" in iconInfo) { + source = "lib/graphics/icons/" + iconInfo.name + ".svg"; + } + + var fillColor = iconInfo.fillColor; + var strokeColor = iconInfo.strokeColor; + + // If source is already a data uri, read it instead of doing + // the XMLHttpRequest + if (source.substring(0, 4) == 'data') { + var iconData = unescape(source.slice(dataHeader.length)); + var newData = changeColors(iconData, fillColor, strokeColor); + callback(dataHeader + escape(newData)); + return; + } + + var client = new XMLHttpRequest(); + + client.onload = function () { + var iconData = this.responseText; + var newData = changeColors(iconData, fillColor, strokeColor); + callback(dataHeader + escape(newData)); + }; + + client.open("GET", source); + client.send(); + }; + + function getBackgroundURL(elem) { + var style = elem.currentStyle || window.getComputedStyle(elem, ''); + // Remove prefix 'url(' and suffix ')' before return + return style.backgroundImage.slice(4, -1); + } + + function setBackgroundURL(elem, url) { + elem.style.backgroundImage = "url('" + url + "')"; + } + + icon.colorize = function (elem, colors, callback) { + var iconInfo = { + "uri": getBackgroundURL(elem), + "strokeColor": colors.stroke, + "fillColor": colors.fill + }; + + icon.load(iconInfo, function (url) { + setBackgroundURL(elem, url); + if (callback) { + callback(); + } + }); + + }; + + return icon; +}); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg new file mode 100644 index 000000000..11b82e817 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg new file mode 100644 index 000000000..8ec1223a7 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg @@ -0,0 +1,27 @@ + + + +]> + + + + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg new file mode 100644 index 000000000..3cfce18f6 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg @@ -0,0 +1,27 @@ + + + +]> + + + + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg new file mode 100644 index 000000000..2263279eb --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg @@ -0,0 +1,22 @@ + + +]> + + + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg new file mode 100644 index 000000000..e1587823e --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg @@ -0,0 +1,22 @@ + + +]> + + + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg new file mode 100644 index 000000000..dc0d688f6 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg @@ -0,0 +1,6 @@ + + +]> + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg new file mode 100644 index 000000000..dab4ae2d9 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg new file mode 100644 index 000000000..45de8401a --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg @@ -0,0 +1,6 @@ + + +]> + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg new file mode 100644 index 000000000..69e5a2a13 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg new file mode 100644 index 000000000..467509e75 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg @@ -0,0 +1,23 @@ + + + +]> + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg new file mode 100644 index 000000000..55b4cdb87 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg @@ -0,0 +1,21 @@ + + + +]> + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg new file mode 100644 index 000000000..5339a7e94 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg @@ -0,0 +1,21 @@ + + + +]> + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg new file mode 100644 index 000000000..c1d1085a0 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg @@ -0,0 +1,31 @@ + + + +]> + + + + + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg new file mode 100644 index 000000000..a5fe59156 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg @@ -0,0 +1,31 @@ + + + +]> + + + + + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg new file mode 100644 index 000000000..a24b97e65 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg @@ -0,0 +1,26 @@ + + + +]> + + + + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio.svg new file mode 100644 index 000000000..d25028624 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/radio.svg @@ -0,0 +1,26 @@ + + + +]> + + + + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg new file mode 100644 index 000000000..b88462ff5 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg new file mode 100644 index 000000000..5578fecbf --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg new file mode 100644 index 000000000..8d3f8d134 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg new file mode 100644 index 000000000..2de1a9e34 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg @@ -0,0 +1,20 @@ + + + +]> + + + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg new file mode 100644 index 000000000..a977f4a86 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg @@ -0,0 +1,20 @@ + + + +]> + + + + + diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/palette.js b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/palette.js new file mode 100644 index 000000000..9a194a148 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/palette.js @@ -0,0 +1,179 @@ +define(function () { + var palettesGroup = []; + + function getOffset(elem) { + // Ugly hack to consider the palette margin. + var style = elem.currentStyle || window.getComputedStyle(elem, ''); + + // Remove 'px' from the strings. + var x = -2 * style.marginLeft.slice(0, -2); + var y = -1 * style.marginTop.slice(0, -2); + + var rect = elem.getBoundingClientRect(); + x += rect.left; + y += rect.top; + return { + top: y, + left: x, + width: rect.width, + height: rect.height + }; + } + + var palette = {}; + + palette.Palette = function (invoker, primaryText) { + this.invoker = invoker; + if (this.invoker.classList.contains("toolbutton")) { + this.invoker.classList.add("invoker"); + } + this.primaryText = primaryText; + var paletteElem; + var wrapperElem; + var headerElem; + var headerSeparatorElem; + var containerElem; + var that = this; + palettesGroup.push(this); + + invoker.addEventListener('click', function (event) { + if (!that.invoker.classList.contains("toolbutton")) { + updatePosition(event.x, event.y); + } + that.toggle(); + }); + + function updatePosition(clickX, clickY) { + var paletteX; + var paletteY; + + if (typeof (clickX) !== 'undefined' && + typeof (clickY) !== 'undefined') { + paletteX = clickX; + paletteY = clickY; + } else { + var invokerOffset = getOffset(that.invoker); + paletteX = invokerOffset.left; + paletteY = invokerOffset.top; + } + + paletteElem.style.left = paletteX + "px"; + paletteElem.style.top = paletteY + "px"; + } + + // A palette element can have a header, content, one or both. + + function createPaletteElement() { + if (paletteElem !== undefined) { + return; + } + paletteElem = document.createElement('div'); + paletteElem.className = "palette"; + paletteElem.style.visibility = "hidden"; + document.body.appendChild(paletteElem); + + if (that.invoker.classList.contains("toolbutton")) { + invokerElem = document.createElement('div'); + invokerElem.className = "palette-invoker"; + var style = that.invoker.currentStyle || + window.getComputedStyle(that.invoker, ''); + invokerElem.style.backgroundImage = style.backgroundImage; + + invokerElem.addEventListener('click', function (e) { + that.toggle(); + }); + + paletteElem.appendChild(invokerElem); + + } + + wrapperElem = document.createElement('div'); + wrapperElem.className = "wrapper"; + paletteElem.appendChild(wrapperElem); + + if (that.primaryText !== undefined) { + headerElem = document.createElement('div'); + headerElem.className = "header"; + headerElem.innerText = that.primaryText; + wrapperElem.appendChild(headerElem); + } + + headerSeparatorElem = document.createElement('hr'); + headerSeparatorElem.className = "header-separator"; + headerSeparatorElem.style.display = "none"; + wrapperElem.appendChild(headerSeparatorElem); + + containerElem = document.createElement('div'); + containerElem.className = "container"; + wrapperElem.appendChild(containerElem); + + updatePosition(); + } + + this.getPalette = function () { + if (paletteElem === undefined) { + createPaletteElement(); + } + return paletteElem; + }; + + this.setContent = function (elementsList) { + if (paletteElem === undefined) { + createPaletteElement(); + } + + (function removePreviousContent() { + for (var i = 0; i < containerElem.children.length; i++) { + var child = containerElem.children[i]; + containerElem.removeChild(child); + } + }()); + + (function addNewContent() { + for (var i = 0; i < elementsList.length; i++) { + var child = elementsList[i]; + containerElem.appendChild(child); + } + }()); + + // The header separator will be visible only if there are + // both, header and content. + if (elementsList.length > 0 && this.primaryText !== undefined) { + headerSeparatorElem.style.display = "block"; + } else { + headerSeparatorElem.style.display = "none"; + } + }; + + this.isDown = function () { + return paletteElem === undefined || + paletteElem.style.visibility == "hidden"; + }; + + }; + + palette.Palette.prototype.popUp = function () { + for (var i = 0; i < palettesGroup.length; i++) { + otherPalette = palettesGroup[i]; + if (otherPalette != this) { + otherPalette.popDown(); + } + } + this.getPalette().style.visibility = "visible"; + }; + + palette.Palette.prototype.popDown = function () { + this.getPalette().style.visibility = "hidden"; + }; + + palette.Palette.prototype.toggle = function () { + if (this.isDown()) { + this.popUp(); + } else { + this.popDown(); + } + }; + + return palette; + +}); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/radiobuttonsgroup.js b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/radiobuttonsgroup.js new file mode 100644 index 000000000..712681bff --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/radiobuttonsgroup.js @@ -0,0 +1,59 @@ +define(function () { + var radioButtonsGroup = {}; + + // ## RadioButtonsGroup + // + // A group of elements where only one can be active at the same + // time. + // + // When an element is clicked, it becomes the active one. The + // active element gains the 'active' CSS class. + // + // Parameters: + // + // * **elems** Array of elements of the group. + radioButtonsGroup.RadioButtonsGroup = function (elems) { + this.elems = elems; + var active; + + for (i = 0; i < elems.length; i++) { + var elem = elems[i]; + elem.addEventListener("click", clickHandler); + + // The first element that has 'active' CSS class is made + // the active of the group on startup. + if (active === undefined && elem.classList.contains('active')) { + active = elem; + } + } + + // If no element has 'active' CSS class, the first element of + // the array is made the active. + if (active === undefined) { + active = elems[0]; + updateClasses(); + } + + function clickHandler(evt) { + active = evt.target; + updateClasses(); + } + + function updateClasses() { + for (i = 0; i < elems.length; i++) { + var elem = elems[i]; + elem.classList.remove('active'); + } + active.classList.add('active'); + } + + // Get the active element. + this.getActive = function () { + return active; + }; + + }; + + return radioButtonsGroup; + +}); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/graphics/xocolor.js b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/xocolor.js new file mode 100644 index 000000000..1184ffa5c --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/graphics/xocolor.js @@ -0,0 +1,729 @@ +define(function () { + + var xocolor = {}; + + xocolor.colors = [ + { + stroke: '#B20008', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#B20008' + }, + { + stroke: '#E6000A', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#E6000A' + }, + { + stroke: '#FFADCE', + fill: '#FF2B34' + }, + { + stroke: '#9A5200', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#FF2B34' + }, + { + stroke: '#807500', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#FF2B34' + }, + { + stroke: '#008009', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#FF2B34' + }, + { + stroke: '#00588C', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#FF2B34' + }, + { + stroke: '#5E008C', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#FF2B34' + }, + { + stroke: '#9A5200', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#9A5200' + }, + { + stroke: '#C97E00', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#C97E00' + }, + { + stroke: '#FFC169', + fill: '#FF8F00' + }, + { + stroke: '#807500', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#FF8F00' + }, + { + stroke: '#008009', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#FF8F00' + }, + { + stroke: '#00588C', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#FF8F00' + }, + { + stroke: '#5E008C', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#5E008C' + }, + { + stroke: '#A700FF', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#A700FF' + }, + { + stroke: '#D1A3FF', + fill: '#FF8F00' + }, + { + stroke: '#B20008', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#FF8F00' + }, + { + stroke: '#807500', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#BE9E00' + }, + { + stroke: '#FFFA00', + fill: '#EDDE00' + }, + { + stroke: '#008009', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#008009' + }, + { + stroke: '#00EA11', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00EA11' + }, + { + stroke: '#8BFF7A', + fill: '#F8E800' + }, + { + stroke: '#00588C', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00588C' + }, + { + stroke: '#00A0FF', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00A0FF' + }, + { + stroke: '#BCCEFF', + fill: '#F8E800' + }, + { + stroke: '#5E008C', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#5E008C' + }, + { + stroke: '#AC32FF', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#AC32FF' + }, + { + stroke: '#D1A3FF', + fill: '#F8E800' + }, + { + stroke: '#B20008', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#F8E800' + }, + { + stroke: '#9A5200', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#F8E800' + }, + { + stroke: '#008009', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#00EA11' + }, + { + stroke: '#00588C', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#00EA11' + }, + { + stroke: '#5E008C', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#00EA11' + }, + { + stroke: '#B20008', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#00EA11' + }, + { + stroke: '#9A5200', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#00EA11' + }, + { + stroke: '#807500', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#00EA11' + }, + { + stroke: '#00588C', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#00A0FF' + }, + { + stroke: '#5E008C', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#5E008C' + }, + { + stroke: '#9900E6', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#9900E6' + }, + { + stroke: '#D1A3FF', + fill: '#00A0FF' + }, + { + stroke: '#B20008', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#00A0FF' + }, + { + stroke: '#9A5200', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#00A0FF' + }, + { + stroke: '#807500', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#00A0FF' + }, + { + stroke: '#008009', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#00A0FF' + }, + { + stroke: '#5E008C', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#AC32FF' + }, + { + stroke: '#B20008', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#AC32FF' + }, + { + stroke: '#9A5200', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#AC32FF' + }, + { + stroke: '#807500', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#AC32FF' + }, + { + stroke: '#008009', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#AC32FF' + }, + { + stroke: '#00588C', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#AC32FF' + } + ]; + + return xocolor; +}); diff --git a/activities/ConnecttheDots.activity/lib/sugar-web/package.json b/activities/ConnecttheDots.activity/lib/sugar-web/package.json new file mode 100644 index 000000000..51eb4c1f6 --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/sugar-web/package.json @@ -0,0 +1,8 @@ +{ + "volo": { + "baseUrl": "lib", + "dependencies": { + "webL10n": "github:sugarlabs/webL10n" + } + } +} diff --git a/activities/ConnecttheDots.activity/lib/webL10n.js b/activities/ConnecttheDots.activity/lib/webL10n.js new file mode 100644 index 000000000..f76d66a5e --- /dev/null +++ b/activities/ConnecttheDots.activity/lib/webL10n.js @@ -0,0 +1,1029 @@ +/** + * Copyright (c) 2011-2013 Fabien Cazenave, Mozilla. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/*jshint browser: true, devel: true, es5: true, globalstrict: true */ +'use strict'; + +define(function (require) { + var gL10nData = {}; + var gTextData = ''; + var gTextProp = 'textContent'; + var gLanguage = ''; + var gMacros = {}; + var gReadyState = 'loading'; + + /** + * Synchronously loading l10n resources significantly minimizes flickering + * from displaying the app with non-localized strings and then updating the + * strings. Although this will block all script execution on this page, we + * expect that the l10n resources are available locally on flash-storage. + * + * As synchronous XHR is generally considered as a bad idea, we're still + * loading l10n resources asynchronously -- but we keep this in a setting, + * just in case... and applications using this library should hide their + * content until the `localized' event happens. + */ + + var gAsyncResourceLoading = true; // read-only + + + /** + * Debug helpers + * + * gDEBUG == 0: don't display any console message + * gDEBUG == 1: display only warnings, not logs + * gDEBUG == 2: display all console messages + */ + + var gDEBUG = 1; + + function consoleLog(message) { + if (gDEBUG >= 2) { + console.log('[l10n] ' + message); + } + }; + + function consoleWarn(message) { + if (gDEBUG) { + console.warn('[l10n] ' + message); + } + }; + + + /** + * DOM helpers for the so-called "HTML API". + * + * These functions are written for modern browsers. For old versions of IE, + * they're overridden in the 'startup' section at the end of this file. + */ + + function getL10nResourceLinks() { + return document.querySelectorAll('link[type="application/l10n"]'); + } + + function getL10nDictionary() { + var script = document.querySelector('script[type="application/l10n"]'); + // TODO: support multiple and external JSON dictionaries + return script ? JSON.parse(script.innerHTML) : null; + } + + function getTranslatableChildren(element) { + return element ? element.querySelectorAll('*[data-l10n-id]') : []; + } + + function getL10nAttributes(element) { + if (!element) + return {}; + + var l10nId = element.getAttribute('data-l10n-id'); + var l10nArgs = element.getAttribute('data-l10n-args'); + var args = {}; + if (l10nArgs) { + try { + args = JSON.parse(l10nArgs); + } catch (e) { + consoleWarn('could not parse arguments for #' + l10nId); + } + } + return { id: l10nId, args: args }; + } + + function fireL10nReadyEvent(lang) { + var evtObject = document.createEvent('Event'); + evtObject.initEvent('localized', true, false); + evtObject.language = lang; + document.dispatchEvent(evtObject); + } + + function xhrLoadText(url, onSuccess, onFailure, asynchronous) { + onSuccess = onSuccess || function _onSuccess(data) {}; + onFailure = onFailure || function _onFailure() { + consoleWarn(url + ' not found.'); + }; + + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, asynchronous); + if (xhr.overrideMimeType) { + xhr.overrideMimeType('text/plain; charset=utf-8'); + } + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + if (xhr.status == 200 || xhr.status === 0) { + onSuccess(xhr.responseText); + } else { + onFailure(); + } + } + }; + xhr.onerror = onFailure; + xhr.ontimeout = onFailure; + + // in Firefox OS with the app:// protocol, trying to XHR a non-existing + // URL will raise an exception here -- hence this ugly try...catch. + try { + xhr.send(null); + } catch (e) { + onFailure(); + } + } + + + /** + * l10n resource parser: + * - reads (async XHR) the l10n resource matching `lang'; + * - imports linked resources (synchronously) when specified; + * - parses the text data (fills `gL10nData' and `gTextData'); + * - triggers success/failure callbacks when done. + * + * @param {string} href + * URL of the l10n resource to parse. + * + * @param {string} lang + * locale (language) to parse. + * + * @param {Function} successCallback + * triggered when the l10n resource has been successully parsed. + * + * @param {Function} failureCallback + * triggered when the an error has occured. + * + * @return {void} + * uses the following global variables: gL10nData, gTextData, gTextProp. + */ + + function parseResource(href, lang, successCallback, failureCallback) { + var baseURL = href.replace(/[^\/]*$/, '') || './'; + + // handle escaped characters (backslashes) in a string + function evalString(text) { + if (text.lastIndexOf('\\') < 0) + return text; + return text.replace(/\\\\/g, '\\') + .replace(/\\n/g, '\n') + .replace(/\\r/g, '\r') + .replace(/\\t/g, '\t') + .replace(/\\b/g, '\b') + .replace(/\\f/g, '\f') + .replace(/\\{/g, '{') + .replace(/\\}/g, '}') + .replace(/\\"/g, '"') + .replace(/\\'/g, "'"); + } + + // parse *.properties text data into an l10n dictionary + function parseProperties(text) { + var dictionary = []; + + // token expressions + var reBlank = /^\s*|\s*$/; + var reComment = /^\s*#|^\s*$/; + var reSection = /^\s*\[(.*)\]\s*$/; + var reImport = /^\s*@import\s+url\((.*)\)\s*$/i; + var reSplit = /^([^=\s]*)\s*=\s*(.+)$/; // TODO: escape EOLs with '\' + + // parse the *.properties file into an associative array + function parseRawLines(rawText, extendedSyntax) { + var entries = rawText.replace(reBlank, '').split(/[\r\n]+/); + var currentLang = '*'; + var genericLang = lang.replace(/-[a-z]+$/i, ''); + var skipLang = false; + var match = ''; + + for (var i = 0; i < entries.length; i++) { + var line = entries[i]; + + // comment or blank line? + if (reComment.test(line)) + continue; + + // the extended syntax supports [lang] sections and @import rules + if (extendedSyntax) { + if (reSection.test(line)) { // section start? + match = reSection.exec(line); + currentLang = match[1]; + skipLang = (currentLang !== '*') && + (currentLang !== lang) && (currentLang !== genericLang); + continue; + } else if (skipLang) { + continue; + } + if (reImport.test(line)) { // @import rule? + match = reImport.exec(line); + loadImport(baseURL + match[1]); // load the resource synchronously + } + } + + // key-value pair + var tmp = line.match(reSplit); + if (tmp && tmp.length == 3) { + dictionary[tmp[1]] = evalString(tmp[2]); + } + } + } + + // import another *.properties file + function loadImport(url) { + xhrLoadText(url, function(content) { + parseRawLines(content, false); // don't allow recursive imports + }, null, false); // load synchronously + } + + // fill the dictionary + parseRawLines(text, true); + return dictionary; + } + + // load and parse l10n data (warning: global variables are used here) + xhrLoadText(href, function(response) { + gTextData += response; // mostly for debug + + // parse *.properties text data into an l10n dictionary + var data = parseProperties(response); + + // find attribute descriptions, if any + for (var key in data) { + var id, prop, index = key.lastIndexOf('.'); + if (index > 0) { // an attribute has been specified + id = key.substring(0, index); + prop = key.substr(index + 1); + } else { // no attribute: assuming text content by default + id = key; + prop = gTextProp; + } + if (!gL10nData[id]) { + gL10nData[id] = {}; + } + gL10nData[id][prop] = data[key]; + } + + // trigger callback + if (successCallback) { + successCallback(); + } + }, failureCallback, gAsyncResourceLoading); + }; + + // load and parse all resources for the specified locale + function loadLocale(lang, callback) { + callback = callback || function _callback() {}; + + clear(); + gLanguage = lang; + + // check all nodes + // and load the resource files + var langLinks = getL10nResourceLinks(); + var langCount = langLinks.length; + if (langCount == 0) { + // we might have a pre-compiled dictionary instead + var dict = getL10nDictionary(); + if (dict && dict.locales && dict.default_locale) { + consoleLog('using the embedded JSON directory, early way out'); + gL10nData = dict.locales[lang] || dict.locales[dict.default_locale]; + callback(); + } else { + consoleLog('no resource to load, early way out'); + } + // early way out + fireL10nReadyEvent(lang); + gReadyState = 'complete'; + return; + } + + // start the callback when all resources are loaded + var onResourceLoaded = null; + var gResourceCount = 0; + onResourceLoaded = function() { + gResourceCount++; + if (gResourceCount >= langCount) { + callback(); + fireL10nReadyEvent(lang); + gReadyState = 'complete'; + } + }; + + // load all resource files + function l10nResourceLink(link) { + var href = link.href; + var type = link.type; + this.load = function(lang, callback) { + var applied = lang; + parseResource(href, lang, callback, function() { + consoleWarn(href + ' not found.'); + applied = ''; + }); + return applied; // return lang if found, an empty string if not found + }; + } + + for (var i = 0; i < langCount; i++) { + var resource = new l10nResourceLink(langLinks[i]); + var rv = resource.load(lang, onResourceLoaded); + if (rv != lang) { // lang not found, used default resource instead + consoleWarn('"' + lang + '" resource not found'); + gLanguage = ''; + } + } + } + + // clear all l10n data + function clear() { + gL10nData = {}; + gTextData = ''; + gLanguage = ''; + // TODO: clear all non predefined macros. + // There's no such macro /yet/ but we're planning to have some... + } + + + /** + * Get rules for plural forms (shared with JetPack), see: + * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p + * + * @param {string} lang + * locale (language) used. + * + * @return {Function} + * returns a function that gives the plural form name for a given integer: + * var fun = getPluralRules('en'); + * fun(1) -> 'one' + * fun(0) -> 'other' + * fun(1000) -> 'other'. + */ + + function getPluralRules(lang) { + var locales2rules = { + 'af': 3, + 'ak': 4, + 'am': 4, + 'ar': 1, + 'asa': 3, + 'az': 0, + 'be': 11, + 'bem': 3, + 'bez': 3, + 'bg': 3, + 'bh': 4, + 'bm': 0, + 'bn': 3, + 'bo': 0, + 'br': 20, + 'brx': 3, + 'bs': 11, + 'ca': 3, + 'cgg': 3, + 'chr': 3, + 'cs': 12, + 'cy': 17, + 'da': 3, + 'de': 3, + 'dv': 3, + 'dz': 0, + 'ee': 3, + 'el': 3, + 'en': 3, + 'eo': 3, + 'es': 3, + 'et': 3, + 'eu': 3, + 'fa': 0, + 'ff': 5, + 'fi': 3, + 'fil': 4, + 'fo': 3, + 'fr': 5, + 'fur': 3, + 'fy': 3, + 'ga': 8, + 'gd': 24, + 'gl': 3, + 'gsw': 3, + 'gu': 3, + 'guw': 4, + 'gv': 23, + 'ha': 3, + 'haw': 3, + 'he': 2, + 'hi': 4, + 'hr': 11, + 'hu': 0, + 'id': 0, + 'ig': 0, + 'ii': 0, + 'is': 3, + 'it': 3, + 'iu': 7, + 'ja': 0, + 'jmc': 3, + 'jv': 0, + 'ka': 0, + 'kab': 5, + 'kaj': 3, + 'kcg': 3, + 'kde': 0, + 'kea': 0, + 'kk': 3, + 'kl': 3, + 'km': 0, + 'kn': 0, + 'ko': 0, + 'ksb': 3, + 'ksh': 21, + 'ku': 3, + 'kw': 7, + 'lag': 18, + 'lb': 3, + 'lg': 3, + 'ln': 4, + 'lo': 0, + 'lt': 10, + 'lv': 6, + 'mas': 3, + 'mg': 4, + 'mk': 16, + 'ml': 3, + 'mn': 3, + 'mo': 9, + 'mr': 3, + 'ms': 0, + 'mt': 15, + 'my': 0, + 'nah': 3, + 'naq': 7, + 'nb': 3, + 'nd': 3, + 'ne': 3, + 'nl': 3, + 'nn': 3, + 'no': 3, + 'nr': 3, + 'nso': 4, + 'ny': 3, + 'nyn': 3, + 'om': 3, + 'or': 3, + 'pa': 3, + 'pap': 3, + 'pl': 13, + 'ps': 3, + 'pt': 3, + 'rm': 3, + 'ro': 9, + 'rof': 3, + 'ru': 11, + 'rwk': 3, + 'sah': 0, + 'saq': 3, + 'se': 7, + 'seh': 3, + 'ses': 0, + 'sg': 0, + 'sh': 11, + 'shi': 19, + 'sk': 12, + 'sl': 14, + 'sma': 7, + 'smi': 7, + 'smj': 7, + 'smn': 7, + 'sms': 7, + 'sn': 3, + 'so': 3, + 'sq': 3, + 'sr': 11, + 'ss': 3, + 'ssy': 3, + 'st': 3, + 'sv': 3, + 'sw': 3, + 'syr': 3, + 'ta': 3, + 'te': 3, + 'teo': 3, + 'th': 0, + 'ti': 4, + 'tig': 3, + 'tk': 3, + 'tl': 4, + 'tn': 3, + 'to': 0, + 'tr': 0, + 'ts': 3, + 'tzm': 22, + 'uk': 11, + 'ur': 3, + 've': 3, + 'vi': 0, + 'vun': 3, + 'wa': 4, + 'wae': 3, + 'wo': 0, + 'xh': 3, + 'xog': 3, + 'yo': 0, + 'zh': 0, + 'zu': 3 + }; + + // utility functions for plural rules methods + function isIn(n, list) { + return list.indexOf(n) !== -1; + } + function isBetween(n, start, end) { + return start <= n && n <= end; + } + + // list of all plural rules methods: + // map an integer to the plural form name to use + var pluralRules = { + '0': function(n) { + return 'other'; + }, + '1': function(n) { + if ((isBetween((n % 100), 3, 10))) + return 'few'; + if (n === 0) + return 'zero'; + if ((isBetween((n % 100), 11, 99))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '2': function(n) { + if (n !== 0 && (n % 10) === 0) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '3': function(n) { + if (n == 1) + return 'one'; + return 'other'; + }, + '4': function(n) { + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '5': function(n) { + if ((isBetween(n, 0, 2)) && n != 2) + return 'one'; + return 'other'; + }, + '6': function(n) { + if (n === 0) + return 'zero'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '7': function(n) { + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '8': function(n) { + if ((isBetween(n, 3, 6))) + return 'few'; + if ((isBetween(n, 7, 10))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '9': function(n) { + if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '10': function(n) { + if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) + return 'few'; + if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19))) + return 'one'; + return 'other'; + }, + '11': function(n) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if ((n % 10) === 0 || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 11, 14))) + return 'many'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '12': function(n) { + if ((isBetween(n, 2, 4))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '13': function(n) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if (n != 1 && (isBetween((n % 10), 0, 1)) || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 12, 14))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '14': function(n) { + if ((isBetween((n % 100), 3, 4))) + return 'few'; + if ((n % 100) == 2) + return 'two'; + if ((n % 100) == 1) + return 'one'; + return 'other'; + }, + '15': function(n) { + if (n === 0 || (isBetween((n % 100), 2, 10))) + return 'few'; + if ((isBetween((n % 100), 11, 19))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '16': function(n) { + if ((n % 10) == 1 && n != 11) + return 'one'; + return 'other'; + }, + '17': function(n) { + if (n == 3) + return 'few'; + if (n === 0) + return 'zero'; + if (n == 6) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '18': function(n) { + if (n === 0) + return 'zero'; + if ((isBetween(n, 0, 2)) && n !== 0 && n != 2) + return 'one'; + return 'other'; + }, + '19': function(n) { + if ((isBetween(n, 2, 10))) + return 'few'; + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '20': function(n) { + if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !( + isBetween((n % 100), 10, 19) || + isBetween((n % 100), 70, 79) || + isBetween((n % 100), 90, 99) + )) + return 'few'; + if ((n % 1000000) === 0 && n !== 0) + return 'many'; + if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92])) + return 'two'; + if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91])) + return 'one'; + return 'other'; + }, + '21': function(n) { + if (n === 0) + return 'zero'; + if (n == 1) + return 'one'; + return 'other'; + }, + '22': function(n) { + if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) + return 'one'; + return 'other'; + }, + '23': function(n) { + if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) + return 'one'; + return 'other'; + }, + '24': function(n) { + if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) + return 'few'; + if (isIn(n, [2, 12])) + return 'two'; + if (isIn(n, [1, 11])) + return 'one'; + return 'other'; + } + }; + + // return a function that gives the plural form name for a given integer + var index = locales2rules[lang.replace(/-.*$/, '')]; + if (!(index in pluralRules)) { + consoleWarn('plural form unknown for [' + lang + ']'); + return function() { return 'other'; }; + } + return pluralRules[index]; + } + + // pre-defined 'plural' macro + gMacros.plural = function(str, param, key, prop) { + var n = parseFloat(param); + if (isNaN(n)) + return str; + + // TODO: support other properties (l20n still doesn't...) + if (prop != gTextProp) + return str; + + // initialize _pluralRules + if (!gMacros._pluralRules) { + gMacros._pluralRules = getPluralRules(gLanguage); + } + var index = '[' + gMacros._pluralRules(n) + ']'; + + // try to find a [zero|one|two] key if it's defined + if (n === 0 && (key + '[zero]') in gL10nData) { + str = gL10nData[key + '[zero]'][prop]; + } else if (n == 1 && (key + '[one]') in gL10nData) { + str = gL10nData[key + '[one]'][prop]; + } else if (n == 2 && (key + '[two]') in gL10nData) { + str = gL10nData[key + '[two]'][prop]; + } else if ((key + index) in gL10nData) { + str = gL10nData[key + index][prop]; + } else if ((key + '[other]') in gL10nData) { + str = gL10nData[key + '[other]'][prop]; + } + + return str; + }; + + + /** + * l10n dictionary functions + */ + + // fetch an l10n object, warn if not found, apply `args' if possible + function getL10nData(key, args) { + var data = gL10nData[key]; + if (!data) { + consoleWarn('#' + key + ' missing for [' + gLanguage + ']'); + } + + /** This is where l10n expressions should be processed. + * The plan is to support C-style expressions from the l20n project; + * until then, only two kinds of simple expressions are supported: + * {[ index ]} and {{ arguments }}. + */ + var rv = {}; + for (var prop in data) { + var str = data[prop]; + str = substIndexes(str, args, key, prop); + str = substArguments(str, args); + rv[prop] = str; + } + return rv; + } + + // replace {[macros]} with their values + function substIndexes(str, args, key, prop) { + var reIndex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)\s*\]\}/; + var reMatch = reIndex.exec(str); + if (!reMatch || !reMatch.length) + return str; + + // an index/macro has been found + // Note: at the moment, only one parameter is supported + var macroName = reMatch[1]; + var paramName = reMatch[2]; + var param; + if (args && paramName in args) { + param = args[paramName]; + } else if (paramName in gL10nData) { + param = gL10nData[paramName]; + } + + // there's no macro parser yet: it has to be defined in gMacros + if (macroName in gMacros) { + var macro = gMacros[macroName]; + str = macro(str, param, key, prop); + } + return str; + } + + // replace {{arguments}} with their values + function substArguments(str, args) { + var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/; + var match = reArgs.exec(str); + while (match) { + if (!match || match.length < 2) + return str; // argument key not found + + var arg = match[1]; + var sub = ''; + if (arg in args) { + sub = args[arg]; + } else if (arg in gL10nData) { + sub = gL10nData[arg][gTextProp]; + } else { + consoleWarn('could not find argument {{' + arg + '}}'); + return str; + } + + str = str.substring(0, match.index) + sub + + str.substr(match.index + match[0].length); + match = reArgs.exec(str); + } + return str; + } + + // translate an HTML element + function translateElement(element) { + var l10n = getL10nAttributes(element); + if (!l10n.id) + return; + + // get the related l10n object + var data = getL10nData(l10n.id, l10n.args); + if (!data) { + consoleWarn('#' + l10n.id + ' missing for [' + gLanguage + ']'); + return; + } + + // translate element (TODO: security checks?) + // for the node content, replace the content of the first child textNode + // and clear other child textNodes + if (data[gTextProp]) { // XXX + if (getChildElementCount(element) === 0) { + element[gTextProp] = data[gTextProp]; + } else { + var children = element.childNodes, + found = false; + for (var i = 0, l = children.length; i < l; i++) { + if (children[i].nodeType === 3 && + /\S/.test(children[i].textContent)) { // XXX + // using nodeValue seems cross-browser + if (found) { + children[i].nodeValue = ''; + } else { + children[i].nodeValue = data[gTextProp]; + found = true; + } + } + } + if (!found) { + consoleWarn('unexpected error, could not translate element content'); + } + } + delete data[gTextProp]; + } + + for (var k in data) { + element[k] = data[k]; + } + } + + // webkit browsers don't currently support 'children' on SVG elements... + function getChildElementCount(element) { + if (element.children) { + return element.children.length; + } + if (typeof element.childElementCount !== 'undefined') { + return element.childElementCount; + } + var count = 0; + for (var i = 0; i < element.childNodes.length; i++) { + count += element.nodeType === 1 ? 1 : 0; + } + return count; + } + + // translate an HTML subtree + function translateFragment(element) { + element = element || document.documentElement; + + // check all translatable children (= w/ a `data-l10n-id' attribute) + var children = getTranslatableChildren(element); + var elementCount = children.length; + for (var i = 0; i < elementCount; i++) { + translateElement(children[i]); + } + + // translate element itself if necessary + translateElement(element); + } + + // Startup & Public API + + function l10nStartup() { + gReadyState = 'interactive'; + consoleLog('loading [' + navigator.language + '] resources, ' + + (gAsyncResourceLoading ? 'asynchronously.' : 'synchronously.')); + + // load the default locale and translate the document if required + if (document.documentElement.lang === navigator.language) { + loadLocale(navigator.language); + } else { + loadLocale(navigator.language, translateFragment); + } + } + + // public API + var l10n = { + start: function() { + if (document.readyState === 'complete' || + document.readyState === 'interactive') { + window.setTimeout(l10nStartup); + } else { + document.addEventListener('DOMContentLoaded', l10nStartup); + } + }, + + // get a localized string + get: function l10n_get(key, args, fallback) { + var data = getL10nData(key, args) || fallback; + if (data) { + return 'textContent' in data ? data.textContent : ''; + } + return '{{' + key + '}}'; + }, + + // get|set the document language and direction + get language() { + return { + // get|set the document language (ISO-639-1) + get code() { return gLanguage; }, + set code(lang) { loadLocale(lang, translateFragment); }, + + // get the direction (ltr|rtl) of the current language + get direction() { + // http://www.w3.org/International/questions/qa-scripts + // Arabic, Hebrew, Farsi, Pashto, Urdu + var rtlList = ['ar', 'he', 'fa', 'ps', 'ur']; + return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr'; + } + }; + }, + + // translate an element or document fragment + translate: translateFragment, + + // get (a clone of) the dictionary for the current locale + get dictionary() { return JSON.parse(JSON.stringify(gL10nData)); }, + + // this can be used to prevent race conditions + get readyState() { return gReadyState; }, + ready: function l10n_ready(callback) { + if (!callback) + return; + + if (gReadyState == 'complete') { + window.setTimeout(callback); + } else { + window.addEventListener('localized', callback); + } + } + }; + + return l10n; +}); diff --git a/activities/ConnecttheDots.activity/package.json b/activities/ConnecttheDots.activity/package.json new file mode 100644 index 000000000..4361fdc35 --- /dev/null +++ b/activities/ConnecttheDots.activity/package.json @@ -0,0 +1,11 @@ +{ + "amd": {}, + "volo": { + "baseUrl": "lib", + "dependencies": { + "sugar-web": "github:sugarlabs/sugar-web/master", + "domReady": "github:requirejs/domReady/2.0.1", + "webL10n": "github:sugarlabs/webL10n/master" + } + } +} \ No newline at end of file diff --git a/activities/ConnecttheDots.activity/po/ConnecttheDots.pot b/activities/ConnecttheDots.activity/po/ConnecttheDots.pot new file mode 100644 index 000000000..33079b297 --- /dev/null +++ b/activities/ConnecttheDots.activity/po/ConnecttheDots.pot @@ -0,0 +1,22 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-07-12 12:20-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: activity/activity.info:2 +msgid "Connect the Dots" +msgstr "" diff --git a/activities/ConnecttheDots.activity/setup.py b/activities/ConnecttheDots.activity/setup.py new file mode 100644 index 000000000..ad218b212 --- /dev/null +++ b/activities/ConnecttheDots.activity/setup.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +from sugar3.activity import bundlebuilder + +bundlebuilder.start() diff --git a/activities/Gears.activity/LICENSE b/activities/Gears.activity/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/activities/Gears.activity/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/activities/Gears.activity/activity/activity-icon.svg b/activities/Gears.activity/activity/activity-icon.svg new file mode 100644 index 000000000..7adca3194 --- /dev/null +++ b/activities/Gears.activity/activity/activity-icon.svg @@ -0,0 +1,28 @@ + + + +]> + diff --git a/activities/Gears.activity/activity/activity.info b/activities/Gears.activity/activity/activity.info new file mode 100644 index 000000000..14161e989 --- /dev/null +++ b/activities/Gears.activity/activity/activity.info @@ -0,0 +1,6 @@ +[Activity] +name = Gears +activity_version = 5 +bundle_id = org.sugarlabs.GearsActivity +exec = sugar-activity-web +icon = activity-icon diff --git a/activities/Gears.activity/css/activity.css b/activities/Gears.activity/css/activity.css new file mode 100644 index 000000000..d4daad019 --- /dev/null +++ b/activities/Gears.activity/css/activity.css @@ -0,0 +1,31 @@ +#canvas { + overflow: hidden; +} + +#main-toolbar #activity-button { + background-image: url(../activity/activity-icon.svg); +} + +#main-toolbar #gear-button { + background-image: url(../icons/GearIcon.svg); +} + +#main-toolbar #chain-button { + background-image: url(../icons/ChainIcon.svg); +} + +#main-toolbar #momentum-button { + background-image: url(../icons/MomentumIcon.svg); +} + +#main-toolbar #play-button { + background-image: url(../icons/media-playback-start.svg); +} + +#main-toolbar #clear-button { + background-image: url(../icons/edit-clear.svg); +} + +#main-toolbar #help-button { + background-image: url(../icons/toolbar-help.svg); +} diff --git a/activities/Gears.activity/icons/ChainIcon.svg b/activities/Gears.activity/icons/ChainIcon.svg new file mode 100644 index 000000000..ad41c5904 --- /dev/null +++ b/activities/Gears.activity/icons/ChainIcon.svg @@ -0,0 +1,28 @@ + + + +image/svg+xml diff --git a/activities/Gears.activity/icons/GearIcon.svg b/activities/Gears.activity/icons/GearIcon.svg new file mode 100644 index 000000000..ef736fb4b --- /dev/null +++ b/activities/Gears.activity/icons/GearIcon.svg @@ -0,0 +1,25 @@ + + + +image/svg+xml diff --git a/activities/Gears.activity/icons/MomentumIcon.svg b/activities/Gears.activity/icons/MomentumIcon.svg new file mode 100644 index 000000000..03555f25a --- /dev/null +++ b/activities/Gears.activity/icons/MomentumIcon.svg @@ -0,0 +1,32 @@ + + + +image/svg+xml diff --git a/activities/Gears.activity/icons/edit-clear.svg b/activities/Gears.activity/icons/edit-clear.svg new file mode 100644 index 000000000..bf2a7ac7d --- /dev/null +++ b/activities/Gears.activity/icons/edit-clear.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Gears.activity/icons/media-playback-start.svg b/activities/Gears.activity/icons/media-playback-start.svg new file mode 100644 index 000000000..5dd53fadc --- /dev/null +++ b/activities/Gears.activity/icons/media-playback-start.svg @@ -0,0 +1,26 @@ + + + +image/svg+xml diff --git a/activities/Gears.activity/icons/toolbar-help.svg b/activities/Gears.activity/icons/toolbar-help.svg new file mode 100644 index 000000000..36f763c33 --- /dev/null +++ b/activities/Gears.activity/icons/toolbar-help.svg @@ -0,0 +1,32 @@ + + +]> + + + + + + + diff --git a/activities/Gears.activity/img/ChainIcon.png b/activities/Gears.activity/img/ChainIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..a4588dc85c00a52fb1c4a6e14ca5dc5ebd79d895 GIT binary patch literal 2169 zcmV-<2!{8GP)@ws8uq;aB^j8+Av$%_8tveOE?}T}EznL2b5~!Crm{$-`m?S-qV^dk(<3_UOQU?0V2d??RefI;aG&Z*d>=kU-l( zBb_PNW}Gh z@v;&Bq5%^CG{*r>2R{m$lDR!E<^a0F*Zw2nrqG6jo5TK3_~RC2KftnB0Q?!Ka;n|{ zrb;w=+gat7W!-35);3^T4!39eK97Phdjkoh&FiE3d|iN^|DkuWQzPiCfF550G!uUQ zZw1n~%WB}+aMv9&M!=S_1)Bn(QqXcA{5sHS8HRkWIUi&vu@}vb5N#L3vav=kM}qRO zoMie{EQ?CuPr$WC^<%(7iA3-<)=j`Tpxm(v;M>HvO?Mrr3|QrSUL$$(vr7y;KN9;< z*6jHo=jV$}L?0EItP5K98#yfsjM-24v3TFSlAzo3g#Qe&J{V0iJER>{9jgGK%Rv7z zg1is9E>HN2fjHg-dx7;({XXnB1o-PS8Dr-OKMQ1RVyG2#l><`;{1liM621o5Z$PAg z2J8hdssML`Ea-mD&F!58{NeoA1OR;n)Z+$79s*4;fNpb=`CCW z1o#W+7e>eQLhtmf@gu_D2}-#Eb_p(i&Ii=sB-0)M`vYJ`K>aPiBwyXVKDeOU=3_=Y z!F|RCiv;>kPNoR(Kk+*MM+=;uPGpWToO_G}YYd?DvwJsC6=Gy;F#N1=>K-)eTAjKI zU?VJ%5B`BrT;bsN{>JD0DX6;7Nai5+qv|((J#@K~H%*>!M&=@KeyuA( zRxAQw@7fy99~STea4Arq$0F4Y4EMo~1o|Wld>rU=Tg86K67VC?t-j8Ak}NyGkput@ z108go|4~p971(dDVZXtZVLvuM>#P}}AD)1#fIDEvV()^i)u7TS@VNxI*Kpnh6XexLaMqK2j@>3ZGE`>^(g5WOgQS0rtH*4iQXS4U|QIzZQF2EuGs7 zpi}dK&mus>Qa3=;ZeT9<`+MB*&jX=*4m2C@LFiT@oCHS@;H5X*AYY9eI&J?1LiZeK z2A*RyZ70KCL%#$7l|j;Z&QA(z3nf4&@F`#{5O`{1$$ma^saRYugXha@wB@3G{oY{X(AL-vbuF8LjZ5>9ZH~ z#bUtc6u=fi-}j1es2!%Kp}K*m2zRVi1DXgudtF}%!LhFu3qF(p!6Kvtze~d8o9ypB zzt>3uR|E6ix^lsu<#=|Pl0C3vVKL!D3*dSP?Hv5o6~+2T)!EX z30ma4t_^fevEd^LU_(7P2G;F_RLTeBSdXBygY5tCuo^U~*x-2yV2p+ie3H(yd#ijTrbV>00000NkvXXu0mjfCfexS literal 0 HcmV?d00001 diff --git a/activities/Gears.activity/img/ClearIcon.png b/activities/Gears.activity/img/ClearIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..920b4b9ec87dd1379972ebaa556dfd510a39c450 GIT binary patch literal 652 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=EX7WqAsj$Z!;#Vf4nJ z@c#p0#!c6zY5@f$OI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!zA;`DTJ z45^s&cDA?Qp#X{2^Z7h>+ZYx35*-W-gyj?2bsVQ3(0*u|uB~;`Lu|7!o9k4UTZE4WYuM-j_l)98!3(hUw%_u8aDJ(dBi?5pSbLaKff1B|&M9;5z zsmD0+^!#--aUT8-G5-XkpGx<-Wd;A5(8n-w^^#A`2N~9eX;0Oxy(qq6@t>rP0rT_X zOIT~B+5g*BZ6Wh)lg$E6hbLAm|74gPs<8{7?ItbDSoo7&Wy<`sXN(l$Hpzwd9O%fa+s^YQ`z zh7&)JPu0(r=W0?YS1o_IngTe~DWM4fwH^#z literal 0 HcmV?d00001 diff --git a/activities/Gears.activity/img/CloudIcon.png b/activities/Gears.activity/img/CloudIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..2bbb714be85cb4c6d5fceae3ed9ca145346bd13b GIT binary patch literal 1443 zcmV;U1zh@xP)cq3!wzt-p(HKb{;i^d<34#9Mc_0{sx$Cl`*zqziZf&1_7^F46pa@bQotNu-Ah1`s4)YpA~cJ z@9^%d8}A&#=a8M%5b&^J>}btQdpG8F2Ig^KiK~63Nbnj%-;T)13VnRbJHRgiy#ct+ z)wmJ?9frO?A*lRBWDNM&s9ox6?j}IT_REdh+9yDZ86jTbYB=2tXdxWi*ZTCIl%k-Woz+#xJkGaW%pp6Dr0iH-632LJq zgumAv0d7hO^bDZ0N*j&nQ>lsb3!tGo0`vfzEzFJsF93_H0KN|Rr)2LqFeicItFV7D zI!%NN*=Pw8N;RVQ1J|9~VCnfm^xK{Vq1bt=cbK}`L1K@H)-vexNwiYR(GspUkC!^`Y zDi4_T47d=TG%2Y}TmRY2s= ztl;IBE+5DFsr@o@9_s%uP$!^HfNuc4#Ww*%hMwt$XA|E9Yz_SVE7@n2g~ZSQ3{63x xTG1&uyAT=4eR%H#coyGFz`N85@D0GX{0qL(9)mSa?z#W~002ovPDHLkV1gF;mrDQu literal 0 HcmV?d00001 diff --git a/activities/Gears.activity/img/Gear.png b/activities/Gears.activity/img/Gear.png new file mode 100644 index 0000000000000000000000000000000000000000..57ba33e47ce4f790d5db4bbb310102e1e2ec1ff3 GIT binary patch literal 2607 zcmV+~3efe5P)00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D3B*Z6K~!i%)mnK_ zPTe23{92SOg%E{=hEjx(r3lr?Qb?LCSt^VPW1pl3Ga)oIjU{WzRALHQG7?!Ml8~*D z<)uNkjAico={d)By3c*K`#gDm@8_@cJ@-EMIiK_0&%O1y|2XP|KyYxdy}fA(6n#moMYu;##z5(W6I?&!0cjSsALUtBs6|zJ2?K@ywYsH*eld<_SK1 z`qZ{AidR=mn{eNT6T8eqLT)__rwe{o2kI7gvBogrT_07o0zyLc9gE@BW7?}qmA|j%rqp84gty;Az zFE1zK+qZ9JfH+J27G*c($mw) z*ulYp&?Yc5GyC}QBNb$TOKjb`Rn)H(K`jFEK_fdm+uPfl3LFQ2aCUb7`t>WuPzY-n z`}Xa_orpzPnVXv%>$M0|bR?AfzTn>N@KI;#q*39Med8qa<7=n<7=c>er( z%a$#vAOnnQ)~s36rcHwfv#W%S!PfsLLuqL#o#nU>A3nf|slai!Zr!r9v|PG$DSmt8 z$PwIJR#rx@DTOiucvfg=sFRaZWMm}Nosf{g26Yn?ld7sJD##WZ7#NtCnE343Gdfip z;7ms{6c-nF@7|q^larI-8(23HuU)$a&Od+tB%*RCBaoAmV{B~v?%g|#@%JzQS65eF z6?S%Z?2c**{1zHGbm-9h`STH}*myc(#E6uX6e_5K3KQzzzkga<8WlKhQy?W zQ^07~tXV?^HK5vwM~@!8Y11Yu)G?67Ei5daJb6Op-#~y;q!ZNyu;tJx9zo^01WYs_ zAb<+8x+5zq3-Jw+ZOW7>q9ve^fapzAr%qkAY#9~m5-~9`FwPe*UZ55X3JSu;4H+^7 z2?S2U7l;DXBJle4Yy4*H*s)1TN!PDmCj}XRCnD+m9VMXt!+!JYrn4LuA0OYLLkIc& ztwmtMgbCPbl!}ulPj+;4G%zsm^z_`aWsAGJ`=UjQ=uLGGe&cxL=jS7DVo#~e;12<8 zKd2*$QCaq&%*;%L1z4f{L;o7?-@lJYc>45dD%Zvz0$8Gpi_7%s)AhJkd0_ReTepTQ zQ&|o|Cr0rIvq7OkWxb-Jq5|oXFEKhScDky+B0buv;-G+vS8ub=B$Kk_=5x(sipq2IW^Fz^2?@&SjF$)@`g8qPr zfwh@7}#{ z+_*txodDG<{$tdrQP`28Lx&<1A|cXiQo%2M%$PB3xmrWO%E}6Ll4uzx0Vqa#_3Fh& zVb~{<5qcmhYY0V4C#WF+vq7!r;NY--|9;XyN(R=Af0EB3-~c^Hvn2#{^d$02C#WGn z041R2Y1F6@iXVg^X$nCMSh;c~6>0;XS5Qzu1x4MPz=0Po zT%dv`Kq^?bZXGP#*48#LF;QTjA_Sg3eTwaeBT`utl$4afU(pIu`8QCO!8f!#358*! zV?oP>WXvBa)FObT&z(Eh+}wP@f(7iMl}1453=9lpIbiju4igd*sH`2Jkj0A^BlHuI z641%gc?1UsLl3063kwUw-m~Y`+GE$QT{C9PAmbVWYuB#T)zW#umau4O14&Iw%R%rh zFE5A9@Wl=qfBvMWh5$TIqqC&H2_!F5Q&VeeYiDO?QtZUU#Gu&WH>HNaxpU`m`Ocj? zFxGz<=qfjB14~Owm^T%~Kz#rN5U1L;YuAcFg9Z%*N6LR0$iDUK*H?7hO+X2V0=}sC zihBF@?IV!P@MMfKy?UsWBc~)5t;>|MT7uW4-eMk>Z_}(qoSg+v$M;}%Gl+iCN(xT78r$) zz{QIfMNc8|P&%r6ppHby_ww>W_{U;Um?bACqoYSXD_<$rB7icZY15`i3sjb6LG%eo zABKj8irh%IC@LyK{mj<{upG2wLB+6oEaCZsbH6BDv-_H4q*$%$1?-Qn!nv+!5Z<;p|EiIFwrUr1pR zQB|}jstXJnH0Z#A15{875dxy{_V(VhXAc#0io(J|bPMoKcFR%B!eZ5V*{T+S%F0SO z1M5I=64Z(4^rRg$@JLBXLCD^@bEl`LC+e}{;$nJ%o)hT4uA`l)PzGB4+DsUVk z8Uh@AJs=IS%0e1KDaC%Fg9J~$L&Kz*nHl?)2q?){ z{A$KfKrI5m`Sa(OELkE@D7rTGt0DB(!-fqbV?RGXUchAYmp#sMR1qFqLg%KO`_4P%M zOXej&D+0V>04Tp=V`IsfpmF2IyuEem)QSB93l;(;VBN4NA0Hn!#Aynx2&AT_+S}Ws z?nO9e6|#G1XlO-61)bF#S`mP=Zrira!@~o0B0?mc{SCMT zN<(-ydO}`NEKszo`CXY3XhndBXwX1VC*S%Z*T%)g(OF$XHw5&E|2P4T`xiy_O^&s) RRHgs`002ovPDHLkV1lsw#@+w` literal 0 HcmV?d00001 diff --git a/activities/Gears.activity/img/GearIcon.png b/activities/Gears.activity/img/GearIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..c88e713cf7397bf2fba6265acc04975a665a066e GIT binary patch literal 1702 zcmV;X23h%uP)$fu&H1iQ2=oL@F{REK*FZKQz~YXh>Ta{y#*K-P2)fUFG&OCnMxBG-#Z zMH2d29RR?7U>k6++aMy9A~G_JvepPd&itL|(e@Vbk%)X4R9R~T;7{}S77^){7mCPa zV3hg#V^C!+761|HD zvw@wK?1xZeW$kPM06GI(EvFiQrN9Hg@0Q$)XL{%>YgGUkqq_n-e4O>-IY0$)GcX-k zW5Q-&s{+7)aETQPVfeI%Y<{Z)z<_WCQ0F*v23P@%2UY|1MZm}b^MKBo000aC9tJLr z_<%KW6TVXwvj+>9vq+@a5pg1#O`4ar&bmi zXPeXS4M2SDPRRLMW9&p16yTIGXtUGS1vq4{+(31J_?}Y0hF6__Zvy3|0wBLP{|of7 z~`FFshG(F@f zV6>&H8?esEKQ^*2Je)cJEC!DHIG^V|v1fpV^jhF@OWrS_5rz16qo*&h&yv4}-a*3X z`p4)Q2#f`u2R;C5lK{Y;>c@defB?N?0801OCIQgGIb-?cuk1Fi(F1uk^@J#A!LEZNs-0?-!N zowD-{=wxOs@RE7!znZo?oqkout}187)Wik=U150v7y)##HfKpRK{_f$xE3C2+nF&oj2=zt8fp`!--QMQQ+HX26ULoppH;=KKNd zLT7}YIew^+4iNL>%wSnMzXE4#1KJQE+3{oFqNq}85qb@%^Xr=;ZB$Ct+XAIjy)sC| zAqlEOM5duv<1FYfumL?2bLQ`HrPSlGHTz8ea-at=2wk1cdL3~2vbZ5o?gfg7tORO+ z?j&<}pwJ<7K?1r!??}sm|6*(McDafcW-V!lFoXu?=B!B=L08| zQ<4&q?Z7ofAC6B4fSyXJ6KM?)q$XJB;ruYtaeg&$#QI(ZJdwgUjT9+vbXeF^bs0K2 ziBm{fXstW%S@Pq+MQMdV6z6-<_@kv9*0nP_T@C86n;Wch#uAY~#oz#yg6Tu0s!U0JFC1}mjDBsOSFN)&*i1Bnbqm)yTmN}Vpr wpaH040JKzUvHvntDmn*1)&^uvXFUM_11OllvZ3ce#Q*>R07*qoM6N<$f_Tshwg3PC literal 0 HcmV?d00001 diff --git a/activities/Gears.activity/img/HelpIcon.png b/activities/Gears.activity/img/HelpIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..42b386b032ee1e3e9f348b625c62a748b52dcdd7 GIT binary patch literal 1810 zcmV+t2krQYP)(Cm(~(6&mie;Hhwa4&O_ng1EN00bOf<$PAIM^yg9c(uMiI6_1qHSE2NhRJ3sk7B z&|2EwvxnP;ioLh@+&?YJ_>$jCZ_oXG-*?Wr_uSvPmw;0IpHYx+0{O$=S z*X)E45JK`wAt!eMLf!(f2f$_kZ>GhD()qZZEZnSRneFJHdw;CX(QW=D8zZ0w)?`}YsAlCthNRKTLAEK3AkLYKCQ&3;C8#6gb=wLcmP|p zG{7NOd}U>&mr@$n+5k!^J#yp-%M#n6jRC?_oZO`J{Q2|UGPJ}WJ$lsN)z#%_Z*T98 zMx$;y224&)USr_`V2?Tj00V$gmZVp%TyaQ=2!%qU+qP}Ho8E>H!XG+x$Vn-UW;4!Y zGR-jX83kZaW58}k$7yG0r>Ft5v$MlSqj5p3?dj8}CC#Ih(gOz$3^H=sEu}GAMQC4^ z<1fK(x39_cdms?FwQ=Ldns7L56l;6<@Zk?)v6y5LWYeZi5k^W`G>G>;fl!@c8UY4_ zA@1>boaw#h_xo>@l$3gw9& z=H_b`E?jW5w6r+O%gf&n27~M5wC6ZZ@;Sic@vzo0O%Sw*Kzsz80Auf~jr#ifk!;t1 zUAuPOQgH4Q>=!|xE0^LM8yklbiNtgk{c*-;gH&`WFsja8*TK9%A)F5d$jq5MNeS7NC^ISjGE%zM;axLbXNLN|xJT#>I?Ul$Dh&04QW> z<8(TEtE;Ow$K!FPb{SBV`RB|dU>$TA0W~!>9+{oa`+UB^rluy=haag}%X$%fmTLnN ziG<&7w|AS(X1B(<14`}!EM+K73^;b|m|lAfx0Nh~y%NV1feOZo;>nXIYUjBaJYol= z40xb93y|fCjuR6T&P>C9et!NA$8ixoassGVat(MD z!j6lkWGX5urbb6c|DtJo1&Kt$ufv5e6(LBK$wdzcCj71wDQdM^@0~n(vZt=DZnU7F zAfZCK9|xcy*Wxq354C(Qt>~nXv_hq0;M6bKS)#qgY&nYke{17dY`bxiw^8RC0( zJ**7`6Mj}oa>tGxu1F;EaGCh{sZ*yMD#ib*OpB66f(hRMh$|s$Fc{*kt*uwb$H)6S zJ3AdVo9&JY;c)<8E7zg4pe56i;n#O_&Zv(iSL(!?Y4&WX2{MDX7 zFrf&*x6o-bWm$LuGy^EmhPxIgSu&y+Kr3{pBS3f&@&|}jcUOb~OGcOgdobT=SlR#Jkc?}m&3 zCICDDrSmzbk{1J35LUk%%r}92W7U9s6UaCI12ClvlKtNEZ2$lO07*qoM6N<$g2ad_ A+W-In literal 0 HcmV?d00001 diff --git a/activities/Gears.activity/img/MomentumIcon.png b/activities/Gears.activity/img/MomentumIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..ac4734be00130f9b8706ede2ccb22ffddb2009a4 GIT binary patch literal 1818 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 IM6N<$g2fdd7ytkO literal 0 HcmV?d00001 diff --git a/activities/Gears.activity/img/hand.png b/activities/Gears.activity/img/hand.png new file mode 100644 index 0000000000000000000000000000000000000000..a003fdc182f2c808d75dd994816cac5f6333d33d GIT binary patch literal 2541 zcmV{+P)#AZ!@oSCQh-aY5sbI}SO|LMg3sCnea~5B!X`u$TVG9PcMV@e(B6%VPx^VnSaTpk|J`oI zWr)@mc#YKvI6K%&%Y>+3$zJ+N$Ug+heG7hDGaTwF3}1df86xQE76Ps|_R>$C)Ag{7 zlws`pN9^I!bcp%3g+}jUF?VjCK2(EKyzUelsU_xY2A)=4(9_F86ZbN2`N3n3~kU1Uj~+i>fz(>BYYxbh(~Crrf=0?u~9HgqXr zpRB>mtpW^x`bv1gHorC#x4G2LbmiGW<>9~R>H246ACi6Nfpy{*;Pu*?2wEYTx>yR4 zxHJR5t^vBMyC9ldFj2IQMczqvitJlS2g!wtQ%AFSJa4r(>K}uyawnqVI5lT047DG_ zXTAY-@w;GqHT34N0Nc`Kd@3F=BIcifbF_>-aK)r4ggti<_TELnA*1^j;W2!L$uHKz zt@{e9(sc=X??>DLEo4rXDh|Bph=?i#on5eYeU6a#4oyM_FTLO11P_(pHhvl7bvs!Q zOQr@9-;faY^$TJD5WArZlM~6^#ShePYl7QQ3p=%^W%v@*O@}d1xSBb5GUI@qBWqd7 z-N|dAJoIO$Xz!sjy+IMLz&uz6qr3!DEypof@(x?PV!7$>Xg26Pl&ExCtJnZ zHQk;MX(Thdv4(7xtT-^!gOIHkiv2Idf;Ks3d-4*gj(o5Kc9fQ;&mwVA3sc>pgm1h# z5%DSD94h5=XdODq3Ybe)N%qD92Mo?wdSkh7y5a#Xe41KVTJmV20`|Uhglv7VDftLS zMH_4Bwc&Jqgt#ujMK{~OdBun!a=NbT#KETQ(^!7)u$xGIvF1^U_U zxLq1o99-_8>$hF~m5H#s59+h;B1Dl{8waQ(ol|A7_kT%8TZEB=FSs)EK6>O8M_e*M zG_+#k)Zc=d^P6KVbv8^vZ!atzxg0F@b^oQ7mO8>k|1jdt7=;@}#?XmQySq@X*1?aj8 zFQ33&**+tRhDV597qs-x&vVH)@n>gJ=A{$>_^>wU981Wt=(0-d$s zHr?QinsiW08B~yiv(WV%BR<$Sm*&JrjPs){)%I)#o+8^w_Aj#Er5rDskM57dk;f!p znh!`nO!du=$$mk0)ZSSfXG0sZ-GQkghwaS7x_hcqX%Tkz!8?5&`l@WG&%8tEp@d{@gKgvt#`0hH zu>m3b9@!64|4SZ|fFVtk?8VLc%6*|ZV%oSk1Ic&`j{XwX2r=i~OJD*l>Qh2AwZcWX zZ>-ND*cv1TZiIFCG^R>7uz|6w3;*GFsmc-f z4KNSnVxnX%lUKV^@Rgf3B z(U<3V$y2+Q99;z0a=v2*nm$7vL?&UZjU+Eg16FK-t?vvx<^}>$1|++nIQ<4I|3~*q zJc4s|TAi`7E1dlm2_B3vQJf`tV`{>9-kWeKu5ylKV9U;xaRt{-1o=!qG4S=-8u?d& zhaH2KM2X@{Ox@g1iuFB=?0?=#{{L_v6vq52lKZ1%Dj{|5+iCp#VLFPisNjT5@(Gfy zga0$FEV*ti2+SDIYiS?>S4Y%QLt3no6j2GL+m8_jZ^hu}tC*x{Pb=9 zCsDz8vN0K0LxCqMsmZQTxyz~3FOiBpPr*-Oyml{!%ihOe-mCG{=gboih2mu3?j$Xe z(&f}hCMEfmEzDM*T`zfzr1Xj9IMZovfPL{;Rh9hf&mbop=sbhzwgODlAHYb(2N*p4 z7CZOm6o)*V3xcG}lY!g0%x2Weq_*GILZ7y+>7IDi*p?s8BtTqko2X9ie&ixi?l~Cl zo`kNGz7v~vC)Bmu5{g4B47Bby4-&-RRGu%8>NaJ?uV?$tKWFJJ%rbX>k!9>Wm1Sr@ zk)>-p{zskc$a7lRp=W1W_b!`m&R(Wz&VKrvQu6-*w}rQ2of*k300000NkvXXu0mjf Dtcc;B literal 0 HcmV?d00001 diff --git a/activities/Gears.activity/index.html b/activities/Gears.activity/index.html new file mode 100644 index 000000000..a16b41255 --- /dev/null +++ b/activities/Gears.activity/index.html @@ -0,0 +1,34 @@ + + + + + + My Activity + + + + + + + + +
+ +
+ + + + +
+ + + +
+
+ +
+ + + diff --git a/activities/Gears.activity/js/activity.js b/activities/Gears.activity/js/activity.js new file mode 100644 index 000000000..35c68d847 --- /dev/null +++ b/activities/Gears.activity/js/activity.js @@ -0,0 +1,121 @@ +define(function (require) { + var activity = require("sugar-web/activity/activity"); + var radioButtonsGroup = require("sugar-web/graphics/radiobuttonsgroup"); + require("gearsketch_main"); + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + var gearSketch; + + // Initialize the activity. + activity.setup(); + + activity.write = function (callback) { + console.log("writing..."); + var jsonData = JSON.stringify(gearSketch.board); + this.getDatastoreObject().setDataAsText(jsonData); + this.getDatastoreObject().save(function (error) { + if (error === null) { + console.log("write done."); + } + else { + console.log("write failed."); + } + callback(error); + }); + }; + + gearSketch = new window.gearsketch.GearSketch(false); + + // Read from the datastore + var datastoreObject = activity.getDatastoreObject(); + function onLoaded(error, metadata, jsonData) { + if (error === null) { + gearSketch.board = window.gearsketch.model.Board. + fromObject(JSON.parse(jsonData)); + console.log("read done."); + } + else { + console.log("read failed."); + } + } + datastoreObject.loadAsText(onLoaded); + + var radioButtons; + var gearButton = document.getElementById("gear-button"); + var chainButton = document.getElementById("chain-button"); + var momentumButton = document.getElementById("momentum-button"); + var playButton = document.getElementById("play-button"); + + var buttonNames = { + "gearButton": gearButton, + "chainButton": chainButton, + "momentumButton": momentumButton, + "playButton": playButton + }; + + gearSketch.selectButton = function (buttonName) { + radioButtons.setActive(buttonNames[buttonName]); + return this.selectedButton = buttonName; + } + + // Gear button. + gearButton.addEventListener('click', function (event) { + if (gearSketch.isDemoPlaying) { + gearSketch.stopDemo(); + } + gearSketch.selectButton("gearButton"); + }); + + // Chain button. + chainButton.addEventListener('click', function (event) { + if (gearSketch.isDemoPlaying) { + gearSketch.stopDemo(); + } + gearSketch.selectButton("chainButton"); + }); + + // Momentum button. + momentumButton.addEventListener('click', function (event) { + if (gearSketch.isDemoPlaying) { + gearSketch.stopDemo(); + } + gearSketch.selectButton("momentumButton"); + }); + + // Play button. + playButton.addEventListener('click', function (event) { + if (gearSketch.isDemoPlaying) { + gearSketch.stopDemo(); + } + gearSketch.selectButton("playButton"); + }); + + radioButtons = new radioButtonsGroup.RadioButtonsGroup( + [gearButton, chainButton, momentumButton, playButton]); + + // Clear button. + var clearButton = document.getElementById("clear-button"); + clearButton.addEventListener('click', function (event) { + if (gearSketch.isDemoPlaying) { + gearSketch.showButtons = false; + gearSketch.stopDemo(); + return; + } + gearSketch.board.clear(); + }); + + // Help button. + var helpButton = document.getElementById("help-button"); + helpButton.addEventListener('click', function (event) { + if (gearSketch.isDemoPlaying) { + gearSketch.stopDemo(); + return; + } + gearSketch.playDemo(); + }); + + }); + +}); diff --git a/activities/Gears.activity/js/hammer.min.js b/activities/Gears.activity/js/hammer.min.js new file mode 100644 index 000000000..de7e79e64 --- /dev/null +++ b/activities/Gears.activity/js/hammer.min.js @@ -0,0 +1,7 @@ +/*! Hammer.JS - v1.0.6dev - 2013-07-31 + * http://eightmedia.github.com/hammer.js + * + * Copyright (c) 2013 Jorik Tangelder ; + * Licensed under the MIT license */ + +(function(t,e){"use strict";function n(){if(!i.READY){i.event.determineEventTypes();for(var t in i.gestures)i.gestures.hasOwnProperty(t)&&i.detection.register(i.gestures[t]);i.event.onTouch(i.DOCUMENT,i.EVENT_MOVE,i.detection.detect),i.event.onTouch(i.DOCUMENT,i.EVENT_END,i.detection.detect),i.READY=!0}}var i=function(t,e){return new i.Instance(t,e||{})};i.defaults={stop_browser_behavior:{userSelect:"none",touchAction:"none",touchCallout:"none",contentZooming:"none",userDrag:"none",tapHighlightColor:"rgba(0,0,0,0)"}},i.HAS_POINTEREVENTS=t.navigator.pointerEnabled||t.navigator.msPointerEnabled,i.HAS_TOUCHEVENTS="ontouchstart"in t,i.MOBILE_REGEX=/mobile|tablet|ip(ad|hone|od)|android|silk/i,i.NO_MOUSEEVENTS=i.HAS_TOUCHEVENTS&&t.navigator.userAgent.match(i.MOBILE_REGEX),i.EVENT_TYPES={},i.DIRECTION_DOWN="down",i.DIRECTION_LEFT="left",i.DIRECTION_UP="up",i.DIRECTION_RIGHT="right",i.POINTER_MOUSE="mouse",i.POINTER_TOUCH="touch",i.POINTER_PEN="pen",i.EVENT_START="start",i.EVENT_MOVE="move",i.EVENT_END="end",i.DOCUMENT=t.document,i.plugins={},i.READY=!1,i.Instance=function(t,e){var r=this;return n(),this.element=t,this.enabled=!0,this.options=i.utils.extend(i.utils.extend({},i.defaults),e||{}),this.options.stop_browser_behavior&&i.utils.stopDefaultBrowserBehavior(this.element,this.options.stop_browser_behavior),i.event.onTouch(t,i.EVENT_START,function(t){r.enabled&&i.detection.startDetect(r,t)}),this},i.Instance.prototype={on:function(t,e){for(var n=t.split(" "),i=0;n.length>i;i++)this.element.addEventListener(n[i],e,!1);return this},off:function(t,e){for(var n=t.split(" "),i=0;n.length>i;i++)this.element.removeEventListener(n[i],e,!1);return this},trigger:function(t,e){var n=i.DOCUMENT.createEvent("Event");n.initEvent(t,!0,!0),n.gesture=e;var r=this.element;return i.utils.hasParent(e.target,r)&&(r=e.target),r.dispatchEvent(n),this},enable:function(t){return this.enabled=t,this}};var r=null,o=!1,s=!1;i.event={bindDom:function(t,e,n){for(var i=e.split(" "),r=0;i.length>r;r++)t.addEventListener(i[r],n,!1)},onTouch:function(t,e,n){var a=this;this.bindDom(t,i.EVENT_TYPES[e],function(c){var u=c.type.toLowerCase();if(!u.match(/mouse/)||!s){u.match(/touch/)||u.match(/pointerdown/)||u.match(/mouse/)&&1===c.which?o=!0:u.match(/mouse/)&&1!==c.which&&(o=!1),u.match(/touch|pointer/)&&(s=!0);var h=0;o&&(i.HAS_POINTEREVENTS&&e!=i.EVENT_END?h=i.PointerEvent.updatePointer(e,c):u.match(/touch/)?h=c.touches.length:s||(h=u.match(/up/)?0:1),h>0&&e==i.EVENT_END?e=i.EVENT_MOVE:h||(e=i.EVENT_END),(h||null===r)&&(r=c),n.call(i.detection,a.collectEventData(t,e,a.getTouchList(r,e),c)),i.HAS_POINTEREVENTS&&e==i.EVENT_END&&(h=i.PointerEvent.updatePointer(e,c))),h||(r=null,o=!1,s=!1,i.PointerEvent.reset())}})},determineEventTypes:function(){var t;t=i.HAS_POINTEREVENTS?i.PointerEvent.getEvents():i.NO_MOUSEEVENTS?["touchstart","touchmove","touchend touchcancel"]:["touchstart mousedown","touchmove mousemove","touchend touchcancel mouseup"],i.EVENT_TYPES[i.EVENT_START]=t[0],i.EVENT_TYPES[i.EVENT_MOVE]=t[1],i.EVENT_TYPES[i.EVENT_END]=t[2]},getTouchList:function(t){return i.HAS_POINTEREVENTS?i.PointerEvent.getTouchList():t.touches?t.touches:(t.indentifier=1,[t])},collectEventData:function(t,e,n,r){var o=i.POINTER_TOUCH;return(r.type.match(/mouse/)||i.PointerEvent.matchType(i.POINTER_MOUSE,r))&&(o=i.POINTER_MOUSE),{center:i.utils.getCenter(n),timeStamp:(new Date).getTime(),target:r.target,touches:n,eventType:e,pointerType:o,srcEvent:r,preventDefault:function(){this.srcEvent.preventManipulation&&this.srcEvent.preventManipulation(),this.srcEvent.preventDefault&&this.srcEvent.preventDefault()},stopPropagation:function(){this.srcEvent.stopPropagation()},stopDetect:function(){return i.detection.stopDetect()}}}},i.PointerEvent={pointers:{},getTouchList:function(){var t=this,e=[];return Object.keys(t.pointers).sort().forEach(function(n){e.push(t.pointers[n])}),e},updatePointer:function(t,e){return t==i.EVENT_END?this.pointers={}:(e.identifier=e.pointerId,this.pointers[e.pointerId]=e),Object.keys(this.pointers).length},matchType:function(t,e){if(!e.pointerType)return!1;var n={};return n[i.POINTER_MOUSE]=e.pointerType==e.MSPOINTER_TYPE_MOUSE||e.pointerType==i.POINTER_MOUSE,n[i.POINTER_TOUCH]=e.pointerType==e.MSPOINTER_TYPE_TOUCH||e.pointerType==i.POINTER_TOUCH,n[i.POINTER_PEN]=e.pointerType==e.MSPOINTER_TYPE_PEN||e.pointerType==i.POINTER_PEN,n[t]},getEvents:function(){return["pointerdown MSPointerDown","pointermove MSPointerMove","pointerup pointercancel MSPointerUp MSPointerCancel"]},reset:function(){this.pointers={}}},i.utils={extend:function(t,n,i){for(var r in n)t[r]!==e&&i||(t[r]=n[r]);return t},hasParent:function(t,e){for(;t;){if(t==e)return!0;t=t.parentNode}return!1},getCenter:function(t){for(var e=[],n=[],i=0,r=t.length;r>i;i++)e.push(t[i].pageX),n.push(t[i].pageY);return{pageX:(Math.min.apply(Math,e)+Math.max.apply(Math,e))/2,pageY:(Math.min.apply(Math,n)+Math.max.apply(Math,n))/2}},getVelocity:function(t,e,n){return{x:Math.abs(e/t)||0,y:Math.abs(n/t)||0}},getAngle:function(t,e){var n=e.pageY-t.pageY,i=e.pageX-t.pageX;return 180*Math.atan2(n,i)/Math.PI},getDirection:function(t,e){var n=Math.abs(t.pageX-e.pageX),r=Math.abs(t.pageY-e.pageY);return n>=r?t.pageX-e.pageX>0?i.DIRECTION_LEFT:i.DIRECTION_RIGHT:t.pageY-e.pageY>0?i.DIRECTION_UP:i.DIRECTION_DOWN},getDistance:function(t,e){var n=e.pageX-t.pageX,i=e.pageY-t.pageY;return Math.sqrt(n*n+i*i)},getScale:function(t,e){return t.length>=2&&e.length>=2?this.getDistance(e[0],e[1])/this.getDistance(t[0],t[1]):1},getRotation:function(t,e){return t.length>=2&&e.length>=2?this.getAngle(e[1],e[0])-this.getAngle(t[1],t[0]):0},isVertical:function(t){return t==i.DIRECTION_UP||t==i.DIRECTION_DOWN},stopDefaultBrowserBehavior:function(t,e){var n,i=["webkit","khtml","moz","Moz","ms","o",""];if(e&&t.style){for(var r=0;i.length>r;r++)for(var o in e)e.hasOwnProperty(o)&&(n=o,i[r]&&(n=i[r]+n.substring(0,1).toUpperCase()+n.substring(1)),t.style[n]=e[o]);"none"==e.userSelect&&(t.onselectstart=function(){return!1})}}},i.detection={gestures:[],current:null,previous:null,stopped:!1,startDetect:function(t,e){this.current||(this.stopped=!1,this.current={inst:t,startEvent:i.utils.extend({},e),lastEvent:!1,name:""},this.detect(e))},detect:function(t){if(this.current&&!this.stopped){t=this.extendEventData(t);for(var e=this.current.inst.options,n=0,r=this.gestures.length;r>n;n++){var o=this.gestures[n];if(!this.stopped&&e[o.name]!==!1&&o.handler.call(o,t,this.current.inst)===!1){this.stopDetect();break}}return this.current&&(this.current.lastEvent=t),t.eventType==i.EVENT_END&&!t.touches.length-1&&this.stopDetect(),t}},stopDetect:function(){this.previous=i.utils.extend({},this.current),this.current=null,this.stopped=!0},extendEventData:function(t){var e=this.current.startEvent;if(e&&(t.touches.length!=e.touches.length||t.touches===e.touches)){e.touches=[];for(var n=0,r=t.touches.length;r>n;n++)e.touches.push(i.utils.extend({},t.touches[n]))}var o=t.timeStamp-e.timeStamp,s=t.center.pageX-e.center.pageX,a=t.center.pageY-e.center.pageY,c=i.utils.getVelocity(o,s,a);return i.utils.extend(t,{deltaTime:o,deltaX:s,deltaY:a,velocityX:c.x,velocityY:c.y,distance:i.utils.getDistance(e.center,t.center),angle:i.utils.getAngle(e.center,t.center),direction:i.utils.getDirection(e.center,t.center),scale:i.utils.getScale(e.touches,t.touches),rotation:i.utils.getRotation(e.touches,t.touches),startEvent:e}),t},register:function(t){var n=t.defaults||{};return n[t.name]===e&&(n[t.name]=!0),i.utils.extend(i.defaults,n,!0),t.index=t.index||1e3,this.gestures.push(t),this.gestures.sort(function(t,e){return t.indexe.index?1:0}),this.gestures}},i.gestures=i.gestures||{},i.gestures.Hold={name:"hold",index:10,defaults:{hold_timeout:500,hold_threshold:1},timer:null,handler:function(t,e){switch(t.eventType){case i.EVENT_START:clearTimeout(this.timer),i.detection.current.name=this.name,this.timer=setTimeout(function(){"hold"==i.detection.current.name&&e.trigger("hold",t)},e.options.hold_timeout);break;case i.EVENT_MOVE:t.distance>e.options.hold_threshold&&clearTimeout(this.timer);break;case i.EVENT_END:clearTimeout(this.timer)}}},i.gestures.Tap={name:"tap",index:100,defaults:{tap_max_touchtime:250,tap_max_distance:10,tap_always:!0,doubletap_distance:20,doubletap_interval:300},handler:function(t,e){if(t.eventType==i.EVENT_END){var n=i.detection.previous,r=!1;if(t.deltaTime>e.options.tap_max_touchtime||t.distance>e.options.tap_max_distance)return;n&&"tap"==n.name&&t.timeStamp-n.lastEvent.timeStamp0&&t.touches.length>e.options.swipe_max_touches)return;(t.velocityX>e.options.swipe_velocity||t.velocityY>e.options.swipe_velocity)&&(e.trigger(this.name,t),e.trigger(this.name+t.direction,t))}}},i.gestures.Drag={name:"drag",index:50,defaults:{drag_min_distance:10,correct_for_drag_min_distance:!0,drag_max_touches:1,drag_block_horizontal:!1,drag_block_vertical:!1,drag_lock_to_axis:!1,drag_lock_min_distance:25},triggered:!1,handler:function(t,n){if(i.detection.current.name!=this.name&&this.triggered)return n.trigger(this.name+"end",t),this.triggered=!1,e;if(!(n.options.drag_max_touches>0&&t.touches.length>n.options.drag_max_touches))switch(t.eventType){case i.EVENT_START:this.triggered=!1;break;case i.EVENT_MOVE:if(t.distancet.deltaY?i.DIRECTION_UP:i.DIRECTION_DOWN:0>t.deltaX?i.DIRECTION_LEFT:i.DIRECTION_RIGHT),this.triggered||(n.trigger(this.name+"start",t),this.triggered=!0),n.trigger(this.name,t),n.trigger(this.name+t.direction,t),(n.options.drag_block_vertical&&i.utils.isVertical(t.direction)||n.options.drag_block_horizontal&&!i.utils.isVertical(t.direction))&&t.preventDefault();break;case i.EVENT_END:this.triggered&&n.trigger(this.name+"end",t),this.triggered=!1}}},i.gestures.Transform={name:"transform",index:45,defaults:{transform_min_scale:.01,transform_min_rotation:1,transform_always_block:!1},triggered:!1,handler:function(t,n){if(i.detection.current.name!=this.name&&this.triggered)return n.trigger(this.name+"end",t),this.triggered=!1,e;if(!(2>t.touches.length))switch(n.options.transform_always_block&&t.preventDefault(),t.eventType){case i.EVENT_START:this.triggered=!1;break;case i.EVENT_MOVE:var r=Math.abs(1-t.scale),o=Math.abs(t.rotation);if(n.options.transform_min_scale>r&&n.options.transform_min_rotation>o)return;i.detection.current.name=this.name,this.triggered||(n.trigger(this.name+"start",t),this.triggered=!0),n.trigger(this.name,t),o>n.options.transform_min_rotation&&n.trigger("rotate",t),r>n.options.transform_min_scale&&(n.trigger("pinch",t),n.trigger("pinch"+(1>t.scale?"in":"out"),t));break;case i.EVENT_END:this.triggered&&n.trigger(this.name+"end",t),this.triggered=!1}}},i.gestures.Touch={name:"touch",index:-1/0,defaults:{prevent_default:!1,prevent_mouseevents:!1},handler:function(t,n){return n.options.prevent_mouseevents&&t.pointerType==i.POINTER_MOUSE?(t.stopDetect(),e):(n.options.prevent_default&&t.preventDefault(),t.eventType==i.EVENT_START&&n.trigger(this.name,t),e)}},i.gestures.Release={name:"release",index:1/0,handler:function(t,e){t.eventType==i.EVENT_END&&e.trigger(this.name,t)}},"function"==typeof define&&"object"==typeof define.amd&&define.amd?define(function(){return i}):"object"==typeof module&&"object"==typeof module.exports?module.exports=i:t.Hammer=i})(this); \ No newline at end of file diff --git a/activities/Gears.activity/js/loader.js b/activities/Gears.activity/js/loader.js new file mode 100644 index 000000000..ba250830d --- /dev/null +++ b/activities/Gears.activity/js/loader.js @@ -0,0 +1,24 @@ +requirejs.config({ + baseUrl: "lib", + shim: { + gearsketch_util: { + exports: 'gearsketch/gearsketch_util' + }, + gearsketch_model: { + exports: 'gearsketch/gearsketch_model', + deps: ['gearsketch_util'] + }, + gearsketch_main: { + exports: 'gearsketch/gearsketch_main', + deps: ['gearsketch_model', 'gearsketch_util'] + } + }, + paths: { + activity: "../js", + gearsketch_util: "../lib/gearsketch/gearsketch_util", + gearsketch_model: "../lib/gearsketch/gearsketch_model", + gearsketch_main: "../lib/gearsketch/gearsketch_main" + } +}); + +requirejs(["activity/activity"]); diff --git a/activities/Gears.activity/lib/domReady.js b/activities/Gears.activity/lib/domReady.js new file mode 100644 index 000000000..2b5412209 --- /dev/null +++ b/activities/Gears.activity/lib/domReady.js @@ -0,0 +1,129 @@ +/** + * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/domReady for details + */ +/*jslint */ +/*global require: false, define: false, requirejs: false, + window: false, clearInterval: false, document: false, + self: false, setInterval: false */ + + +define(function () { + 'use strict'; + + var isTop, testDiv, scrollIntervalId, + isBrowser = typeof window !== "undefined" && window.document, + isPageLoaded = !isBrowser, + doc = isBrowser ? document : null, + readyCalls = []; + + function runCallbacks(callbacks) { + var i; + for (i = 0; i < callbacks.length; i += 1) { + callbacks[i](doc); + } + } + + function callReady() { + var callbacks = readyCalls; + + if (isPageLoaded) { + //Call the DOM ready callbacks + if (callbacks.length) { + readyCalls = []; + runCallbacks(callbacks); + } + } + } + + /** + * Sets the page as loaded. + */ + function pageLoaded() { + if (!isPageLoaded) { + isPageLoaded = true; + if (scrollIntervalId) { + clearInterval(scrollIntervalId); + } + + callReady(); + } + } + + if (isBrowser) { + if (document.addEventListener) { + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. + document.addEventListener("DOMContentLoaded", pageLoaded, false); + window.addEventListener("load", pageLoaded, false); + } else if (window.attachEvent) { + window.attachEvent("onload", pageLoaded); + + testDiv = document.createElement('div'); + try { + isTop = window.frameElement === null; + } catch (e) {} + + //DOMContentLoaded approximation that uses a doScroll, as found by + //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, + //but modified by other contributors, including jdalton + if (testDiv.doScroll && isTop && window.external) { + scrollIntervalId = setInterval(function () { + try { + testDiv.doScroll(); + pageLoaded(); + } catch (e) {} + }, 30); + } + } + + //Check if document already complete, and if so, just trigger page load + //listeners. Latest webkit browsers also use "interactive", and + //will fire the onDOMContentLoaded before "interactive" but not after + //entering "interactive" or "complete". More details: + //http://dev.w3.org/html5/spec/the-end.html#the-end + //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded + //Hmm, this is more complicated on further use, see "firing too early" + //bug: https://github.com/requirejs/domReady/issues/1 + //so removing the || document.readyState === "interactive" test. + //There is still a window.onload binding that should get fired if + //DOMContentLoaded is missed. + if (document.readyState === "complete") { + pageLoaded(); + } + } + + /** START OF PUBLIC API **/ + + /** + * Registers a callback for DOM ready. If DOM is already ready, the + * callback is called immediately. + * @param {Function} callback + */ + function domReady(callback) { + if (isPageLoaded) { + callback(doc); + } else { + readyCalls.push(callback); + } + return domReady; + } + + domReady.version = '2.0.1'; + + /** + * Loader Plugin API method + */ + domReady.load = function (name, req, onLoad, config) { + if (config.isBuild) { + onLoad(null); + } else { + domReady(onLoad); + } + }; + + /** END OF PUBLIC API **/ + + return domReady; +}); diff --git a/activities/Gears.activity/lib/gearsketch/gearsketch_main.js b/activities/Gears.activity/lib/gearsketch/gearsketch_main.js new file mode 100644 index 000000000..03f354a16 --- /dev/null +++ b/activities/Gears.activity/lib/gearsketch/gearsketch_main.js @@ -0,0 +1,1054 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + "use strict"; + var ArcSegment, Board, Chain, FPS, Gear, GearSketch, LineSegment, MIN_GEAR_TEETH, MIN_MOMENTUM, Point, Util, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __slice = [].slice; + + Point = window.gearsketch.Point; + + ArcSegment = window.gearsketch.ArcSegment; + + LineSegment = window.gearsketch.LineSegment; + + Util = window.gearsketch.Util; + + Gear = window.gearsketch.model.Gear; + + Chain = window.gearsketch.model.Chain; + + Board = window.gearsketch.model.Board; + + FPS = 60; + + MIN_GEAR_TEETH = 8; + + MIN_MOMENTUM = 0.2; + + GearSketch = (function() { + var AXIS_RADIUS, BUTTON_INFO, MODULE, MovementAction, MovementType; + + MODULE = Util.MODULE; + + AXIS_RADIUS = Util.AXIS_RADIUS; + + BUTTON_INFO = [["gearButton", "GearIcon.png"], ["chainButton", "ChainIcon.png"], ["momentumButton", "MomentumIcon.png"], ["playButton", "PlayIcon.png"], ["clearButton", "ClearIcon.png"], ["cloudButton", "CloudIcon.png"], ["helpButton", "HelpIcon.png"]]; + + MovementAction = { + PEN_DOWN: "penDown", + PEN_UP: "penUp", + PEN_TAP: "penTap" + }; + + MovementType = { + STRAIGHT: "straight", + CIRCLE: "circle", + LEFT_HALF_CIRCLE: "leftHalfCircle", + RIGHT_HALF_CIRCLE: "rightHalfCircle" + }; + + GearSketch.prototype.buttons = {}; + + GearSketch.prototype.loadedButtons = 0; + + GearSketch.prototype.areButtonsLoaded = false; + + GearSketch.prototype.selectedButton = BUTTON_INFO[0][0]; + + GearSketch.prototype.gearImages = {}; + + GearSketch.prototype.isPenDown = false; + + GearSketch.prototype.stroke = []; + + GearSketch.prototype.offset = new Point(); + + GearSketch.prototype.message = ""; + + GearSketch.prototype.messageColor = "black"; + + GearSketch.prototype.pointerLocation = new Point(); + + GearSketch.prototype.currentDemoMovement = 0; + + GearSketch.prototype.movementCompletion = 0; + + GearSketch.prototype.restTimer = 0; + + function GearSketch(showButtons) { + if (showButtons == null) { + showButtons = true; + } + this.update = __bind(this.update, this); + this.updateAndDrawNoRAF = __bind(this.updateAndDrawNoRAF, this); + this.updateAndDraw = __bind(this.updateAndDraw, this); + this.loadButtons(); + this.showButtons = showButtons; + this.loadDemoPointer(); + this.loadBoard(); + this.canvas = document.getElementById("gearsketch_canvas"); + this.canvasOffsetX = this.canvas.getBoundingClientRect().left; + this.canvasOffsetY = this.canvas.getBoundingClientRect().top; + this.isDemoPlaying = false; + this.updateCanvasSize(); + this.addCanvasListeners(); + this.lastUpdateTime = new Date().getTime(); + this.updateAndDrawNoRAF(); + } + + GearSketch.prototype.buttonLoaded = function() { + this.loadedButtons++; + if (this.loadedButtons === BUTTON_INFO.length) { + return this.areButtonsLoaded = true; + } + }; + + GearSketch.prototype.loadButtons = function() { + var button, file, name, x, y, _i, _len, _ref, _results, + _this = this; + x = y = 20; + _results = []; + for (_i = 0, _len = BUTTON_INFO.length; _i < _len; _i++) { + _ref = BUTTON_INFO[_i], name = _ref[0], file = _ref[1]; + button = new Image(); + button.name = name; + button.onload = function() { + return _this.buttonLoaded(); + }; + button.src = "img/" + file; + button.location = new Point(x, y); + button.padding = 3; + this.buttons[name] = button; + _results.push(x += 80); + } + return _results; + }; + + GearSketch.prototype.loadDemoPointer = function() { + var image, + _this = this; + image = new Image(); + image.onload = function() { + return _this.pointerImage = image; + }; + return image.src = "img/hand.png"; + }; + + GearSketch.prototype.loadBoard = function() { + var boardJSON, error, gear, hash, id, _ref, _results; + this.board = (function() { + if (parent.location.hash.length > 1) { + try { + hash = parent.location.hash.substr(1); + boardJSON = Util.sendGetRequest("boards/" + hash + ".txt"); + return Board.fromObject(JSON.parse(boardJSON)); + } catch (_error) { + error = _error; + this.displayMessage("Error: could not load board", "red", 2000); + return new Board(); + } + } else { + return new Board(); + } + }).call(this); + _ref = this.board.getGears(); + _results = []; + for (id in _ref) { + gear = _ref[id]; + _results.push(this.addGearImage(gear)); + } + return _results; + }; + + GearSketch.prototype.displayMessage = function(message, color, time) { + var _this = this; + if (color == null) { + color = "black"; + } + if (time == null) { + time = 0; + } + this.message = message; + this.messageColor = color; + if (time > 0) { + return setTimeout((function() { + return _this.clearMessage(); + }), time); + } + }; + + GearSketch.prototype.clearMessage = function() { + return this.message = ""; + }; + + GearSketch.prototype.selectButton = function(buttonName) { + return this.selectedButton = buttonName; + }; + + GearSketch.prototype.shouldShowButtons = function() { + return this.showButtons || this.isDemoPlaying; + }; + + GearSketch.prototype.addCanvasListeners = function() { + var canvasEventHandler, + _this = this; + canvasEventHandler = Hammer(this.canvas, { + drag_min_distance: 1 + }); + canvasEventHandler.on("touch", (function(e) { + return _this.forwardPenDownEvent.call(_this, e); + })); + canvasEventHandler.on("drag", (function(e) { + return _this.forwardPenMoveEvent.call(_this, e); + })); + return canvasEventHandler.on("release", (function(e) { + return _this.forwardPenUpEvent.call(_this, e); + })); + }; + + GearSketch.prototype.forwardPenDownEvent = function(event) { + var x, y; + event.gesture.preventDefault(); + if (this.isDemoPlaying) { + return this.stopDemo(); + } else { + x = event.gesture.center.pageX - this.canvasOffsetX; + y = event.gesture.center.pageY - this.canvasOffsetY; + return this.handlePenDown(x, y); + } + }; + + GearSketch.prototype.forwardPenMoveEvent = function(event) { + var x, y; + event.gesture.preventDefault(); + if (!this.isDemoPlaying) { + x = event.gesture.center.pageX - this.canvasOffsetX; + y = event.gesture.center.pageY - this.canvasOffsetY; + return this.handlePenMove(x, y); + } + }; + + GearSketch.prototype.forwardPenUpEvent = function(event) { + if (!this.isDemoPlaying) { + return this.handlePenUp(); + } + }; + + GearSketch.prototype.handlePenDown = function(x, y) { + var button, point; + point = new Point(x, y); + if (this.isPenDown) { + return this.handlePenUp(); + } else { + button = this.getButtonAt(x, y); + if (button) { + if (button.name === "clearButton") { + parent.location.hash = ""; + return this.board.clear(); + } else if (button.name === "cloudButton") { + return this.uploadBoard(); + } else if (button.name === "helpButton") { + return this.playDemo(); + } else { + return this.selectButton(button.name); + } + } else if (this.selectedButton === "gearButton") { + this.selectedGear = this.board.getTopLevelGearAt(point); + if (this.selectedGear != null) { + this.offset = point.minus(this.selectedGear.location); + } else if (this.board.getGearAt(point) == null) { + this.stroke.push(point); + } + return this.isPenDown = true; + } else if (this.selectedButton === "chainButton") { + this.stroke.push(point); + return this.isPenDown = true; + } else if (this.selectedButton === "momentumButton") { + this.selectedGear = this.board.getGearAt(point); + if (this.selectedGear) { + this.selectedGear.momentum = 0; + this.selectedGearMomentum = this.calculateMomentumFromCoords(this.selectedGear, x, y); + } + return this.isPenDown = true; + } + } + }; + + GearSketch.prototype.handlePenMove = function(x, y) { + var canPlaceGear, goalLocation, point; + point = new Point(x, y); + if (this.isPenDown) { + if (this.selectedButton === "gearButton") { + if (this.selectedGear) { + goalLocation = point.minus(this.offset); + canPlaceGear = this.board.placeGear(this.selectedGear, goalLocation); + if (canPlaceGear) { + return this.goalLocationGear = null; + } else { + return this.goalLocationGear = new Gear(goalLocation, this.selectedGear.rotation, this.selectedGear.numberOfTeeth, this.selectedGear.id); + } + } else if (this.stroke.length > 0) { + return this.stroke.push(point); + } + } else if (this.selectedButton === "chainButton") { + return this.stroke.push(point); + } else if (this.selectedButton === "momentumButton") { + if (this.selectedGear) { + return this.selectedGearMomentum = this.calculateMomentumFromCoords(this.selectedGear, x, y); + } + } + } + }; + + GearSketch.prototype.handlePenUp = function() { + if (this.isPenDown) { + if (this.selectedButton === "gearButton") { + if (!((this.selectedGear != null) || this.stroke.length === 0)) { + this.processGearStroke(); + } + } else if (this.selectedButton === "chainButton") { + this.processChainStroke(); + } else if (this.selectedButton === "momentumButton") { + if (this.selectedGear) { + if (Math.abs(this.selectedGearMomentum) > MIN_MOMENTUM) { + this.selectedGear.momentum = this.selectedGearMomentum; + } else { + this.selectedGear.momentum = 0; + } + } + this.selectedGearMomentum = 0; + } + this.selectedGear = null; + this.goalLocationGear = null; + return this.isPenDown = false; + } + }; + + GearSketch.prototype.isButtonAt = function(x, y, button) { + return x > button.location.x && x < button.location.x + button.width + 2 * button.padding && y > button.location.y && y < button.location.y + button.height + 2 * button.padding; + }; + + GearSketch.prototype.getButtonAt = function(x, y) { + var button, buttonName, _ref; + if (!this.shouldShowButtons()) { + return null; + } + _ref = this.buttons; + for (buttonName in _ref) { + if (!__hasProp.call(_ref, buttonName)) continue; + button = _ref[buttonName]; + if (this.isButtonAt(x, y, button)) { + return button; + } + } + return null; + }; + + GearSketch.prototype.normalizeStroke = function(stroke) { + var MIN_POINT_DISTANCE, normalizedStroke, p1, p2, strokeTail, _i, _len; + MIN_POINT_DISTANCE = 10; + normalizedStroke = []; + if (stroke.length > 0) { + p1 = stroke[0], strokeTail = 2 <= stroke.length ? __slice.call(stroke, 1) : []; + normalizedStroke.push(p1); + for (_i = 0, _len = strokeTail.length; _i < _len; _i++) { + p2 = strokeTail[_i]; + if (p1.distance(p2) > MIN_POINT_DISTANCE) { + normalizedStroke.push(p2); + p1 = p2; + } + } + } + return normalizedStroke; + }; + + GearSketch.prototype.createGearFromStroke = function(stroke) { + var area, doubleArea, height, i, idealTrueAreaRatio, j, maxX, maxY, minX, minY, numberOfPoints, p, radius, sumX, sumY, t, width, x, y, _i, _j, _len; + numberOfPoints = stroke.length; + if (numberOfPoints > 0) { + sumX = 0; + sumY = 0; + minX = Number.MAX_VALUE; + maxX = Number.MIN_VALUE; + minY = Number.MAX_VALUE; + maxY = Number.MIN_VALUE; + for (_i = 0, _len = stroke.length; _i < _len; _i++) { + p = stroke[_i]; + sumX += p.x; + sumY += p.y; + minX = Math.min(minX, p.x); + maxX = Math.max(maxX, p.x); + minY = Math.min(minY, p.y); + maxY = Math.max(maxY, p.y); + } + width = maxX - minX; + height = maxY - minY; + t = Math.floor(0.5 * (width + height) / MODULE); + doubleArea = 0; + for (i = _j = 0; 0 <= numberOfPoints ? _j < numberOfPoints : _j > numberOfPoints; i = 0 <= numberOfPoints ? ++_j : --_j) { + j = (i + 1) % numberOfPoints; + doubleArea += stroke[i].x * stroke[j].y; + doubleArea -= stroke[i].y * stroke[j].x; + } + area = Math.abs(doubleArea) / 2; + radius = 0.25 * ((maxX - minX) + (maxY - minY)); + idealTrueAreaRatio = (Math.PI * Math.pow(radius, 2)) / area; + if (idealTrueAreaRatio > 0.80 && idealTrueAreaRatio < 1.20 && t > MIN_GEAR_TEETH) { + x = sumX / numberOfPoints; + y = sumY / numberOfPoints; + return new Gear(new Point(x, y), 0, t); + } + } + return null; + }; + + GearSketch.prototype.removeStrokedGears = function(stroke) { + var gear, id, _ref, _results; + _ref = this.board.getTopLevelGears(); + _results = []; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + gear = _ref[id]; + if (Util.pointPathDistance(gear.location, stroke, false) < gear.innerRadius) { + _results.push(this.board.removeGear(gear)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + GearSketch.prototype.processGearStroke = function() { + var gear, isGearAdded, normalizedStroke; + normalizedStroke = this.normalizeStroke(this.stroke); + gear = this.createGearFromStroke(normalizedStroke); + if (gear != null) { + isGearAdded = this.board.addGear(gear); + if (isGearAdded && !(gear.numberOfTeeth in this.gearImages)) { + this.addGearImage(gear); + } + } else { + this.removeStrokedGears(normalizedStroke); + } + return this.stroke = []; + }; + + GearSketch.prototype.gearImageLoaded = function(numberOfTeeth, image) { + return this.gearImages[numberOfTeeth] = image; + }; + + GearSketch.prototype.addGearImage = function(gear) { + var ctx, gearCanvas, gearCopy, image, size, + _this = this; + gearCanvas = document.createElement("canvas"); + size = 2 * (gear.outerRadius + MODULE); + gearCanvas.height = size; + gearCanvas.width = size; + ctx = gearCanvas.getContext("2d"); + gearCopy = new Gear(new Point(0.5 * size, 0.5 * size), 0, gear.numberOfTeeth, gear.id); + this.drawGear(ctx, gearCopy); + image = new Image(); + image.onload = function() { + return _this.gearImageLoaded(gear.numberOfTeeth, image); + }; + return image.src = gearCanvas.toDataURL("image/png"); + }; + + GearSketch.prototype.removeStrokedChains = function(stroke) { + var chain, id, _ref, _results; + _ref = this.board.getChains(); + _results = []; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + chain = _ref[id]; + if (chain.intersectsPath(stroke)) { + _results.push(this.board.removeChain(chain)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + GearSketch.prototype.processChainStroke = function() { + var chain, gearsInChain, normalizedStroke; + normalizedStroke = this.normalizeStroke(this.stroke); + this.stroke = []; + gearsInChain = Util.findGearsInsidePolygon(normalizedStroke, this.board.getGears()); + if (normalizedStroke.length >= 3 && gearsInChain.length > 0) { + chain = new Chain(normalizedStroke); + return this.board.addChain(chain); + } else if (normalizedStroke.length >= 2) { + return this.removeStrokedChains(normalizedStroke); + } + }; + + GearSketch.prototype.calculateMomentumFromCoords = function(gear, x, y) { + var angle, angleFromTop; + angle = Math.atan2(y - gear.location.y, x - gear.location.x); + angleFromTop = angle + 0.5 * Math.PI; + if (angleFromTop < Math.PI) { + return angleFromTop; + } else { + return angleFromTop - 2 * Math.PI; + } + }; + + GearSketch.prototype.updateAndDraw = function() { + var _this = this; + return setTimeout((function() { + requestAnimationFrame(_this.updateAndDraw); + _this.update(); + return _this.draw(); + }), 1000 / FPS); + }; + + GearSketch.prototype.updateAndDrawNoRAF = function() { + var _this = this; + this.update(); + this.draw(); + return setTimeout((function() { + return _this.updateAndDrawNoRAF(); + }), 1000 / FPS); + }; + + GearSketch.prototype.update = function() { + var delta, updateTime; + updateTime = new Date().getTime(); + delta = updateTime - this.lastUpdateTime; + if (this.selectedButton === "playButton") { + this.board.rotateAllTurningObjects(delta); + } + if (this.isDemoPlaying) { + this.updateDemo(delta); + } + return this.lastUpdateTime = updateTime; + }; + + GearSketch.prototype.drawGear = function(ctx, gear, color) { + var angleStep, gearImage, i, innerPoints, numberOfTeeth, outerPoints, r, rotation, x, y, _i, _j, _k, _ref, _ref1; + if (color == null) { + color = "black"; + } + _ref = gear.location, x = _ref.x, y = _ref.y; + rotation = gear.rotation; + numberOfTeeth = gear.numberOfTeeth; + gearImage = this.gearImages[gear.numberOfTeeth]; + if (color === "black" && (gearImage != null)) { + gearImage = this.gearImages[gear.numberOfTeeth]; + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rotation); + ctx.drawImage(gearImage, -0.5 * gearImage.width, -0.5 * gearImage.height); + ctx.restore(); + return; + } + angleStep = 2 * Math.PI / numberOfTeeth; + innerPoints = []; + outerPoints = []; + for (i = _i = 0; 0 <= numberOfTeeth ? _i < numberOfTeeth : _i > numberOfTeeth; i = 0 <= numberOfTeeth ? ++_i : --_i) { + for (r = _j = 0; _j < 4; r = ++_j) { + if (r === 0 || r === 3) { + innerPoints.push(Point.polar((i + 0.25 * r) * angleStep, gear.innerRadius)); + } else { + outerPoints.push(Point.polar((i + 0.25 * r) * angleStep, gear.outerRadius)); + } + } + } + ctx.save(); + ctx.fillStyle = "rgba(255, 255, 255, 0.8)"; + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.translate(x, y); + ctx.rotate(rotation); + ctx.beginPath(); + ctx.moveTo(gear.innerRadius, 0); + for (i = _k = 0, _ref1 = numberOfTeeth * 2; 0 <= _ref1 ? _k < _ref1 : _k > _ref1; i = 0 <= _ref1 ? ++_k : --_k) { + if (i % 2 === 0) { + ctx.lineTo(innerPoints[i].x, innerPoints[i].y); + ctx.lineTo(outerPoints[i].x, outerPoints[i].y); + } else { + ctx.lineTo(outerPoints[i].x, outerPoints[i].y); + ctx.lineTo(innerPoints[i].x, innerPoints[i].y); + } + } + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(AXIS_RADIUS, 0); + ctx.arc(0, 0, AXIS_RADIUS, 0, 2 * Math.PI, true); + ctx.closePath(); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(AXIS_RADIUS, 0); + ctx.lineTo(gear.innerRadius, 0); + ctx.closePath(); + ctx.stroke(); + return ctx.restore(); + }; + + GearSketch.prototype.drawButton = function(ctx, button) { + var height, padding, radius, width, x, y, _ref; + _ref = button.location, x = _ref.x, y = _ref.y; + padding = button.padding; + ctx.save(); + ctx.translate(x, y); + ctx.beginPath(); + radius = 10; + width = button.width + 2 * padding; + height = button.height + 2 * padding; + ctx.moveTo(radius, 0); + ctx.lineTo(width - radius, 0); + ctx.quadraticCurveTo(width, 0, width, radius); + ctx.lineTo(width, height - radius); + ctx.quadraticCurveTo(width, height, width - radius, height); + ctx.lineTo(radius, height); + ctx.quadraticCurveTo(0, height, 0, height - radius); + ctx.lineTo(0, radius); + ctx.quadraticCurveTo(0, 0, radius, 0); + if (button.name === this.selectedButton) { + ctx.fillStyle = "rgba(50, 150, 255, 0.8)"; + } else { + ctx.fillStyle = "rgba(255, 255, 255, 0.8)"; + } + ctx.fill(); + ctx.lineWidth = 1; + ctx.strokeStyle = "black"; + ctx.stroke(); + ctx.drawImage(button, padding, padding); + return ctx.restore(); + }; + + GearSketch.prototype.drawMomentum = function(ctx, gear, momentum, color) { + var angle, head, headX, headY, length, p1, p2, pitchRadius, sign, top; + if (color == null) { + color = "red"; + } + pitchRadius = gear.pitchRadius; + top = new Point(gear.location.x, gear.location.y - pitchRadius); + ctx.save(); + ctx.lineWidth = 5; + ctx.lineCap = "round"; + ctx.strokeStyle = color; + ctx.translate(top.x, top.y); + ctx.beginPath(); + ctx.arc(0, pitchRadius, pitchRadius, -0.5 * Math.PI, momentum - 0.5 * Math.PI, momentum < 0); + ctx.stroke(); + length = 15; + angle = 0.2 * Math.PI; + headX = -Math.cos(momentum + 0.5 * Math.PI) * pitchRadius; + headY = pitchRadius - Math.sin(momentum + 0.5 * Math.PI) * pitchRadius; + head = new Point(headX, headY); + sign = Util.sign(momentum); + p1 = head.minus(Point.polar(momentum + angle, sign * length)); + ctx.beginPath(); + ctx.moveTo(headX, headY); + ctx.lineTo(p1.x, p1.y); + ctx.stroke(); + p2 = head.minus(Point.polar(momentum - angle, sign * length)); + ctx.beginPath(); + ctx.moveTo(headX, headY); + ctx.lineTo(p2.x, p2.y); + ctx.stroke(); + return ctx.restore(); + }; + + GearSketch.prototype.drawChain = function(ctx, chain) { + var isCounterClockwise, point, segment, _i, _j, _len, _len1, _ref, _ref1; + ctx.save(); + ctx.lineWidth = Chain.WIDTH; + ctx.lineCap = "round"; + ctx.strokeStyle = "rgb(0, 0, 255)"; + ctx.moveTo(chain.segments[0].start.x, chain.segments[0].start.y); + _ref = chain.segments; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + segment = _ref[_i]; + if (segment instanceof ArcSegment) { + isCounterClockwise = segment.direction === Util.Direction.COUNTER_CLOCKWISE; + ctx.beginPath(); + ctx.arc(segment.center.x, segment.center.y, segment.radius, segment.startAngle, segment.endAngle, isCounterClockwise); + ctx.stroke(); + } else { + ctx.beginPath(); + ctx.moveTo(segment.start.x, segment.start.y); + ctx.lineTo(segment.end.x, segment.end.y); + ctx.stroke(); + } + } + ctx.fillStyle = "white"; + _ref1 = chain.findPointsOnChain(25); + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + point = _ref1[_j]; + ctx.beginPath(); + ctx.arc(point.x, point.y, 3, 0, 2 * Math.PI, true); + ctx.fill(); + } + return ctx.restore(); + }; + + GearSketch.prototype.drawDemoPointer = function(ctx, location) { + return ctx.drawImage(this.pointerImage, location.x - 0.5 * this.pointerImage.width, location.y); + }; + + GearSketch.prototype.draw = function() { + var arrow, arrowsToDraw, buttonName, chain, ctx, gear, i, momentum, shouldDrawChainsAndArrows, sortedGears, _i, _j, _k, _l, _len, _len1, _ref, _ref1, _ref2, _ref3; + if (this.canvas.getContext != null) { + this.updateCanvasSize(); + ctx = this.canvas.getContext("2d"); + ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + sortedGears = this.board.getGearsSortedByGroupAndLevel(); + arrowsToDraw = []; + for (i = _i = 0, _ref = sortedGears.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + gear = sortedGears[i]; + momentum = gear.momentum; + if (gear === this.selectedGear && this.goalLocationGear) { + this.drawGear(ctx, gear, "grey"); + if (momentum) { + arrowsToDraw.push([gear, momentum, "grey"]); + } + } else { + this.drawGear(ctx, gear); + if (momentum) { + arrowsToDraw.push([gear, momentum, "red"]); + } + } + shouldDrawChainsAndArrows = (i === sortedGears.length - 1) || (this.board.getLevelScore(gear) !== this.board.getLevelScore(sortedGears[i + 1])); + if (shouldDrawChainsAndArrows) { + _ref1 = this.board.getChainsInGroupOnLevel(gear.group, gear.level); + for (_j = 0, _len = _ref1.length; _j < _len; _j++) { + chain = _ref1[_j]; + this.drawChain(ctx, chain); + } + for (_k = 0, _len1 = arrowsToDraw.length; _k < _len1; _k++) { + arrow = arrowsToDraw[_k]; + this.drawMomentum(ctx, arrow[0], arrow[1], arrow[2]); + } + arrowsToDraw = []; + } + } + if (this.goalLocationGear) { + this.drawGear(ctx, this.goalLocationGear, "red"); + } + if ((this.selectedGear != null) && this.selectedGearMomentum) { + this.drawMomentum(ctx, this.selectedGear, this.selectedGearMomentum); + } + if (this.stroke.length > 0) { + ctx.save(); + if (this.selectedButton === "gearButton") { + ctx.strokeStyle = "black"; + ctx.lineWidth = 2; + } else { + ctx.strokeStyle = "blue"; + ctx.lineWidth = 4; + } + ctx.beginPath(); + ctx.moveTo(this.stroke[0].x, this.stroke[0].y); + for (i = _l = 1, _ref2 = this.stroke.length; 1 <= _ref2 ? _l < _ref2 : _l > _ref2; i = 1 <= _ref2 ? ++_l : --_l) { + ctx.lineTo(this.stroke[i].x, this.stroke[i].y); + } + ctx.stroke(); + ctx.restore(); + } + if (this.areButtonsLoaded && this.shouldShowButtons()) { + _ref3 = this.buttons; + for (buttonName in _ref3) { + if (!__hasProp.call(_ref3, buttonName)) continue; + this.drawButton(ctx, this.buttons[buttonName]); + } + } + if (this.message.length > 0) { + ctx.save(); + ctx.fillStyle = this.messageColor; + ctx.font = "bold 20px Arial"; + ctx.fillText(this.message, 20, 120); + ctx.restore(); + } + if (this.isDemoPlaying && this.pointerImage) { + return this.drawDemoPointer(ctx, this.pointerLocation); + } + } + }; + + GearSketch.prototype.updateCanvasSize = function() { + this.canvas.width = this.canvas.parentElement.getBoundingClientRect().width; + this.canvas.height = this.canvas.parentElement.getBoundingClientRect().height; + this.buttons["clearButton"].location.x = Math.max(this.canvas.width - 260, this.buttons["playButton"].location.x + 80); + this.buttons["cloudButton"].location.x = this.buttons["clearButton"].location.x + 80; + return this.buttons["helpButton"].location.x = this.buttons["cloudButton"].location.x + 80; + }; + + GearSketch.prototype.loadDemoMovements = function() { + return this.demoMovements = [ + { + from: this.getButtonCenter("helpButton"), + to: this.getButtonCenter("gearButton"), + atEnd: MovementAction.PEN_TAP, + type: MovementType.STRAIGHT, + duration: 2000 + }, { + to: new Point(300, 200), + type: MovementType.STRAIGHT, + duration: 1500 + }, { + atStart: MovementAction.PEN_DOWN, + atEnd: MovementAction.PEN_UP, + type: MovementType.CIRCLE, + radius: 100, + duration: 1500 + }, { + to: new Point(500, 200), + type: MovementType.STRAIGHT, + duration: 1000 + }, { + atStart: MovementAction.PEN_DOWN, + atEnd: MovementAction.PEN_UP, + type: MovementType.CIRCLE, + radius: 40, + duration: 1000 + }, { + to: new Point(500, 240), + type: MovementType.STRAIGHT, + duration: 500 + }, { + to: new Point(300, 300), + atStart: MovementAction.PEN_DOWN, + atEnd: MovementAction.PEN_UP, + type: MovementType.STRAIGHT, + duration: 1500 + }, { + to: new Point(100, 180), + type: MovementType.STRAIGHT, + duration: 1000 + }, { + atStart: MovementAction.PEN_DOWN, + atEnd: MovementAction.PEN_UP, + type: MovementType.CIRCLE, + radius: 90, + duration: 1000 + }, { + to: new Point(100, 260), + type: MovementType.STRAIGHT, + duration: 500 + }, { + to: new Point(180, 260), + atStart: MovementAction.PEN_DOWN, + atEnd: MovementAction.PEN_UP, + type: MovementType.STRAIGHT, + duration: 1500 + }, { + to: new Point(550, 220), + type: MovementType.STRAIGHT, + duration: 1500 + }, { + atStart: MovementAction.PEN_DOWN, + atEnd: MovementAction.PEN_UP, + type: MovementType.CIRCLE, + radius: 80, + duration: 1000 + }, { + to: this.getButtonCenter("chainButton"), + atEnd: MovementAction.PEN_TAP, + type: MovementType.STRAIGHT, + duration: 1500 + }, { + to: new Point(280, 150), + type: MovementType.STRAIGHT, + duration: 1500 + }, { + atStart: MovementAction.PEN_DOWN, + type: MovementType.LEFT_HALF_CIRCLE, + radius: 140, + duration: 1500, + pause: 0 + }, { + to: new Point(600, 400), + type: MovementType.STRAIGHT, + duration: 1000, + pause: 0 + }, { + type: MovementType.RIGHT_HALF_CIRCLE, + radius: 110, + duration: 1000, + pause: 0 + }, { + to: new Point(280, 150), + atEnd: MovementAction.PEN_UP, + type: MovementType.STRAIGHT, + duration: 1000 + }, { + to: this.getButtonCenter("momentumButton"), + atEnd: MovementAction.PEN_TAP, + type: MovementType.STRAIGHT, + duration: 1500 + }, { + to: new Point(185, 180), + type: MovementType.STRAIGHT, + duration: 1500 + }, { + to: new Point(150, 190), + atStart: MovementAction.PEN_DOWN, + atEnd: MovementAction.PEN_UP, + type: MovementType.STRAIGHT, + duration: 1000 + }, { + to: this.getButtonCenter("playButton"), + atEnd: MovementAction.PEN_TAP, + type: MovementType.STRAIGHT, + duration: 1500 + }, { + to: this.getButtonCenter("chainButton"), + atEnd: MovementAction.PEN_TAP, + type: MovementType.STRAIGHT, + duration: 3000 + }, { + to: new Point(425, 250), + type: MovementType.STRAIGHT, + duration: 1000 + }, { + to: new Point(525, 150), + atStart: MovementAction.PEN_DOWN, + atEnd: MovementAction.PEN_UP, + type: MovementType.STRAIGHT, + duration: 1000 + }, { + to: this.getButtonCenter("gearButton"), + atEnd: MovementAction.PEN_TAP, + type: MovementType.STRAIGHT, + duration: 1500 + }, { + to: new Point(20, 250), + type: MovementType.STRAIGHT, + duration: 1000 + }, { + to: new Point(650, 300), + atStart: MovementAction.PEN_DOWN, + atEnd: MovementAction.PEN_UP, + type: MovementType.STRAIGHT, + duration: 1500 + }, { + to: new Point(425, 200), + type: MovementType.STRAIGHT, + duration: 1000 + }, { + to: new Point(200, 400), + atStart: MovementAction.PEN_DOWN, + atEnd: MovementAction.PEN_UP, + type: MovementType.STRAIGHT, + duration: 1500 + } + ]; + }; + + GearSketch.prototype.getButtonCenter = function(buttonName) { + var button, buttonCorner; + button = this.buttons[buttonName]; + buttonCorner = new Point(button.location.x, button.location.y); + return buttonCorner.plus(new Point(0.5 * button.width + button.padding, 0.5 * button.height + button.padding)); + }; + + GearSketch.prototype.updateDemo = function(delta) { + var movement; + if (this.restTimer > 0) { + this.restTimer = Math.max(this.restTimer - delta, 0); + return; + } else if (this.currentDemoMovement === this.demoMovements.length) { + this.stopDemo(); + return; + } + movement = this.demoMovements[this.currentDemoMovement]; + if (this.movementCompletion === 0) { + if (movement.from == null) { + movement.from = this.pointerLocation; + } + if (movement.pause == null) { + movement.pause = 500; + } + this.pointerLocation = movement.from.clone(); + if (movement.atStart === MovementAction.PEN_DOWN) { + this.handlePenDown(this.pointerLocation.x, this.pointerLocation.y); + } + } + if (this.movementCompletion < 1) { + this.movementCompletion = Math.min(1, this.movementCompletion + delta / movement.duration); + this.updatePointerLocation(movement, this.movementCompletion); + this.handlePenMove(this.pointerLocation.x, this.pointerLocation.y); + } + if (this.movementCompletion === 1) { + if (movement.atEnd === MovementAction.PEN_TAP) { + this.handlePenDown(this.pointerLocation.x, this.pointerLocation.y); + this.handlePenUp(); + } else if (movement.atEnd === MovementAction.PEN_UP) { + this.handlePenUp(); + } + this.restTimer = movement.pause; + this.movementCompletion = 0; + return this.currentDemoMovement++; + } + }; + + GearSketch.prototype.updatePointerLocation = function(movement, movementCompletion) { + var angle, center, delta; + if (movement.type === MovementType.STRAIGHT) { + delta = movement.to.minus(movement.from); + return this.pointerLocation = movement.from.plus(delta.times(movementCompletion)); + } else if (movement.type === MovementType.CIRCLE) { + center = new Point(movement.from.x, movement.from.y + movement.radius); + return this.pointerLocation = center.plus(Point.polar(Math.PI - (movementCompletion - 0.25) * 2 * Math.PI, movement.radius)); + } else if (movement.type === MovementType.LEFT_HALF_CIRCLE) { + center = new Point(movement.from.x, movement.from.y + movement.radius); + angle = 1.5 * Math.PI - movementCompletion * Math.PI; + return this.pointerLocation = center.plus(Point.polar(angle, movement.radius)); + } else if (movement.type === MovementType.RIGHT_HALF_CIRCLE) { + center = new Point(movement.from.x, movement.from.y - movement.radius); + angle = 0.5 * Math.PI - movementCompletion * Math.PI; + return this.pointerLocation = center.plus(Point.polar(angle, movement.radius)); + } + }; + + GearSketch.prototype.playDemo = function() { + this.loadDemoMovements(); + this.boardBackup = this.board.clone(); + this.board.clear(); + this.currentDemoMovement = 0; + this.movementCompletion = 0; + this.isDemoPlaying = true; + return this.displayMessage("click anywhere to stop the demo"); + }; + + GearSketch.prototype.stopDemo = function() { + this.isDemoPlaying = false; + this.restTimer = 0; + this.stroke = []; + this.selectedGear = null; + this.selectedIcon = "gearIcon"; + this.board.restoreAfterDemo(this.boardBackup); + return this.clearMessage(); + }; + + GearSketch.prototype.boardUploaded = function(event) { + parent.location.hash = event.target.responseText.trim(); + return this.displayMessage("Board saved. Share it by copying the text in your address bar.", "black", 4000); + }; + + GearSketch.prototype.uploadBoard = function() { + var boardJSON, + _this = this; + boardJSON = JSON.stringify(this.board); + return Util.sendPostRequest(boardJSON, "upload_board.php", (function(event) { + return _this.boardUploaded(event); + })); + }; + + return GearSketch; + + })(); + + window.gearsketch.GearSketch = GearSketch; + +}).call(this); + +/* +//@ sourceMappingURL=gearsketch_main.map +*/ diff --git a/activities/Gears.activity/lib/gearsketch/gearsketch_main.map b/activities/Gears.activity/lib/gearsketch/gearsketch_main.map new file mode 100644 index 000000000..b0437c5a7 --- /dev/null +++ b/activities/Gears.activity/lib/gearsketch/gearsketch_main.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "gearsketch_main.js", + "sourceRoot": "../..", + "sources": [ + "lib/gearsketch/src/gearsketch_main.coffee" + ], + "names": [], + "mappings": "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" +} \ No newline at end of file diff --git a/activities/Gears.activity/lib/gearsketch/gearsketch_model.js b/activities/Gears.activity/lib/gearsketch/gearsketch_model.js new file mode 100644 index 000000000..454597fe1 --- /dev/null +++ b/activities/Gears.activity/lib/gearsketch/gearsketch_model.js @@ -0,0 +1,1450 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + "use strict"; + var ArcSegment, Board, Chain, Gear, LineSegment, Point, Util, + __hasProp = {}.hasOwnProperty, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + Point = window.gearsketch.Point; + + ArcSegment = window.gearsketch.ArcSegment; + + LineSegment = window.gearsketch.LineSegment; + + Util = window.gearsketch.Util; + + window.gearsketch.model = {}; + + Gear = (function() { + function Gear(location, rotation, numberOfTeeth, id, momentum, group, level, connections) { + this.location = location; + this.rotation = rotation; + this.numberOfTeeth = numberOfTeeth; + this.id = id; + this.momentum = momentum != null ? momentum : 0; + this.group = group != null ? group : 0; + this.level = level != null ? level : 0; + this.connections = connections != null ? connections : {}; + if (this.id == null) { + this.id = Util.createUUID(); + } + this.pitchRadius = Util.MODULE * (0.5 * this.numberOfTeeth); + this.innerRadius = Util.MODULE * (0.5 * this.numberOfTeeth - 1.25); + this.outerRadius = Util.MODULE * (0.5 * this.numberOfTeeth + 1); + } + + Gear.prototype.getCircumference = function() { + return 2 * Math.PI * this.pitchRadius; + }; + + Gear.prototype.distanceToPoint = function(point) { + return Math.max(0, this.location.distance(point) - this.pitchRadius); + }; + + Gear.prototype.edgeDistance = function(gear) { + var axisDistance; + axisDistance = this.location.distance(gear.location); + return Math.abs(axisDistance - this.pitchRadius - gear.pitchRadius); + }; + + Gear.prototype.restore = function(gear) { + this.location = gear.location; + this.rotation = gear.rotation; + this.momentum = gear.momentum; + this.group = gear.group; + this.level = gear.level; + return this.connections = gear.connections; + }; + + Gear.prototype.clone = function() { + return new Gear(this.location.clone(), this.rotation, this.numberOfTeeth, this.id, this.momentum, this.group, this.level, Util.clone(this.connections)); + }; + + Gear.fromObject = function(obj) { + return new Gear(Point.fromObject(obj.location), obj.rotation, obj.numberOfTeeth, obj.id, obj.momentum, obj.group, obj.level, obj.connections); + }; + + return Gear; + + })(); + + window.gearsketch.model.Gear = Gear; + + Chain = (function() { + Chain.WIDTH = 8; + + Chain.prototype.points = []; + + Chain.prototype.segments = []; + + function Chain(stroke, id, group, level, connections) { + this.id = id; + this.group = group != null ? group : 0; + this.level = level != null ? level : 0; + this.connections = connections != null ? connections : {}; + if (this.id == null) { + this.id = Util.createUUID(); + } + this.points = Util.clone(stroke); + this.rotation = 0; + } + + Chain.prototype.getLength = function() { + return this.segments.reduce((function(total, segment) { + return total + segment.getLength(); + }), 0); + }; + + Chain.prototype.getCircumference = function() { + return this.getLength(); + }; + + Chain.prototype.getStartingPoint = function() { + if (this.direction === Util.Direction.CLOCKWISE) { + return this.rotation / (2 * Math.PI) * this.getLength(); + } else { + return -this.rotation / (2 * Math.PI) * this.getLength(); + } + }; + + Chain.prototype.findPointOnChain = function(distance) { + var distanceToGo, length, segment, segmentLength, _i, _len, _ref; + length = this.getLength(); + distanceToGo = Util.mod(distance + this.getStartingPoint(), length); + _ref = this.segments; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + segment = _ref[_i]; + segmentLength = segment.getLength(); + if (distanceToGo < segmentLength) { + return segment.findPoint(distanceToGo); + } else { + distanceToGo -= segmentLength; + } + } + return null; + }; + + Chain.prototype.findPointsOnChain = function(numberOfPoints) { + var delta, p, _i, _results; + delta = this.getLength() / numberOfPoints; + _results = []; + for (p = _i = 0; 0 <= numberOfPoints ? _i < numberOfPoints : _i > numberOfPoints; p = 0 <= numberOfPoints ? ++_i : --_i) { + _results.push(this.findPointOnChain(p * delta)); + } + return _results; + }; + + Chain.prototype.distanceToPoint = function(point) { + var segment; + return Math.min.apply(null, (function() { + var _i, _len, _ref, _results; + _ref = this.segments; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + segment = _ref[_i]; + _results.push(segment.distanceToPoint(point)); + } + return _results; + }).call(this)); + }; + + Chain.prototype.distanceToSegment = function(s) { + var segment; + return Math.min.apply(null, (function() { + var _i, _len, _ref, _results; + _ref = this.segments; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + segment = _ref[_i]; + _results.push(segment.distanceToSegment(s)); + } + return _results; + }).call(this)); + }; + + Chain.prototype.distanceToChain = function(chain) { + var segment; + return Math.min.apply(null, (function() { + var _i, _len, _ref, _results; + _ref = this.segments; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + segment = _ref[_i]; + _results.push(chain.distanceToSegment(segment)); + } + return _results; + }).call(this)); + }; + + Chain.prototype.intersectsPath = function(path) { + var i, j, _i, _ref; + for (i = _i = 0, _ref = path.length - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + j = i + 1; + if (this.distanceToSegment(new LineSegment(path[i], path[j])) === 0) { + return true; + } + } + return false; + }; + + Chain.prototype.crossesNonSupportingGears = function(board) { + var gear, id, _ref; + _ref = board.getGears(); + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + gear = _ref[id]; + if (!(__indexOf.call(this.supportingGearIds, id) >= 0) && !(id in this.ignoredGearIds)) { + if (this.distanceToPoint(gear.location) < gear.pitchRadius + Util.EPSILON) { + return true; + } + } + } + return false; + }; + + Chain.prototype.findPointOnSupportingGear = function(gearIndex, incoming) { + if (incoming) { + return this.points[Util.mod(2 * gearIndex - 1, this.points.length)]; + } else { + return this.points[2 * gearIndex]; + } + }; + + Chain.prototype.removeGear = function(gear, board) { + var acknowledgedGears, afterIndex, beforeGear, beforeIndex, g, gears, index, numberOfGears, path, replacementGears; + while ((index = this.supportingGearIds.indexOf(gear.id)) !== -1) { + gears = board.getGearsWithIds(this.supportingGearIds); + numberOfGears = gears.length; + beforeIndex = Util.mod(index - 1, numberOfGears); + beforeGear = gears[beforeIndex]; + afterIndex = Util.mod(index + 1, numberOfGears); + acknowledgedGears = board.getAcknowledgedGears(this.ignoredGearIds); + path = [this.findPointOnSupportingGear(index, true), this.findPointOnSupportingGear(index, false), this.findPointOnSupportingGear(afterIndex, true)]; + replacementGears = this.findSupportingGearsOnPath(acknowledgedGears, beforeGear, path, 0, false); + gears.splice.apply(gears, [index, 1].concat(replacementGears)); + this.removeRepeatedGears(gears); + this.supportingGearIds = (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = gears.length; _i < _len; _i++) { + g = gears[_i]; + _results.push(g.id); + } + return _results; + })(); + } + return this.update(board); + }; + + Chain.prototype.findChainTangentSide = function(gear) { + if ((this.direction === Util.Direction.CLOCKWISE) === (gear.id in this.innerGearIds)) { + return Util.Side.LEFT; + } else { + return Util.Side.RIGHT; + } + }; + + Chain.prototype.findReverseChainTangentSide = function(gear) { + if (this.findChainTangentSide(gear) === Util.Side.LEFT) { + return Util.Side.RIGHT; + } else { + return Util.Side.LEFT; + } + }; + + Chain.prototype.findFirstSupportingGearOnPath = function(path, gears) { + var a, b, d, pathLength, stepSize, supportingGear; + stepSize = 10; + pathLength = Util.getLength(path); + supportingGear = null; + a = path[0]; + d = 0; + while (d < pathLength && (supportingGear == null)) { + d += stepSize; + b = Util.findPointOnPath(path, d); + supportingGear = Util.findNearestIntersectingGear(gears, new LineSegment(a, b)); + } + return [supportingGear, d]; + }; + + Chain.prototype.findSupportingGearsOnPath = function(gears, firstSupportingGear, path, startDistance, isClosed) { + var a, b, d, lineSegment, nextSupportingGear, pathLength, stepSize, supportingGear, supportingGears, tangentPoint, tangentSide; + if (startDistance == null) { + startDistance = 0; + } + if (isClosed == null) { + isClosed = true; + } + stepSize = 10; + pathLength = Util.getLength(path, isClosed); + supportingGear = firstSupportingGear; + supportingGears = []; + a = firstSupportingGear.location; + d = startDistance; + while (d < pathLength) { + d += stepSize; + b = Util.findPointOnPath(path, d); + tangentSide = this.findReverseChainTangentSide(supportingGear); + tangentPoint = Util.findGearTangentPoints(b, supportingGear)[tangentSide]; + if (tangentPoint != null) { + a = tangentPoint; + } + lineSegment = new LineSegment(a, b); + nextSupportingGear = Util.findNearestIntersectingGear(gears, lineSegment, Util.makeSet(supportingGear.id)); + if (nextSupportingGear != null) { + supportingGear = nextSupportingGear; + supportingGears.push(supportingGear); + } + } + return supportingGears; + }; + + Chain.prototype.removeRepeatedGears = function(gearsList) { + var g1, g2, i, j, numberOfGears, numberOfNoops; + numberOfNoops = 0; + i = 0; + while (numberOfNoops < (numberOfGears = gearsList.length)) { + j = (i + 1) % numberOfGears; + g1 = gearsList[i]; + g2 = gearsList[j]; + if (g1 === g2) { + gearsList.splice(j, 1); + numberOfNoops = 0; + } else { + numberOfNoops++; + i = (i + 1) % numberOfGears; + } + } + return gearsList; + }; + + Chain.prototype.containsSuccessiveOverlappingGears = function(gearsList) { + var g1, g2, i, j, numberOfGears, _i; + numberOfGears = gearsList.length; + for (i = _i = 0; 0 <= numberOfGears ? _i < numberOfGears : _i > numberOfGears; i = 0 <= numberOfGears ? ++_i : --_i) { + j = (i + 1) % numberOfGears; + g1 = gearsList[i]; + g2 = gearsList[j]; + if (g1.location.distance(g2.location) < (g1.outerRadius + g2.outerRadius)) { + return true; + } + } + return false; + }; + + Chain.prototype.findSupportingGearIds = function(gears) { + var finalSegment, firstSupportingGear, gear, lastSupportingGear, nextSupportingGears, startDistance, supportingGears, tangentPoint, tangentSide, _i, _len, _ref, _ref1, _results; + _ref = this.findFirstSupportingGearOnPath(this.points, gears), firstSupportingGear = _ref[0], startDistance = _ref[1]; + supportingGears = [firstSupportingGear]; + nextSupportingGears = this.findSupportingGearsOnPath(gears, firstSupportingGear, this.points, startDistance); + supportingGears = supportingGears.concat(nextSupportingGears); + tangentSide = this.findChainTangentSide(firstSupportingGear); + tangentPoint = Util.findGearTangentPoints(this.points[0], firstSupportingGear)[tangentSide]; + if (tangentPoint != null) { + finalSegment = [this.points[0], tangentPoint]; + lastSupportingGear = supportingGears[supportingGears.length - 1]; + nextSupportingGears = this.findSupportingGearsOnPath(gears, lastSupportingGear, finalSegment, 0, false); + supportingGears = supportingGears.concat(nextSupportingGears); + } + _ref1 = this.removeRepeatedGears(supportingGears); + _results = []; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + gear = _ref1[_i]; + _results.push(gear.id); + } + return _results; + }; + + Chain.prototype.findIgnoredGearIds = function(board) { + var acknowledgedLevels, currentDistance, currentLevel, d, distance, gear, gears, group, id, ignoredGearIds, level, levels, minDistances, _ref; + gears = board.getGears(); + minDistances = {}; + for (id in gears) { + if (!__hasProp.call(gears, id)) continue; + gear = gears[id]; + group = gear.group; + level = gear.level; + d = Util.pointPathDistance(gear.location, this.points) - gear.pitchRadius; + if ((((_ref = minDistances[group]) != null ? _ref[level] : void 0) == null) || d < minDistances[group][level]) { + if (minDistances[group] == null) { + minDistances[group] = {}; + } + minDistances[group][level] = d; + } + } + acknowledgedLevels = {}; + for (group in minDistances) { + if (!__hasProp.call(minDistances, group)) continue; + levels = minDistances[group]; + for (level in levels) { + if (!__hasProp.call(levels, level)) continue; + distance = levels[level]; + currentLevel = acknowledgedLevels[group]; + if (currentLevel == null) { + acknowledgedLevels[group] = parseInt(level, 10); + } else if (distance > 0) { + currentDistance = minDistances[group][currentLevel]; + if (currentDistance < 0 || distance < currentDistance) { + acknowledgedLevels[group] = parseInt(level, 10); + } + } + } + } + ignoredGearIds = {}; + for (id in gears) { + if (!__hasProp.call(gears, id)) continue; + gear = gears[id]; + if (acknowledgedLevels[gear.group] !== gear.level) { + ignoredGearIds[id] = true; + } + } + return ignoredGearIds; + }; + + Chain.prototype.findIgnoredGearIdsInTightenedChain = function(board) { + var gear, gearId, group, groups, id, level, updatedIgnoredGearIds, _i, _len, _ref, _ref1; + groups = {}; + _ref = this.supportingGearIds; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + gearId = _ref[_i]; + gear = board.getGearWithId(gearId); + group = gear.group; + level = gear.level; + if (groups[group] == null) { + groups[group] = {}; + } + groups[group][level] = true; + } + updatedIgnoredGearIds = {}; + _ref1 = board.getGears(); + for (id in _ref1) { + if (!__hasProp.call(_ref1, id)) continue; + gear = _ref1[id]; + group = gear.group; + level = gear.level; + if ((groups[group] != null) && (groups[group][level] == null)) { + updatedIgnoredGearIds[id] = true; + } + } + return this.ignoredGearIds = updatedIgnoredGearIds; + }; + + Chain.prototype.toPolygon = function(segments) { + var polygon, segment, _i, _len; + if (segments == null) { + segments = this.segments; + } + polygon = []; + for (_i = 0, _len = segments.length; _i < _len; _i++) { + segment = segments[_i]; + if (segment instanceof LineSegment) { + polygon.push(segment.start); + } else { + polygon.push(segment.findPoint(0)); + polygon.push(segment.findPoint(0.5 * segment.getLength())); + } + } + return polygon; + }; + + Chain.prototype.update = function(board, gears) { + var acknowledgedGears, arcEnd, arcSegment, arcStart, chainPolygon, direction, g1, g2, g3, gear, gearId, i, id, intersection, j, k, lineSegment, lineSegment1, lineSegment2, middleSegment, numberOfGears, numberOfSegments, p0, p1, p2, path, replacementGears, s1, s2, tangentLine, tangentPointG1, tangentPointG3, tangentSideG1, tangentSideG3, updatedAcknowledgedGears, updatedIgnoredGearIds, updatedInnerGearIds, updatedPoints, updatedSegments, _i, _j, _k, _l, _ref, _ref1, _ref2; + if (gears == null) { + gears = board.getGearsWithIds(this.supportingGearIds); + } + if (gears.length < 2) { + return false; + } + if (this.containsSuccessiveOverlappingGears(gears)) { + return false; + } + updatedIgnoredGearIds = this.findIgnoredGearIdsInTightenedChain(board); + acknowledgedGears = board.getAcknowledgedGears(updatedIgnoredGearIds); + i = 0; + while (i < (numberOfGears = gears.length)) { + j = (i + 1) % numberOfGears; + k = (i + 2) % numberOfGears; + g1 = gears[i]; + g2 = gears[j]; + g3 = gears[k]; + lineSegment1 = Util.findTangentLine(g1, g2, this.innerGearIds, this.direction); + lineSegment2 = Util.findTangentLine(g2, g3, this.innerGearIds, this.direction); + intersection = lineSegment1.findIntersection(lineSegment2); + if (intersection != null) { + tangentSideG1 = this.findReverseChainTangentSide(g1); + tangentPointG1 = Util.findGearTangentPoints(intersection, g1)[tangentSideG1]; + tangentSideG3 = this.findChainTangentSide(g3); + tangentPointG3 = Util.findGearTangentPoints(intersection, g3)[tangentSideG3]; + path = [tangentPointG1, intersection, tangentPointG3]; + replacementGears = this.findSupportingGearsOnPath(acknowledgedGears, g1, path, 0, false); + if (__indexOf.call(replacementGears, g2) >= 0) { + return false; + } + gears.splice.apply(gears, [j, 1].concat(replacementGears)); + this.removeRepeatedGears(gears); + return this.update(board, gears); + } + gear = Util.findNearestIntersectingGear(acknowledgedGears, lineSegment1, Util.makeSet(g1.id, g2.id)); + if (gear != null) { + gears.splice(j, 0, gear); + if (this.containsSuccessiveOverlappingGears(gears)) { + return false; + } + } + i++; + } + updatedPoints = []; + for (i = _i = 0; 0 <= numberOfGears ? _i < numberOfGears : _i > numberOfGears; i = 0 <= numberOfGears ? ++_i : --_i) { + j = (i + 1) % numberOfGears; + g1 = gears[i]; + g2 = gears[j]; + tangentLine = Util.findTangentLine(g1, g2, this.innerGearIds, this.direction); + updatedPoints.push(tangentLine.start, tangentLine.end); + } + updatedSegments = []; + for (i = _j = 0; 0 <= numberOfGears ? _j < numberOfGears : _j > numberOfGears; i = 0 <= numberOfGears ? ++_j : --_j) { + p0 = updatedPoints[2 * i]; + p1 = updatedPoints[2 * i + 1]; + p2 = updatedPoints[2 * ((i + 1) % numberOfGears)]; + gear = gears[(i + 1) % numberOfGears]; + lineSegment = new LineSegment(p0, p1); + arcStart = Math.atan2(p1.y - gear.location.y, p1.x - gear.location.x); + arcEnd = Math.atan2(p2.y - gear.location.y, p2.x - gear.location.x); + direction = (this.direction === Util.Direction.CLOCKWISE) === (gear.id in this.innerGearIds) ? Util.Direction.CLOCKWISE : Util.Direction.COUNTER_CLOCKWISE; + arcSegment = new ArcSegment(gear.location, gear.pitchRadius, arcStart, arcEnd, direction); + updatedSegments.push(lineSegment, arcSegment); + } + numberOfSegments = updatedSegments.length; + for (i = _k = 0, _ref = numberOfSegments - 2; 0 <= _ref ? _k < _ref : _k > _ref; i = 0 <= _ref ? ++_k : --_k) { + for (j = _l = _ref1 = i + 2; _ref1 <= numberOfSegments ? _l < numberOfSegments : _l > numberOfSegments; j = _ref1 <= numberOfSegments ? ++_l : --_l) { + if (i !== 0 || j !== numberOfSegments - 1) { + s1 = updatedSegments[i]; + s2 = updatedSegments[j]; + if (s1.distanceToSegment(s2) < Chain.WIDTH) { + if ((i + 2) === j) { + middleSegment = updatedSegments[i + 1]; + if ((middleSegment instanceof ArcSegment) && (middleSegment.getLength() < 2 * Chain.WIDTH)) { + continue; + } + } + if (((j + 2) % numberOfSegments) === i) { + middleSegment = updatedSegments[(j + 1) % numberOfSegments]; + if ((middleSegment instanceof ArcSegment) && (middleSegment.getLength() < 2 * Chain.WIDTH)) { + continue; + } + } + return false; + } + } + } + } + updatedIgnoredGearIds = this.findIgnoredGearIdsInTightenedChain(board); + updatedAcknowledgedGears = board.getAcknowledgedGears(updatedIgnoredGearIds); + chainPolygon = this.toPolygon(updatedSegments); + updatedInnerGearIds = {}; + for (id in updatedAcknowledgedGears) { + if (!__hasProp.call(updatedAcknowledgedGears, id)) continue; + gear = updatedAcknowledgedGears[id]; + if (Util.isPointInsidePolygon(gear.location, chainPolygon)) { + updatedInnerGearIds[id] = true; + } + } + _ref2 = this.innerGearIds; + for (gearId in _ref2) { + if (!__hasProp.call(_ref2, gearId)) continue; + if (!(gearId in updatedInnerGearIds) && (__indexOf.call(this.supportingGearIds, gearId) >= 0)) { + return false; + } + } + this.points = updatedPoints; + this.segments = updatedSegments; + this.ignoredGearIds = updatedIgnoredGearIds; + this.innerGearIds = updatedInnerGearIds; + this.supportingGearIds = (function() { + var _len, _m, _results; + _results = []; + for (_m = 0, _len = gears.length; _m < _len; _m++) { + gear = gears[_m]; + _results.push(gear.id); + } + return _results; + })(); + return true; + }; + + Chain.prototype.tighten = function(board) { + var acknowledgedGears, gear; + this.ignoredGearIds = this.findIgnoredGearIds(board); + acknowledgedGears = board.getAcknowledgedGears(this.ignoredGearIds); + this.innerGearIds = Util.makeSetFromList((function() { + var _i, _len, _ref, _results; + _ref = Util.findGearsInsidePolygon(this.points, acknowledgedGears); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + gear = _ref[_i]; + _results.push(gear.id); + } + return _results; + }).call(this)); + if (Object.keys(this.innerGearIds).length < 2) { + return false; + } + this.direction = Util.findDirection(this.points); + this.supportingGearIds = this.findSupportingGearIds(acknowledgedGears); + return this.update(board); + }; + + Chain.prototype.clone = function() { + var copy; + copy = new Chain(this.points, this.id, this.group, this.level, Util.clone(this.connections)); + copy.segments = Util.clone(this.segments); + copy.ignoredGearIds = Util.clone(this.ignoredGearIds); + copy.innerGearIds = Util.clone(this.innerGearIds); + copy.direction = this.direction; + copy.supportingGearIds = Util.clone(this.supportingGearIds); + return copy; + }; + + Chain.fromObject = function(obj) { + var chain, createSegment, p, points, segment; + createSegment = function(obj) { + if (obj.center != null) { + return ArcSegment.fromObject(obj); + } else { + return LineSegment.fromObject(obj); + } + }; + points = (function() { + var _i, _len, _ref, _results; + _ref = obj.points; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + p = _ref[_i]; + _results.push(new Point(p.x, p.y)); + } + return _results; + })(); + chain = new Chain(points, obj.id, obj.group, obj.level, obj.connections); + chain.segments = (function() { + var _i, _len, _ref, _results; + _ref = obj.segments; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + segment = _ref[_i]; + _results.push(createSegment(segment)); + } + return _results; + })(); + chain.ignoredGearIds = obj.ignoredGearIds; + chain.innerGearIds = obj.innerGearIds; + chain.direction = obj.direction; + chain.supportingGearIds = obj.supportingGearIds; + return chain; + }; + + return Chain; + + })(); + + window.gearsketch.model.Chain = Chain; + + Board = (function() { + var AXIS_RADIUS, ConnectionType, EPSILON, MIN_STACKED_GEARS_TEETH_DIFFERENCE, MODULE, SNAPPING_DISTANCE; + + MODULE = Util.MODULE; + + AXIS_RADIUS = Util.AXIS_RADIUS; + + MIN_STACKED_GEARS_TEETH_DIFFERENCE = Util.MIN_STACKED_GEARS_TEETH_DIFFERENCE; + + SNAPPING_DISTANCE = Util.SNAPPING_DISTANCE; + + EPSILON = Util.EPSILON; + + ConnectionType = { + ANY: "any", + MESHING: "meshing", + AXIS: "axis", + CHAIN_INSIDE: "chain_inside", + CHAIN_OUTSIDE: "chain_outside" + }; + + function Board(gears, chains) { + this.gears = gears != null ? gears : {}; + this.chains = chains != null ? chains : {}; + } + + Board.prototype.restore = function(board) { + var gear, id, _ref; + _ref = this.gears; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + gear = _ref[id]; + gear.restore(board.gears[id]); + } + return this.chains = board.chains; + }; + + Board.prototype.restoreAfterDemo = function(board) { + this.gears = board.gears; + return this.chains = board.chains; + }; + + Board.prototype.clear = function() { + this.gears = {}; + return this.chains = {}; + }; + + Board.prototype.getNextGroup = function() { + var gear, id, nextGroup, _ref; + nextGroup = 0; + _ref = this.gears; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + gear = _ref[id]; + nextGroup = Math.max(nextGroup, gear.group + 1); + } + return nextGroup; + }; + + Board.prototype.getGears = function() { + return this.gears; + }; + + Board.prototype.getGearList = function() { + var gear, id, _ref, _results; + _ref = this.gears; + _results = []; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + gear = _ref[id]; + _results.push(gear); + } + return _results; + }; + + Board.prototype.getAcknowledgedGears = function(ignoredGearIds) { + var acknowledgedGears, gear, id, _ref; + acknowledgedGears = {}; + _ref = this.gears; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + gear = _ref[id]; + if (!(id in ignoredGearIds)) { + acknowledgedGears[id] = gear; + } + } + return acknowledgedGears; + }; + + Board.prototype.getLevelScore = function(gear) { + return 1000 * gear.group + gear.level; + }; + + Board.prototype.getGearsSortedByGroupAndLevel = function(gears) { + var _this = this; + if (gears == null) { + gears = this.getGearList(); + } + return gears.sort(function(g1, g2) { + return _this.getLevelScore(g1) - _this.getLevelScore(g2); + }); + }; + + Board.prototype.removeConnection = function(turningObject1, turningObject2) { + delete turningObject1.connections[turningObject2.id]; + return delete turningObject2.connections[turningObject1.id]; + }; + + Board.prototype.removeAllConnections = function(turningObject) { + var neighbor, neighborId, _ref; + _ref = turningObject.connections; + for (neighborId in _ref) { + if (!__hasProp.call(_ref, neighborId)) continue; + neighbor = this.getTurningObjects()[neighborId]; + this.removeConnection(turningObject, neighbor); + } + return this.updateGroupsAndLevels(); + }; + + Board.prototype.findNearestAxis = function(gear) { + var candidate, distance, id, nearestAxis, shortestDistance, _ref; + nearestAxis = null; + shortestDistance = Number.MAX_VALUE; + _ref = this.gears; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + candidate = _ref[id]; + if (candidate !== gear) { + distance = gear.location.distance(candidate.location); + if (!nearestAxis || distance < (shortestDistance - EPSILON) || (distance < (shortestDistance + EPSILON) && candidate.numberOfTeeth < nearestAxis.numberOfTeeth)) { + nearestAxis = candidate; + shortestDistance = distance; + } + } + } + return nearestAxis; + }; + + Board.prototype.updateGroupsAndLevelsFrom = function(turningObjectId, group, level, updatedGroups, updatedLevels) { + var connectionType, connections, gear, neighbor, neighborId, sameLevelConnectionTypes, turningObject, _results; + turningObject = this.getTurningObjects()[turningObjectId]; + updatedGroups[turningObjectId] = group; + updatedLevels[turningObjectId] = level; + connections = turningObject.connections; + sameLevelConnectionTypes = [ConnectionType.MESHING, ConnectionType.CHAIN_INSIDE, ConnectionType.CHAIN_OUTSIDE]; + _results = []; + for (neighborId in connections) { + if (!__hasProp.call(connections, neighborId)) continue; + connectionType = connections[neighborId]; + if (!(neighborId in updatedGroups)) { + if (__indexOf.call(sameLevelConnectionTypes, connectionType) >= 0) { + _results.push(this.updateGroupsAndLevelsFrom(neighborId, group, level, updatedGroups, updatedLevels)); + } else { + gear = this.gears[turningObjectId]; + neighbor = this.gears[neighborId]; + if (gear.numberOfTeeth > neighbor.numberOfTeeth) { + _results.push(this.updateGroupsAndLevelsFrom(neighborId, group, level + 1, updatedGroups, updatedLevels)); + } else { + _results.push(this.updateGroupsAndLevelsFrom(neighborId, group, level - 1, updatedGroups, updatedLevels)); + } + } + } else { + _results.push(void 0); + } + } + return _results; + }; + + Board.prototype.updateGroupsAndLevels = function() { + var group, id, turningObject, updatedGroups, updatedLevels, _ref, _ref1; + updatedGroups = {}; + updatedLevels = {}; + group = 0; + _ref = this.gears; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + if (!(id in updatedGroups)) { + this.updateGroupsAndLevelsFrom(id, group, 0, updatedGroups, updatedLevels); + group++; + } + } + _ref1 = this.getTurningObjects(); + for (id in _ref1) { + if (!__hasProp.call(_ref1, id)) continue; + turningObject = _ref1[id]; + turningObject.group = updatedGroups[id]; + turningObject.level = updatedLevels[id]; + } + return null; + }; + + Board.prototype.addConnection = function(turningObject1, turningObject2, connectionType) { + turningObject1.connections[turningObject2.id] = connectionType; + turningObject2.connections[turningObject1.id] = connectionType; + return this.updateGroupsAndLevels(); + }; + + Board.prototype.findMeshingNeighbors = function(gear) { + var candidate, candidateId, meshingNeighbors, _ref; + meshingNeighbors = []; + _ref = this.gears; + for (candidateId in _ref) { + if (!__hasProp.call(_ref, candidateId)) continue; + candidate = _ref[candidateId]; + if (candidate !== gear && gear.edgeDistance(candidate) < EPSILON) { + if ((candidate.group !== gear.group) || (candidate.level === gear.level)) { + meshingNeighbors.push(candidate); + } + } + } + return meshingNeighbors; + }; + + Board.prototype.findRelativeAlignment = function(gear1, gear2) { + var angle1, angle2, p1, p2, phase1, phase2, phaseSum, r1, r2, shift1, shift2, toothAngle1, toothAngle2; + p1 = gear1.location; + r1 = gear1.rotation; + p2 = gear2.location; + r2 = gear2.rotation; + angle1 = Math.atan2(p2.y - p1.y, p2.x - p1.x); + angle2 = angle1 + Math.PI; + shift1 = Util.mod(angle1 - r1, 2 * Math.PI); + shift2 = Util.mod(angle2 - r2, 2 * Math.PI); + toothAngle1 = (2 * Math.PI) / gear1.numberOfTeeth; + toothAngle2 = (2 * Math.PI) / gear2.numberOfTeeth; + phase1 = (shift1 % toothAngle1) / toothAngle1; + phase2 = (shift2 % toothAngle2) / toothAngle2; + phaseSum = (phase1 + phase2) % 1; + return (phaseSum - 0.25) * toothAngle1; + }; + + Board.prototype.alignGearTeeth = function(rotatingGear, meshingGear) { + return rotatingGear.rotation += this.findRelativeAlignment(rotatingGear, meshingGear); + }; + + Board.prototype.areMeshingGearsAligned = function(gear1, gear2) { + return Math.abs(this.findRelativeAlignment(gear1, gear2)) < EPSILON; + }; + + Board.prototype.rotateTurningObjectsFrom = function(turningObject, angle, rotatedTurningObjectIds) { + var connectionType, neighbor, neighborId, ratio, _ref, _results; + if (!(turningObject.id in rotatedTurningObjectIds)) { + turningObject.rotation = Util.mod(turningObject.rotation + angle, 2 * Math.PI); + rotatedTurningObjectIds[turningObject.id] = true; + } + _ref = turningObject.connections; + _results = []; + for (neighborId in _ref) { + if (!__hasProp.call(_ref, neighborId)) continue; + connectionType = _ref[neighborId]; + neighbor = this.getTurningObjects()[neighborId]; + if (!(neighborId in rotatedTurningObjectIds)) { + ratio = this.calculateRatio(turningObject, neighbor, connectionType); + _results.push(this.rotateTurningObjectsFrom(neighbor, angle * ratio, rotatedTurningObjectIds)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + Board.prototype.alignMeshingGears = function(gear) { + var angle, neighbor, neighbors, r, rotatedGearIds, _i, _len, _results; + rotatedGearIds = {}; + rotatedGearIds[gear.id] = true; + neighbors = this.findMeshingNeighbors(gear); + _results = []; + for (_i = 0, _len = neighbors.length; _i < _len; _i++) { + neighbor = neighbors[_i]; + this.addConnection(gear, neighbor, ConnectionType.MESHING); + r = neighbor.rotation; + this.alignGearTeeth(neighbor, gear); + angle = neighbor.rotation - r; + rotatedGearIds[neighbor.id] = true; + _results.push(this.rotateTurningObjectsFrom(neighbor, angle, rotatedGearIds)); + } + return _results; + }; + + Board.prototype.connectToAxis = function(upperGear, lowerGear) { + this.addConnection(upperGear, lowerGear, ConnectionType.AXIS); + upperGear.location = lowerGear.location.clone(); + upperGear.rotation = lowerGear.rotation; + return this.alignMeshingGears(upperGear); + }; + + Board.prototype.findNearestNeighbor = function(gear, gearIdsToIgnore) { + var edgeDistance, nearestNeighbor, neighbor, neighborId, shortestEdgeDistance, _ref; + if (gearIdsToIgnore == null) { + gearIdsToIgnore = {}; + } + nearestNeighbor = null; + shortestEdgeDistance = Number.MAX_VALUE; + _ref = this.gears; + for (neighborId in _ref) { + if (!__hasProp.call(_ref, neighborId)) continue; + neighbor = _ref[neighborId]; + if (neighbor !== gear && !(neighborId in gearIdsToIgnore)) { + edgeDistance = gear.edgeDistance(neighbor); + if (edgeDistance < shortestEdgeDistance) { + nearestNeighbor = neighbor; + shortestEdgeDistance = edgeDistance; + } + } + } + return nearestNeighbor; + }; + + Board.prototype.connectToOneMeshingGear = function(gear, meshingGear) { + var angle, delta; + delta = gear.location.minus(meshingGear.location); + angle = Math.atan2(delta.y, delta.x); + gear.location = meshingGear.location.plus(Point.polar(angle, gear.pitchRadius + meshingGear.pitchRadius)); + this.alignGearTeeth(gear, meshingGear); + return this.addConnection(gear, meshingGear, ConnectionType.MESHING); + }; + + Board.prototype.connectToTwoMeshingGears = function(gear, meshingGear1, meshingGear2) { + var a, d, h, p0, p1, p2, p3_1, p3_2, p3x1, p3x2, p3y1, p3y2, r0, r1; + p0 = meshingGear1.location; + p1 = meshingGear2.location; + r0 = meshingGear1.pitchRadius + gear.pitchRadius; + r1 = meshingGear2.pitchRadius + gear.pitchRadius; + d = p0.distance(p1); + if (r0 + r1 < d || p0.distance(p1) < EPSILON) { + if (gear.edgeDistance(meshingGear1) < gear.edgeDistance(meshingGear2)) { + this.connectToOneMeshingGear(gear, meshingGear1); + return; + } else { + this.connectToOneMeshingGear(gear, meshingGear2); + return; + } + } + a = (r0 * r0 - r1 * r1 + d * d) / (2 * d); + h = Math.sqrt(r0 * r0 - a * a); + p2 = p0.plus(p1.minus(p0).times(a / d)); + p3x1 = p2.x + h * (p1.y - p0.y) / d; + p3y1 = p2.y - h * (p1.x - p0.x) / d; + p3x2 = p2.x - h * (p1.y - p0.y) / d; + p3y2 = p2.y + h * (p1.x - p0.x) / d; + p3_1 = new Point(p3x1, p3y1); + p3_2 = new Point(p3x2, p3y2); + if (gear.location.distance(p3_1) < gear.location.distance(p3_2)) { + gear.location = p3_1; + } else { + gear.location = p3_2; + } + return this.alignMeshingGears(gear); + }; + + Board.prototype.doChainsCrossNonSupportingGears = function() { + var chain, id, _ref; + _ref = this.chains; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + chain = _ref[id]; + if (chain.crossesNonSupportingGears(this)) { + return true; + } + } + return false; + }; + + Board.prototype.doChainsCrossEachOther = function(c1, c2) { + if ((c1.group !== c2.group) || (c1.level === c2.level)) { + if (c1.distanceToChain(c2) < Chain.WIDTH) { + return true; + } + } + return false; + }; + + Board.prototype.doesChainCrossAnyOtherChain = function(chain) { + var chain2, id2, _ref; + _ref = this.chains; + for (id2 in _ref) { + if (!__hasProp.call(_ref, id2)) continue; + chain2 = _ref[id2]; + if (chain !== chain2) { + if (this.doChainsCrossEachOther(chain, chain2)) { + return true; + } + } + } + return false; + }; + + Board.prototype.doAnyChainsCrossEachOther = function() { + var c1, c2, chain, chainList, i, id, j, numberOfChains, _i, _j, _ref, _ref1; + chainList = (function() { + var _ref, _results; + _ref = this.chains; + _results = []; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + chain = _ref[id]; + _results.push(chain); + } + return _results; + }).call(this); + numberOfChains = chainList.length; + if (numberOfChains < 2) { + return false; + } + for (i = _i = 0, _ref = numberOfChains - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + for (j = _j = _ref1 = i + 1; _ref1 <= numberOfChains ? _j < numberOfChains : _j > numberOfChains; j = _ref1 <= numberOfChains ? ++_j : --_j) { + c1 = chainList[i]; + c2 = chainList[j]; + if (this.doChainsCrossEachOther(c1, c2)) { + return true; + } + } + } + return false; + }; + + Board.prototype.areAllMeshingGearsAligned = function() { + var g1, g2, gears, i, j, numberOfGears, _i, _j, _ref, _ref1; + gears = this.getGearList(); + numberOfGears = gears.length; + if (numberOfGears < 2) { + return true; + } + for (i = _i = 0, _ref = numberOfGears - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + for (j = _j = _ref1 = i + 1; _ref1 <= numberOfGears ? _j < numberOfGears : _j > numberOfGears; j = _ref1 <= numberOfGears ? ++_j : --_j) { + g1 = gears[i]; + g2 = gears[j]; + if (g1.connections[g2.id] === ConnectionType.MESHING) { + if (!this.areMeshingGearsAligned(g1, g2)) { + return false; + } + } + } + } + return true; + }; + + Board.prototype.calculateRatio = function(turningObject1, turningObject2, connectionType) { + if (connectionType === ConnectionType.AXIS) { + return 1; + } else if ((connectionType === ConnectionType.MESHING) || (connectionType === ConnectionType.CHAIN_OUTSIDE)) { + return -turningObject1.getCircumference() / turningObject2.getCircumference(); + } else { + return turningObject1.getCircumference() / turningObject2.getCircumference(); + } + }; + + Board.prototype.calculatePathRatio = function(path) { + var connectionType, i, pathLength, ratio, turningObject1, turningObject2, _i, _ref; + ratio = 1; + pathLength = path.length; + for (i = _i = 0, _ref = pathLength - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + turningObject1 = path[i]; + turningObject2 = path[i + 1]; + connectionType = turningObject1.connections[turningObject2.id]; + ratio *= this.calculateRatio(turningObject1, turningObject2, connectionType); + } + return ratio; + }; + + Board.prototype.areConnectionRatiosConsistent = function() { + var path, pathName, paths, ratio, ratios, _i, _len; + ratios = {}; + paths = Util.findAllSimplePathsBetweenNeighbors(this.getTurningObjects()); + for (_i = 0, _len = paths.length; _i < _len; _i++) { + path = paths[_i]; + pathName = "" + path[0].id + "-" + path[path.length - 1].id; + ratio = this.calculatePathRatio(path); + if (ratios[pathName] == null) { + ratios[pathName] = ratio; + } else { + if (Math.abs(ratios[pathName] - ratio) > EPSILON) { + return false; + } + } + } + return true; + }; + + Board.prototype.isBoardValid = function() { + var axisDistance, combinedOuterRadius, gear1, gear2, group1, group2, id1, id2, level1, level2, maxOuterRadius, _ref, _ref1; + _ref = this.gears; + for (id1 in _ref) { + if (!__hasProp.call(_ref, id1)) continue; + gear1 = _ref[id1]; + group1 = gear1.group; + level1 = gear1.level; + _ref1 = this.gears; + for (id2 in _ref1) { + if (!__hasProp.call(_ref1, id2)) continue; + gear2 = _ref1[id2]; + if (gear1 !== gear2) { + group2 = gear2.group; + level2 = gear2.level; + axisDistance = gear1.location.distance(gear2.location); + maxOuterRadius = Math.max(gear1.outerRadius, gear2.outerRadius); + combinedOuterRadius = gear1.outerRadius + gear2.outerRadius; + if (axisDistance < EPSILON) { + if ((group1 !== group2) || (level1 === level2)) { + return false; + } + } else if (group1 === group2 && level1 === level2 && (gear1.connections[gear2.id] == null)) { + if (axisDistance < combinedOuterRadius) { + return false; + } + } else if (axisDistance < maxOuterRadius + AXIS_RADIUS) { + return false; + } + } + } + } + return !this.doChainsCrossNonSupportingGears() && !this.doAnyChainsCrossEachOther() && this.areAllMeshingGearsAligned() && this.areConnectionRatiosConsistent(); + }; + + Board.prototype.placeGear = function(gear, location) { + var chain, id, nearestAxis, neighbor1, neighbor2, oldBoard, _ref; + oldBoard = this.clone(); + this.removeAllConnections(gear); + gear.location = location.clone(); + nearestAxis = this.findNearestAxis(gear); + if (nearestAxis && gear.location.distance(nearestAxis.location) < SNAPPING_DISTANCE && nearestAxis.numberOfTeeth - gear.numberOfTeeth > MIN_STACKED_GEARS_TEETH_DIFFERENCE) { + this.connectToAxis(gear, nearestAxis); + } else { + neighbor1 = this.findNearestNeighbor(gear); + if (neighbor1 && gear.edgeDistance(neighbor1) < SNAPPING_DISTANCE) { + neighbor2 = this.findNearestNeighbor(gear, Util.makeSet(neighbor1.id)); + if (neighbor2 && gear.edgeDistance(neighbor2) < SNAPPING_DISTANCE) { + this.connectToTwoMeshingGears(gear, neighbor1, neighbor2); + } else { + this.connectToOneMeshingGear(gear, neighbor1); + } + } + } + _ref = this.chains; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + chain = _ref[id]; + if (chain.update(this)) { + this.updateChainConnections(chain); + } else { + this.restore(oldBoard); + return false; + } + } + if (this.isBoardValid()) { + return true; + } else { + this.restore(oldBoard); + return false; + } + }; + + Board.prototype.addGearToChains = function(gear) { + var chain, id, _ref, _results; + _ref = this.chains; + _results = []; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + chain = _ref[id]; + if (Util.isPointInsidePolygon(gear.location, chain.toPolygon())) { + _results.push(chain.innerGearIds[gear.id] = true); + } else { + _results.push(void 0); + } + } + return _results; + }; + + Board.prototype.removeGearFromChains = function(gear) { + var chain, id, _ref, _results; + _ref = this.chains; + _results = []; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + chain = _ref[id]; + if (!chain.removeGear(gear, this) || this.doesChainCrossAnyOtherChain(chain)) { + _results.push(this.removeChain(chain)); + } else { + _results.push(this.updateChainConnections(chain)); + } + } + return _results; + }; + + Board.prototype.addGear = function(gear) { + var oldBoard; + oldBoard = this.clone(); + gear.group = this.getNextGroup(); + this.gears[gear.id] = gear; + this.addGearToChains(gear); + if (!this.placeGear(gear, gear.location)) { + this.removeGear(gear); + this.restore(oldBoard); + return false; + } else { + return true; + } + }; + + Board.prototype.removeGear = function(gear) { + this.removeAllConnections(gear); + delete this.gears[gear.id]; + return this.removeGearFromChains(gear); + }; + + Board.prototype.getGearAt = function(location, candidates) { + var candidate, distance, gear, id; + if (candidates == null) { + candidates = this.gears; + } + gear = null; + for (id in candidates) { + if (!__hasProp.call(candidates, id)) continue; + candidate = candidates[id]; + distance = location.distance(candidate.location); + if (distance < candidate.outerRadius) { + if (!gear || candidate.numberOfTeeth < gear.numberOfTeeth) { + gear = candidate; + } + } + } + return gear; + }; + + Board.prototype.isTopLevelGear = function(gear) { + var connectionType, id, _ref; + _ref = gear.connections; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + connectionType = _ref[id]; + if (connectionType === ConnectionType.AXIS && this.gears[id].level > gear.level) { + return false; + } + } + return true; + }; + + Board.prototype.getTopLevelGears = function() { + var gear, id, topLevelGears, _ref; + topLevelGears = {}; + _ref = this.gears; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + gear = _ref[id]; + if (this.isTopLevelGear(gear)) { + topLevelGears[id] = gear; + } + } + return topLevelGears; + }; + + Board.prototype.getTopLevelGearAt = function(location) { + return this.getGearAt(location, this.getTopLevelGears()); + }; + + Board.prototype.getGearWithId = function(id) { + return this.gears[id]; + }; + + Board.prototype.getGearsWithIds = function(ids) { + var id, _i, _len, _results; + _results = []; + for (_i = 0, _len = ids.length; _i < _len; _i++) { + id = ids[_i]; + _results.push(this.gears[id]); + } + return _results; + }; + + Board.prototype.rotateAllTurningObjects = function(delta) { + var angle, gear, id, _ref, _results; + _ref = this.gears; + _results = []; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + gear = _ref[id]; + if (gear.momentum) { + angle = gear.momentum * (delta / 1000); + _results.push(this.rotateTurningObjectsFrom(gear, angle, {})); + } else { + _results.push(void 0); + } + } + return _results; + }; + + Board.prototype.addChainConnections = function(chain) { + var gearId, _i, _len, _ref, _results; + _ref = chain.supportingGearIds; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + gearId = _ref[_i]; + if (gearId in chain.innerGearIds) { + _results.push(this.addConnection(chain, this.getGearWithId(gearId), ConnectionType.CHAIN_INSIDE)); + } else { + _results.push(this.addConnection(chain, this.getGearWithId(gearId), ConnectionType.CHAIN_OUTSIDE)); + } + } + return _results; + }; + + Board.prototype.updateChainConnections = function(chain) { + this.removeAllConnections(chain); + return this.addChainConnections(chain); + }; + + Board.prototype.addChain = function(chain) { + var oldBoard; + oldBoard = this.clone(); + this.chains[chain.id] = chain; + if (chain.tighten(this)) { + this.chains[chain.id] = chain; + this.addChainConnections(chain); + } else { + this.restore(oldBoard); + return false; + } + if (this.isBoardValid()) { + return true; + } else { + this.restore(oldBoard); + return false; + } + }; + + Board.prototype.removeChain = function(chain) { + this.removeAllConnections(chain); + return delete this.chains[chain.id]; + }; + + Board.prototype.getChains = function() { + return this.chains; + }; + + Board.prototype.getChainsInGroupOnLevel = function(group, level) { + var chain, id, _ref, _results; + _ref = this.chains; + _results = []; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + chain = _ref[id]; + if ((chain.group === group) && (chain.level === level)) { + _results.push(chain); + } + } + return _results; + }; + + Board.prototype.getTurningObjects = function() { + var chain, gear, id, turningObjects, _ref, _ref1; + turningObjects = {}; + _ref = this.gears; + for (id in _ref) { + if (!__hasProp.call(_ref, id)) continue; + gear = _ref[id]; + turningObjects[id] = gear; + } + _ref1 = this.chains; + for (id in _ref1) { + if (!__hasProp.call(_ref1, id)) continue; + chain = _ref1[id]; + turningObjects[id] = chain; + } + return turningObjects; + }; + + Board.prototype.clone = function() { + return { + gears: Util.clone(this.gears), + chains: Util.clone(this.chains) + }; + }; + + Board.fromObject = function(obj) { + var board, chain, gear, id, _ref, _ref1; + board = new Board(); + _ref = obj.gears; + for (id in _ref) { + gear = _ref[id]; + board.gears[id] = Gear.fromObject(gear); + } + _ref1 = obj.chains; + for (id in _ref1) { + chain = _ref1[id]; + board.chains[id] = Chain.fromObject(chain); + } + return board; + }; + + return Board; + + })(); + + window.gearsketch.model.Board = Board; + +}).call(this); + +/* +//@ sourceMappingURL=gearsketch_model.map +*/ diff --git a/activities/Gears.activity/lib/gearsketch/gearsketch_model.map b/activities/Gears.activity/lib/gearsketch/gearsketch_model.map new file mode 100644 index 000000000..703ea1b84 --- /dev/null +++ b/activities/Gears.activity/lib/gearsketch/gearsketch_model.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "gearsketch_model.js", + "sourceRoot": "../..", + "sources": [ + "lib/gearsketch/src/gearsketch_model.coffee" + ], + "names": [], + "mappings": "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" +} \ No newline at end of file diff --git a/activities/Gears.activity/lib/gearsketch/gearsketch_util.js b/activities/Gears.activity/lib/gearsketch/gearsketch_util.js new file mode 100644 index 000000000..76cffbdb1 --- /dev/null +++ b/activities/Gears.activity/lib/gearsketch/gearsketch_util.js @@ -0,0 +1,727 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + "use strict"; + var ArcSegment, LineSegment, Point, Util, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + __hasProp = {}.hasOwnProperty, + __slice = [].slice; + + window.gearsketch = {}; + + Point = (function() { + function Point(x, y) { + this.x = x; + this.y = y; + } + + Point.prototype.plus = function(p) { + return new Point(this.x + p.x, this.y + p.y); + }; + + Point.prototype.minus = function(p) { + return new Point(this.x - p.x, this.y - p.y); + }; + + Point.prototype.times = function(n) { + return new Point(n * this.x, n * this.y); + }; + + Point.prototype.distance = function(p) { + return Math.sqrt(Math.pow(this.x - p.x, 2) + Math.pow(this.y - p.y, 2)); + }; + + Point.prototype.cross = function(p) { + return this.x * p.y - this.y * p.x; + }; + + Point.prototype.clone = function() { + return new Point(this.x, this.y); + }; + + Point.polar = function(theta, r) { + return new Point(r * Math.cos(theta), r * Math.sin(theta)); + }; + + Point.fromObject = function(obj) { + return new Point(obj.x, obj.y); + }; + + return Point; + + })(); + + window.gearsketch.Point = Point; + + ArcSegment = (function() { + function ArcSegment(center, radius, startAngle, endAngle, direction) { + this.center = center; + this.radius = radius; + this.startAngle = startAngle; + this.endAngle = endAngle; + this.direction = direction; + this.start = this.pointOnCircle(startAngle); + this.end = this.pointOnCircle(endAngle); + } + + ArcSegment.prototype.getLength = function() { + var angle; + angle = this.direction === Util.Direction.CLOCKWISE ? Util.mod(this.endAngle - this.startAngle, 2 * Math.PI) : Util.mod(this.startAngle - this.endAngle, 2 * Math.PI); + return angle * this.radius; + }; + + ArcSegment.prototype.findPoint = function(distanceToGo) { + var angle, angleToGo; + angleToGo = distanceToGo / this.radius; + angle = this.startAngle + (this.direction === Util.Direction.CLOCKWISE ? angleToGo : -angleToGo); + return this.center.plus(Point.polar(angle, this.radius)); + }; + + ArcSegment.prototype.pointOnCircle = function(angle) { + return this.center.plus(Point.polar(angle, this.radius)); + }; + + ArcSegment.prototype.containsAngle = function(angle) { + if (this.direction === Util.Direction.CLOCKWISE) { + return Util.mod(this.endAngle - this.startAngle, 2 * Math.PI) > Util.mod(angle - this.startAngle, 2 * Math.PI); + } else { + return Util.mod(this.startAngle - this.endAngle, 2 * Math.PI) > Util.mod(this.startAngle - angle, 2 * Math.PI); + } + }; + + ArcSegment.prototype.distanceToPoint = function(point) { + var angle; + angle = Math.atan2(point.y - this.center.y, point.x - this.center.x); + if (this.containsAngle(angle)) { + return Math.abs(point.distance(this.center) - this.radius); + } else { + return Math.min(point.distance(this.start), point.distance(this.end)); + } + }; + + ArcSegment.prototype.intersectsLineSegment = function(lineSegment) { + var d, discriminant, dr, dx, dy, i1, i1x, i1y, i2, i2x, i2y, p1, p2; + p1 = lineSegment.start.minus(this.center); + p2 = lineSegment.end.minus(this.center); + dx = p2.x - p1.x; + dy = p2.y - p1.y; + dr = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); + if (dr === 0) { + return false; + } + d = p1.x * p2.y - p2.x * p1.y; + discriminant = Math.pow(this.radius, 2) * Math.pow(dr, 2) - Math.pow(d, 2); + if (discriminant < 0) { + return false; + } else { + i1x = (d * dy + Util.sign(dy) * dx * Math.sqrt(discriminant)) / Math.pow(dr, 2); + i1y = (-d * dx + Math.abs(dy) * Math.sqrt(discriminant)) / Math.pow(dr, 2); + i1 = new Point(i1x, i1y).plus(this.center); + if (lineSegment.distanceToPoint(i1) < Util.EPSILON && this.distanceToPoint(i1) < Util.EPSILON) { + return true; + } + i2x = (d * dy - Util.sign(dy) * dx * Math.sqrt(discriminant)) / Math.pow(dr, 2); + i2y = (-d * dx - Math.abs(dy) * Math.sqrt(discriminant)) / Math.pow(dr, 2); + i2 = new Point(i2x, i2y).plus(this.center); + if (lineSegment.distanceToPoint(i2) < Util.EPSILON && this.distanceToPoint(i2) < Util.EPSILON) { + return true; + } + return false; + } + }; + + ArcSegment.prototype.distanceToSegment = function(segment) { + var angle1, angle2, centerDistance, pointNearestToCenter; + if (segment instanceof ArcSegment) { + if (this.center.distance(segment.center) > Util.EPSILON) { + angle1 = Math.atan2(segment.center.y - this.center.y, segment.center.x - this.center.x); + angle2 = Util.mod(angle1 + Math.PI, 2 * Math.PI); + if (this.containsAngle(angle1) && segment.containsAngle(angle2)) { + centerDistance = this.center.distance(segment.center); + return Math.max(0, centerDistance - this.radius - segment.radius); + } + } + return Math.min(this.distanceToPoint(segment.start), this.distanceToPoint(segment.end), segment.distanceToPoint(this.start), segment.distanceToPoint(this.end)); + } else { + if (this.intersectsLineSegment(segment)) { + return 0; + } else { + pointNearestToCenter = segment.findNearestPoint(this.center); + return Math.min(this.distanceToPoint(pointNearestToCenter), this.distanceToPoint(segment.start), this.distanceToPoint(segment.end), segment.distanceToPoint(this.start), segment.distanceToPoint(this.end)); + } + } + }; + + ArcSegment.prototype.clone = function() { + return new ArcSegment(this.center.clone(), this.radius, this.startAngle, this.endAngle, this.direction); + }; + + ArcSegment.fromObject = function(obj) { + return new ArcSegment(Point.fromObject(obj.center), obj.radius, obj.startAngle, obj.endAngle, obj.direction); + }; + + return ArcSegment; + + })(); + + window.gearsketch.ArcSegment = ArcSegment; + + LineSegment = (function() { + function LineSegment(start, end) { + this.start = start; + this.end = end; + } + + LineSegment.prototype.getLength = function() { + return this.start.distance(this.end); + }; + + LineSegment.prototype.findPoint = function(distanceToGo) { + var fraction; + fraction = distanceToGo / this.start.distance(this.end); + return this.start.plus(this.end.minus(this.start).times(fraction)); + }; + + LineSegment.prototype.findNearestPoint = function(p) { + var segmentLength, t; + segmentLength = this.getLength(); + if (segmentLength === 0) { + return this.start; + } else { + t = ((p.x - this.start.x) * (this.end.x - this.start.x) + (p.y - this.start.y) * (this.end.y - this.start.y)) / Math.pow(segmentLength, 2); + if (t < 0) { + return this.start; + } else if (t > 1) { + return this.end; + } else { + return this.start.plus(this.end.minus(this.start).times(t)); + } + } + }; + + LineSegment.prototype.distanceToPoint = function(point) { + return point.distance(this.findNearestPoint(point)); + }; + + LineSegment.prototype.findIntersection = function(lineSegment) { + var crossRS, p, q, r, s, t, u; + p = this.start; + r = this.end.minus(p); + q = lineSegment.start; + s = lineSegment.end.minus(q); + crossRS = r.cross(s); + t = q.minus(p).cross(s) / crossRS; + u = q.minus(p).cross(r) / crossRS; + if (Math.abs(crossRS) > Util.EPSILON && 0 <= t && t <= 1 && 0 <= u && u <= 1) { + return p.plus(r.times(t)); + } else { + return null; + } + }; + + LineSegment.prototype.distanceToSegment = function(segment) { + if (segment instanceof LineSegment) { + if (this.findIntersection(segment)) { + return 0; + } else { + return Math.min(this.distanceToPoint(segment.start), this.distanceToPoint(segment.end), segment.distanceToPoint(this.start), segment.distanceToPoint(this.end)); + } + } else { + return segment.distanceToSegment(this); + } + }; + + LineSegment.prototype.clone = function() { + return new LineSegment(this.start.clone(), this.end.clone()); + }; + + LineSegment.fromObject = function(obj) { + return new LineSegment(Point.fromObject(obj.start), Point.fromObject(obj.end)); + }; + + return LineSegment; + + })(); + + window.gearsketch.LineSegment = LineSegment; + + Util = (function() { + function Util() {} + + Point = window.gearsketch.Point; + + Util.MODULE = 6; + + Util.AXIS_RADIUS = 1.5 * Util.MODULE; + + Util.MIN_STACKED_GEARS_TEETH_DIFFERENCE = 4; + + Util.SNAPPING_DISTANCE = 2 * Util.MODULE; + + Util.EPSILON = 0.000001; + + Util.Direction = { + CLOCKWISE: "clockwise", + COUNTER_CLOCKWISE: "counterclockwise" + }; + + Util.Side = { + LEFT: "left", + RIGHT: "right" + }; + + Util.clone = function(obj) { + var copy, i, key, knownClasses, _i, _ref, _ref1; + if ((obj == null) || (typeof obj !== "object")) { + return obj; + } + knownClasses = ["Point", "Gear", "ArcSegment", "LineSegment", "Chain"]; + if (_ref = obj.constructor.name, __indexOf.call(knownClasses, _ref) >= 0) { + return obj.clone(); + } + if (obj instanceof Array) { + copy = []; + for (i = _i = 0, _ref1 = obj.length; 0 <= _ref1 ? _i < _ref1 : _i > _ref1; i = 0 <= _ref1 ? ++_i : --_i) { + copy[i] = this.clone(obj[i]); + } + return copy; + } + if (obj instanceof Object) { + copy = {}; + for (key in obj) { + if (!__hasProp.call(obj, key)) continue; + copy[key] = this.clone(obj[key]); + } + return copy; + } + throw new Error("Unable to clone object. Its type is not supported."); + }; + + Util.createUUID = function() { + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { + var r, v; + r = Math.random() * 16 | 0; + v = c === "x" ? r : r & 0x3 | 0x8; + return v.toString(16); + }); + }; + + Util.mod = function(a, b) { + return (a % b + b) % b; + }; + + Util.sign = function(x) { + if (x < 0) { + return -1; + } else { + return 1; + } + }; + + Util.addAll = function(set, elements) { + var element, _i, _len; + for (_i = 0, _len = elements.length; _i < _len; _i++) { + element = elements[_i]; + set[element] = true; + } + return set; + }; + + Util.makeSetFromList = function(elements) { + return this.addAll({}, elements); + }; + + Util.makeSet = function() { + var elements; + elements = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + return this.makeSetFromList(elements); + }; + + Util.findPointOnPath = function(path, distance) { + var distanceToGo, i, j, numberOfPoints, segment, segmentLength; + distanceToGo = distance; + i = 0; + numberOfPoints = path.length; + while (distanceToGo > 0) { + j = (i + 1) % numberOfPoints; + segment = new LineSegment(path[i], path[j]); + segmentLength = segment.getLength(); + if (distanceToGo <= segmentLength) { + return segment.findPoint(distanceToGo); + } else { + i = j; + distanceToGo -= segmentLength; + } + } + return null; + }; + + Util.getLength = function(path, isPathClosed) { + var finalIndex, i, j, length, numberOfPoints, _i; + if (isPathClosed == null) { + isPathClosed = true; + } + length = 0; + numberOfPoints = path.length; + finalIndex = numberOfPoints - (isPathClosed ? 0 : 1); + for (i = _i = 0; 0 <= finalIndex ? _i < finalIndex : _i > finalIndex; i = 0 <= finalIndex ? ++_i : --_i) { + j = (i + 1) % numberOfPoints; + length += path[i].distance(path[j]); + } + return length; + }; + + Util.isPointInsidePolygon = function(point, polygon) { + var i, isPointInPolygon, j, numberOfVertices, pix, piy, pjx, pjy, x, y, _i; + isPointInPolygon = false; + x = point.x; + y = point.y; + numberOfVertices = polygon.length; + j = numberOfVertices - 1; + for (i = _i = 0; 0 <= numberOfVertices ? _i < numberOfVertices : _i > numberOfVertices; i = 0 <= numberOfVertices ? ++_i : --_i) { + pix = polygon[i].x; + piy = polygon[i].y; + pjx = polygon[j].x; + pjy = polygon[j].y; + if (((piy > y) !== (pjy > y)) && (x < ((pjx - pix) * (y - piy) / (pjy - piy) + pix))) { + isPointInPolygon = !isPointInPolygon; + } + j = i; + } + return isPointInPolygon; + }; + + Util.isGearInsidePolygon = function(gear, polygon) { + var edgePointAtAngle, edgePoints, i, + _this = this; + edgePointAtAngle = function(angle) { + return gear.location.plus(Point.polar(angle, gear.innerRadius)); + }; + edgePoints = (function() { + var _i, _results; + _results = []; + for (i = _i = 0; _i < 8; i = ++_i) { + _results.push(edgePointAtAngle(0.25 * Math.PI * i)); + } + return _results; + })(); + return edgePoints.every(function(p) { + return _this.isPointInsidePolygon(p, polygon); + }); + }; + + Util.findGearsInsidePolygon = function(polygon, gears) { + var gear, id, _results; + _results = []; + for (id in gears) { + if (!__hasProp.call(gears, id)) continue; + gear = gears[id]; + if (this.isGearInsidePolygon(gear, polygon)) { + _results.push(gear); + } + } + return _results; + }; + + Util.doesGearIntersectLineSegment = function(gear, segment) { + return segment.distanceToPoint(gear.location) < (gear.pitchRadius + Util.EPSILON); + }; + + Util.findGearsIntersectingSegment = function(gears, segment) { + var gear, id, _results; + _results = []; + for (id in gears) { + if (!__hasProp.call(gears, id)) continue; + gear = gears[id]; + if (this.doesGearIntersectLineSegment(gear, segment)) { + _results.push(gear); + } + } + return _results; + }; + + Util.pointPathDistance = function(point, path, isPathClosed) { + var d, distance, finalIndex, i, j, numberOfPoints, segment, _i; + if (isPathClosed == null) { + isPathClosed = true; + } + distance = Number.MAX_VALUE; + numberOfPoints = path.length; + finalIndex = numberOfPoints - (isPathClosed ? 0 : 1); + for (i = _i = 0; 0 <= finalIndex ? _i < finalIndex : _i > finalIndex; i = 0 <= finalIndex ? ++_i : --_i) { + j = (i + 1) % numberOfPoints; + segment = new LineSegment(path[i], path[j]); + d = Math.max(0, segment.distanceToPoint(point)); + distance = Math.min(distance, d); + } + return distance; + }; + + Util.findNearestIntersectingGear = function(gears, lineSegment, ignoredGearIds) { + var intersectingGear, intersectingGears, _i, _len, + _this = this; + if (ignoredGearIds == null) { + ignoredGearIds = {}; + } + intersectingGears = this.findGearsIntersectingSegment(gears, lineSegment); + intersectingGears.sort(function(g1, g2) { + return g1.distanceToPoint(lineSegment.start) - g2.distanceToPoint(lineSegment.start); + }); + for (_i = 0, _len = intersectingGears.length; _i < _len; _i++) { + intersectingGear = intersectingGears[_i]; + if (!(intersectingGear.id in ignoredGearIds)) { + return intersectingGear; + } + } + return null; + }; + + Util.findDirection = function(polygon) { + var doubleArea, i, j, numberOfPoints, _i; + numberOfPoints = polygon.length; + doubleArea = 0; + for (i = _i = 0; 0 <= numberOfPoints ? _i < numberOfPoints : _i > numberOfPoints; i = 0 <= numberOfPoints ? ++_i : --_i) { + j = (i + 1) % numberOfPoints; + doubleArea += polygon[i].x * polygon[j].y; + doubleArea -= polygon[i].y * polygon[j].x; + } + if (doubleArea > 0) { + return this.Direction.CLOCKWISE; + } else { + return this.Direction.COUNTER_CLOCKWISE; + } + }; + + Util.findTangentPoints = function(p, c, r) { + var alpha, beta, d, l, tangentPoints; + tangentPoints = {}; + d = p.distance(c); + if (Math.abs(d - r) < Util.EPSILON) { + tangentPoints[this.Side.RIGHT] = p.clone(); + tangentPoints[this.Side.LEFT] = p.clone(); + } else { + l = Math.sqrt(d * d - r * r); + alpha = Math.atan2(c.y - p.y, c.x - p.x); + beta = Math.asin(r / d); + tangentPoints[this.Side.RIGHT] = p.plus(Point.polar(alpha + beta, l)); + tangentPoints[this.Side.LEFT] = p.plus(Point.polar(alpha - beta, l)); + } + return tangentPoints; + }; + + Util.findGearTangentPoints = function(p, gear) { + return this.findTangentPoints(p, gear.location, gear.pitchRadius); + }; + + Util.findExternalTangents = function(centers, radii) { + var angle, largest, o1, o2, offset1, offset2, r1, r2, r3, ratio, tangentLine1, tangentLine2, tangentLines, tangentPoints, tpl, tpr; + largest = radii[0] >= radii[1] ? 0 : 1; + o1 = centers[largest]; + o2 = centers[1 - largest]; + r1 = radii[largest]; + r2 = radii[1 - largest]; + r3 = r1 - r2; + if (r3 === 0) { + tangentPoints = {}; + tangentPoints[this.Side.LEFT] = o1; + tangentPoints[this.Side.RIGHT] = o1; + angle = Math.atan2(o2.y - o1.y, o2.x - o1.x); + offset1 = Point.polar(angle + 0.5 * Math.PI, r1); + offset2 = Point.polar(angle - 0.5 * Math.PI, r1); + } else { + tangentPoints = this.findTangentPoints(o2, o1, r3); + ratio = r2 / r3; + tpl = tangentPoints[this.Side.LEFT]; + tpr = tangentPoints[this.Side.RIGHT]; + offset1 = tpl.minus(o1).times(ratio); + offset2 = tpr.minus(o1).times(ratio); + } + tangentLine1 = [tangentPoints[this.Side.LEFT].plus(offset1), o2.plus(offset1)]; + tangentLine2 = [tangentPoints[this.Side.RIGHT].plus(offset2), o2.plus(offset2)]; + tangentLines = {}; + if (o1 === centers[0]) { + tangentLines[this.Side.RIGHT] = new LineSegment(tangentLine1[0], tangentLine1[1]); + tangentLines[this.Side.LEFT] = new LineSegment(tangentLine2[0], tangentLine2[1]); + } else { + tangentLines[this.Side.RIGHT] = new LineSegment(tangentLine2[1], tangentLine2[0]); + tangentLines[this.Side.LEFT] = new LineSegment(tangentLine1[1], tangentLine1[0]); + } + return tangentLines; + }; + + Util.findInternalTangents = function(centers, radii) { + var largest, o1, o2, offset1, offset2, r1, r2, r3, ratio, tangentLine1, tangentLine2, tangentLines, tangentPoints, tpl, tpr; + largest = radii[0] >= radii[1] ? 0 : 1; + o1 = centers[largest]; + o2 = centers[1 - largest]; + r1 = radii[largest]; + r2 = radii[1 - largest]; + r3 = r1 + r2; + tangentPoints = this.findTangentPoints(o2, o1, r3); + ratio = r2 / r3; + tpl = tangentPoints[this.Side.LEFT]; + tpr = tangentPoints[this.Side.RIGHT]; + offset1 = o1.minus(tpl).times(ratio); + offset2 = o1.minus(tpr).times(ratio); + tangentLine1 = [tpl.plus(offset1), o2.plus(offset1)]; + tangentLine2 = [tpr.plus(offset2), o2.plus(offset2)]; + tangentLines = {}; + if (o1 === centers[0]) { + tangentLines[this.Side.RIGHT] = new LineSegment(tangentLine1[0], tangentLine1[1]); + tangentLines[this.Side.LEFT] = new LineSegment(tangentLine2[0], tangentLine2[1]); + } else { + tangentLines[this.Side.RIGHT] = new LineSegment(tangentLine1[1], tangentLine1[0]); + tangentLines[this.Side.LEFT] = new LineSegment(tangentLine2[1], tangentLine2[0]); + } + return tangentLines; + }; + + Util.findExternalTangentsOfGears = function(gear1, gear2) { + return this.findExternalTangents([gear1.location, gear2.location], [gear1.pitchRadius, gear2.pitchRadius]); + }; + + Util.findInternalTangentsOfGears = function(gear1, gear2) { + return this.findInternalTangents([gear1.location, gear2.location], [gear1.pitchRadius, gear2.pitchRadius]); + }; + + Util.findTangentLine = function(gear1, gear2, innerGearIds, direction) { + var gear1isInnerGear, side; + gear1isInnerGear = gear1.id in innerGearIds; + if (gear1isInnerGear === (direction === this.Direction.CLOCKWISE)) { + side = this.Side.LEFT; + } else { + side = this.Side.RIGHT; + } + if (gear1isInnerGear === (gear2.id in innerGearIds)) { + return this.findExternalTangentsOfGears(gear1, gear2)[side]; + } else { + return this.findInternalTangentsOfGears(gear1, gear2)[side]; + } + }; + + Util.findAllSimplePathsForNodes = function(turningObjects, goalNode, nodesVisited) { + var currentNode, neighbor, neighborId, paths, updatedNodesVisited, _ref; + paths = []; + currentNode = nodesVisited[nodesVisited.length - 1]; + _ref = currentNode.connections; + for (neighborId in _ref) { + if (!__hasProp.call(_ref, neighborId)) continue; + neighbor = turningObjects[neighborId]; + if (__indexOf.call(nodesVisited, neighbor) < 0) { + updatedNodesVisited = nodesVisited.slice(0); + updatedNodesVisited.push(neighbor); + if (neighbor === goalNode) { + paths.push(updatedNodesVisited); + } else { + paths = paths.concat(this.findAllSimplePathsForNodes(turningObjects, goalNode, updatedNodesVisited)); + } + } + } + return paths; + }; + + Util.findAllSimplePathsBetweenNeighbors = function(turningObjects) { + var i, id, j, nodes, paths, turningObject, _i, _j, _k, _ref, _ref1, _ref2, _ref3; + paths = []; + nodes = (function() { + var _results; + _results = []; + for (id in turningObjects) { + if (!__hasProp.call(turningObjects, id)) continue; + turningObject = turningObjects[id]; + _results.push(turningObject); + } + return _results; + })(); + if (nodes.length < 2) { + return []; + } + for (i = _i = 0, _ref = nodes.length - 1; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { + for (j = _j = _ref1 = i + 1, _ref2 = nodes.length; _ref1 <= _ref2 ? _j < _ref2 : _j > _ref2; j = _ref1 <= _ref2 ? ++_j : --_j) { + if (nodes[i].connections[nodes[j].id] != null) { + paths = paths.concat(this.findAllSimplePathsForNodes(turningObjects, nodes[j], [nodes[i]])); + } + } + } + for (i = _k = 0, _ref3 = paths.length; 0 <= _ref3 ? _k < _ref3 : _k > _ref3; i = 0 <= _ref3 ? ++_k : --_k) { + paths.push(paths[i].slice(0).reverse()); + } + return paths; + }; + + Util.sendGetRequest = function(url) { + var request; + request = new XMLHttpRequest(); + request.open("GET", url, false); + request.send(null); + return request.responseText; + }; + + Util.sendPostRequest = function(data, url, callback) { + var request; + request = new XMLHttpRequest(); + request.open("POST", url, true); + request.setRequestHeader("Content-type", "application/json; charset=UTF-8"); + request.onload = callback; + return request.send(data); + }; + + return Util; + + })(); + + window.gearsketch.Util = Util; + + (function() { + var lastTime, vendor, vendors, _i, _len; + lastTime = 0; + vendors = ["ms", "moz", "webkit", "o"]; + for (_i = 0, _len = vendors.length; _i < _len; _i++) { + vendor = vendors[_i]; + if (!(!window.requestAnimationFrame)) { + continue; + } + window.requestAnimationFrame = window[vendor + "RequestAnimationFrame"]; + window.cancelAnimationFrame = window[vendor + "CancelAnimationFrame"] || window[vendor + "CancelRequestAnimationFrame"]; + } + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = function(callback) { + var currTime, id, timeToCall; + currTime = new Date().getTime(); + timeToCall = Math.max(0, 16 - (currTime - lastTime)); + id = window.setTimeout((function() { + return callback(currTime + timeToCall); + }), timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + } + if (!window.cancelAnimationFrame) { + return window.cancelAnimationFrame = function(id) { + return clearTimeout(id); + }; + } + })(); + + (function() { + if ((Function.prototype.name == null) && (Object.defineProperty != null)) { + return Object.defineProperty(Function.prototype, "name", { + get: function() { + var funcNameRegex, results; + funcNameRegex = /function\s([^(]{1,})\(/; + results = funcNameRegex.exec(this.toString()); + if ((results != null) && results.length > 1) { + return results[1].trim(); + } else { + return ""; + } + }, + set: function(value) {} + }); + } + })(); + +}).call(this); + +/* +//@ sourceMappingURL=gearsketch_util.map +*/ diff --git a/activities/Gears.activity/lib/gearsketch/gearsketch_util.map b/activities/Gears.activity/lib/gearsketch/gearsketch_util.map new file mode 100644 index 000000000..de7c1a357 --- /dev/null +++ b/activities/Gears.activity/lib/gearsketch/gearsketch_util.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "gearsketch_util.js", + "sourceRoot": "../..", + "sources": [ + "lib/gearsketch/src/gearsketch_util.coffee" + ], + "names": [], + "mappings": ";AAGA;CAAA,CAAA,UAAA;CAAA,KAAA,8BAAA;KAAA;;uBAAA;;CAAA,CAEA,CAAoB,GAAd,IAAN;;CAFA,CAOM;CACS,CAAM,CAAN,CAAA,WAAE;CAAQ,EAAR,CAAA,EAAD;CAAS,EAAJ,CAAA,EAAD;CAAlB,IAAa;;CAAb,EAEM,CAAN,KAAO;CACK,CAAU,CAAL,CAAX,CAAA,QAAA;CAHN,IAEM;;CAFN,EAKO,EAAP,IAAQ;CACI,CAAU,CAAL,CAAX,CAAA,QAAA;CANN,IAKO;;CALP,EAQO,EAAP,IAAQ;CACI,CAAQ,CAAJ,CAAV,CAAA,QAAA;CATN,IAQO;;CARP,EAWU,KAAV,CAAW;CACJ,CAAwB,CAAnB,CAAN,SAAJ;CAZF,IAWU;;CAXV,EAcO,EAAP,IAAQ;CACL,EAAI,CAAJ,SAAD;CAfF,IAcO;;CAdP,EAiBO,EAAP,IAAO;CACK,CAAI,EAAV,CAAA,QAAA;CAlBN,IAiBO;;CAjBP,CAoBgB,CAAR,CAAR,CAAC,IAAQ;CACG,CAAqB,CAAjB,CAAV,CAAA,QAAA;CArBN,IAoBQ;;CApBR,EAuBa,CAAb,CAAC,IAAa,CAAd;CACY,CAAO,CAAJ,CAAT,CAAA,QAAA;CAxBN,IAuBa;;CAvBb;;CARF;;CAAA,CAkCA,CAA0B,EAA1B,CAAM,IAAW;;CAlCjB,CAyCM;CACS,CAAW,CAAX,CAAA,EAAA,EAAA,CAAA,CAAA,UAAE;CACb,EADa,CAAA,EAAD;CACZ,EADsB,CAAA,EAAD;CACrB,EAD+B,CAAA,EAAD,IAC9B;CAAA,EAD4C,CAAA,EAAD,EAC3C;CAAA,EADuD,CAAA,EAAD,GACtD;CAAA,EAAS,CAAR,CAAD,CAAA,IAAS,GAAA;CAAT,EACA,CAAC,EAAD,EAAO,KAAA;CAFT,IAAa;;CAAb,EAIW,MAAX;CACE,IAAA,KAAA;CAAA,CACoC,CADzB,CAAC,CAAZ,CAAA,EACW,CADA,CACT;CAGO,EAAD,CAAC,CAAT,QAAA;CATF,IAIW;;CAJX,EAWW,MAAX,GAAW;CACT,SAAA,MAAA;CAAA,EAAY,CAAgB,EAA5B,GAAA,GAAY;AAC0E,CADtF,EACQ,CAAC,CAAT,CAAA,GAA0B,CAAlB;CACP,CAA+B,EAA/B,CAAiB,CAAX,OAAP;CAdF,IAWW;;CAXX,EAgBe,EAAA,IAAC,IAAhB;CACG,CAA+B,EAA/B,CAAiB,CAAX,OAAP;CAjBF,IAgBe;;CAhBf,EAmBe,EAAA,IAAC,IAAhB;CACE,GAAG,CAAc,CAAjB,GAAG;CACI,CAA6B,CAAlC,CAAI,CAAsD,GAAjD,EAAT,KAAA;MADF,EAAA;CAGO,CAA6B,CAAlC,CAAI,CAA6C,GAAjD,EAAS,KAAT;QAJW;CAnBf,IAmBe;;CAnBf,EAyBiB,EAAA,IAAC,MAAlB;CACE,IAAA,KAAA;CAAA,CAAwC,CAAhC,CAAI,CAAZ,CAAA;CACA,GAAG,CAAA,CAAH,OAAG;CACI,EAAL,CAAI,CAAU,CAAL,EAAA,OAAT;MADF,EAAA;CAGO,CAA4B,CAAjC,CAAI,CAAU,GAAL,OAAT;QALa;CAzBjB,IAyBiB;;CAzBjB,EAgCuB,MAAC,EAAD,UAAvB;CAGE,SAAA,qDAAA;CAAA,CAAA,CAAK,CAAyB,CAAR,CAAtB,KAAgB;CAAhB,CACA,CAAK,CAAuB,CAAvB,CAAL,KAAgB;CADhB,CAEA,CAAK,GAAL;CAFA,CAGA,CAAK,GAAL;CAHA,CAIA,CAAK,CAAI,EAAT;CACA,CAAG,EAAA,CAAM,CAAT;CACE,IAAA,UAAO;QANT;CAAA,CAOM,CAAF,GAAJ;CAPA,CAQiC,CAAlB,CAAI,EAAnB,MAAA;CACA,EAAkB,CAAf,EAAH,MAAG;CAAH,cACE;MADF,EAAA;CAGE,CAAO,CAAP,CAAoB,IAApB,IAAqC;AAC7B,CADR,CACO,CAAP,CAAqB,IAArB,IAAgC;CADhC,CAEA,CAAS,CAAA,CAAA,CAAA,EAAT;CACA,CAAG,CAAkC,CAAlC,GAAA,CAAH,GAAc,IAAX;CACD,GAAA,aAAO;UAJT;CAAA,CAKO,CAAP,CAAoB,IAApB,IAAqC;AAC7B,CANR,CAMO,CAAP,CAAqB,IAArB,IAAgC;CANhC,CAOA,CAAS,CAAA,CAAA,CAAA,EAAT;CACA,CAAG,CAAkC,CAAlC,GAAA,CAAH,GAAc,IAAX;CACD,GAAA,aAAO;UATT;CAHF,cAaE;QAzBmB;CAhCvB,IAgCuB;;CAhCvB,EA4DmB,IAAA,EAAC,QAApB;CACE,SAAA,0CAAA;CAAA,GAAG,EAAH,CAAG,GAAH,EAAsB;CACpB,EAAsC,CAAnC,EAAO,CAAiB,CAA3B;CACE,CAAkD,CAAzC,CAAI,CAAJ,CAAT,CAA2B,GAA3B;CAAA,CACS,CAAA,CAAI,EAAb,IAAA;CACA,GAAG,EAAA,CAAkC,GAArC,GAAG;CACD,EAAiB,CAAC,EAAM,CAAiB,CAAxB,IAAjB,EAAA;CACA,CAAmB,CAAZ,CAAI,EAAQ,CAAkC,OAAlC,KAAZ;YALX;UAAA;CAMK,CACH,CADF,CAAI,CAAK,EAAwB,QAAjC;MAPF,EAAA;CAYE,GAAG,GAAA,CAAH,aAAG;CAAH,gBACE;MADF,IAAA;CAGE,EAAuB,CAA0B,EAA1B,CAAO,GAA9B,MAAuB,IAAvB;CACK,CACH,CADF,CAAI,CACF,EAAwB,QADjB,EAAT,GAAS;UAhBb;QADiB;CA5DnB,IA4DmB;;CA5DnB,EAmFO,EAAP,IAAO;CACU,CAAiB,EAA5B,CAAW,CAAO,EAAlB,CAAA,CAAA,GAAA;CApFN,IAmFO;;CAnFP,EAsFa,CAAb,KAAc,CAAb;CACgB,CAA8B,CAAV,CAA/B,CAAgB,CAAL,EAAX,CAAA,CAAA,GAAA;CAvFN,IAsFa;;CAtFb;;CA1CF;;CAAA,CAmIA,CAA+B,GAAzB,IAAW;;CAnIjB,CAwIM;CACS,CAAU,CAAV,CAAA,CAAA,gBAAE;CAAc,EAAd,CAAA,CAAc,CAAf;CAAe,EAAN,CAAA,EAAD;CAAtB,IAAa;;CAAb,EAEW,MAAX;CACG,EAAD,CAAC,CAAK,GAAN,KAAA;CAHF,IAEW;;CAFX,EAKW,MAAX,GAAW;CACT,OAAA,EAAA;CAAA,EAAW,CAAgB,CAAK,CAAhC,EAAA,IAAW;CACV,EAAe,CAAf,CAAK,GAAM,KAAZ;CAPF,IAKW;;CALX,EAUkB,MAAC,OAAnB;CACE,SAAA,MAAA;CAAA,EAAgB,CAAC,EAAjB,GAAgB,IAAhB;CACA,GAAG,CAAiB,CAApB,OAAG;CACA,GAAA,WAAD;MADF,EAAA;CAGE,CAAgH,CAA5G,CAAS,CAAK,GAAlB,KAAwF;CACxF,EAAO,CAAJ,IAAH;CACG,GAAA,aAAD;CACM,EAAI,CAAJ,EAFR,IAAA;CAGG,GAAA,aAAD;MAHF,IAAA;CAKG,EAAe,CAAf,CAAK,YAAN;UATJ;QAFgB;CAVlB,IAUkB;;CAVlB,EAuBiB,EAAA,IAAC,MAAlB;CACQ,GAAU,CAAX,GAAL,KAAA,GAAe;CAxBjB,IAuBiB;;CAvBjB,EA2BkB,MAAC,EAAD,KAAlB;CACE,SAAA,eAAA;CAAA,EAAI,CAAC,CAAL,CAAA;CAAA,EACI,CAAC,CAAD,CAAJ;CADA,EAEI,EAFJ,CAEA,KAAe;CAFf,EAGI,EAAA,CAAJ,KAAe;CAHf,EAIU,EAAA,CAAV,CAAA;CAJA,EAKI,EAAA,CAAJ,CALA;CAAA,EAMI,EAAA,CAAJ,CANA;CAOA,EAAG,CAAA,EAAH,CAAG;CACA,GAAD,CAAO,UAAP;MADF,EAAA;CAAA,cAGE;QAXc;CA3BlB,IA2BkB;;CA3BlB,EAwCmB,IAAA,EAAC,QAApB;CACE,GAAG,EAAH,CAAG,IAAH,CAAsB;CACpB,GAAG,GAAA,CAAH,QAAG;CAAH,gBACE;MADF,IAAA;CAGO,CAEH,CAFF,CAAI,CACF,EAAwB,QAAxB,EADF;UAJJ;MAAA,EAAA;CAUU,GAAR,GAAO,QAAP,EAAA;QAXe;CAxCnB,IAwCmB;;CAxCnB,EAqDO,EAAP,IAAO;CACW,CAAgB,CAAI,CAAhC,CAAkB,MAAlB,EAAA;CAtDN,IAqDO;;CArDP,EAwDa,CAAb,KAAc,CAAd,CAAC;CACiB,CAA6B,CAAT,CAAhC,CAAiB,KAAL,CAAZ,EAAA;CAzDN,IAwDa;;CAxDb;;CAzIF;;CAAA,CAoMA,CAAgC,GAA1B,IAAW,CAAjB;;CApMA,CAyMM;CAEJ;;CAAA,EAAQ,CAAR,CAAA,CAAc,IAAW;;CAAzB,EAGS,CAAT,EAAA;;CAHA,EAIc,CAAd,EAJA,KAIA;;CAJA,EAKqC,CAArC,8BAAA;;CALA,EAMoB,CAApB,EANA,WAMA;;CANA,EAOU,CAAV,GAAA,CAPA;;CAAA,EAWE,CADF,KAAA;CACE,CAAW,IAAX,GAAA,EAAA;CAAA,CACmB,IAAnB,WAAA,CADA;CAXF,KAAA;;CAAA,EAeE,CADF;CACE,CAAM,EAAN,EAAA;CAAA,CACO,GAAP,CAAA,CADA;CAfF,KAAA;;CAAA,EAmBQ,CAAR,CAAA,IAAS;CACP,SAAA,iCAAA;AAAa,CAAb,EAAa,CAAT,CAAyB,CAA7B,EAAY,KAAT;CACD,EAAA,YAAO;QADT;CAAA,CAGyB,CAAV,GAAf,CAAe,KAAf,CAAe;CACf,CAAG,CAAA,CAAA,EAAH,KAAkB,CAAf,GAAwB;CACzB,EAAU,EAAH,UAAA;QALT;CAOA,EAAG,CAAA,CAAH,CAAA,MAAkB;CAChB,CAAA,CAAO,CAAP,IAAA;AACA,CAAA,EAAA,UAAS,qFAAT;CACE,EAAU,CAAL,CAAK,KAAV;CADF,QADA;CAGA,GAAA,WAAO;QAXT;CAaA,EAAG,CAAA,EAAH,MAAkB;CAChB,CAAA,CAAO,CAAP,IAAA;AACA,CAAA,SAAA,GAAA;mDAAA;CACE,EAAK,CAAA,CAAO,KAAZ;CADF,QADA;CAGA,GAAA,WAAO;QAjBT;CAmBA,GAAU,CAAA,OAAA,wCAAA;CAvCZ,IAmBQ;;CAnBR,EA0Ca,CAAb,KAAa,CAAb;CACyC,CAAiB,CAAA,IAAxD,EAAyD,IAAzD,yBAAsC;CACpC,GAAA,QAAA;CAAA,CAAI,CAAA,CAAI,EAAJ,EAAJ;CAAA,EACO,EAAK,GAAZ;CACC,CAAD,MAAA,OAAA;CAHF,MAAwD;CA3C1D,IA0Ca;;CA1Cb,CAgDU,CAAV,CAAA,KAAO;CACJ,EAAI,UAAL;CAjDF,IAgDM;;CAhDN,EAmDO,CAAP,KAAQ;CACN,EAAO,CAAJ,EAAH;AAAe,CAAD,cAAA;MAAd,EAAA;CAAA,cAAsB;QADjB;CAnDP,IAmDO;;CAnDP,CAsDe,CAAN,CAAT,EAAA,EAAS,CAAC;CACR,SAAA,OAAA;AAAA,CAAA,UAAA,oCAAA;gCAAA;CACE,EAAI,CAAJ,GAAI,CAAJ;CADF,MAAA;CADO,YAGP;CAzDF,IAsDS;;CAtDT,EA2DkB,CAAlB,IAAkB,CAAC,MAAnB;CACG,CAAD,EAAC,EAAD,EAAA,KAAA;CA5DF,IA2DkB;;CA3DlB,EA8DU,CAAV,GAAA,EAAU;CACR,OAAA,EAAA;CAAA,KADS,iDACT;CAAC,GAAA,IAAD,KAAA,EAAA;CA/DF,IA8DU;;CA9DV,CAkEyB,CAAP,CAAlB,IAAkB,CAAC,MAAnB;CACE,SAAA,gDAAA;CAAA,EAAe,GAAf,EAAA,IAAA;CAAA,EACI,GAAJ;CADA,EAEiB,CAAI,EAArB,QAAA;CACA,EAAqB,SAAf,CAAA;CACJ,EAAI,KAAJ,MAAA;CAAA,CACmC,CAArB,CAAA,GAAd,CAAA,GAAc;CADd,EAEgB,IAAO,CAAvB,CAAgB,IAAhB;CACA,GAAG,IAAH,IAAG,CAAH;CACE,MAAc,EAAP,GAAA,KAAA;MADT,IAAA;CAGE,EAAI,OAAJ;CAAA,GACgB,MAAhB,EAAA,CADA;UAPJ;CAHA,MAGA;CASA,GAAA,SAAO;CA/ET,IAkEkB;;CAlElB,CAiFmB,CAAP,CAAZ,KAAA,GAAY;CACV,SAAA,kCAAA;;GADgC,KAAf;QACjB;CAAA,EAAS,GAAT;CAAA,EACiB,CAAI,EAArB,QAAA;CADA,EAEa,GAAb,IAAA,EAA+B,EAAlB;AACb,CAAA,EAAA,QAAS,uFAAT;CACE,EAAI,KAAJ,MAAA;CAAA,GACU,EAAV,EAAA;CAFF,MAHA;CADU,YAOV;CAxFF,IAiFY;;CAjFZ,CA2F+B,CAAR,CAAvB,CAAuB,EAAA,EAAC,WAAxB;CACE,SAAA,4DAAA;CAAA,EAAmB,EAAnB,CAAA,UAAA;CAAA,EACI,EAAK,CAAT;CADA,EAEI,EAAK,CAAT;CAFA,EAGmB,GAAnB,CAA0B,SAA1B;CAHA,EAII,GAAJ,UAAI;AACJ,CAAA,EAAA,QAAS,+GAAT;CACE,EAAA,IAAc,CAAd;CAAA,EACA,IAAc,CAAd;CADA,EAEA,IAAc,CAAd;CAFA,EAGA,IAAc,CAAd;CACA,EAAK,CAAF,CAAgB,GAAnB;AACsB,CAApB,EAAmB,OAAnB,MAAA;UALF;CAAA,EAMI,KAAJ;CAPF,MALA;CADqB,YAcrB;CAzGF,IA2FuB;;CA3FvB,CA2G6B,CAAP,CAAtB,GAAsB,EAAC,UAAvB;CACE,SAAA,qBAAA;SAAA,GAAA;CAAA,EAAmB,EAAA,CAAnB,GAAoB,OAApB;CACO,CAAiC,EAAlC,CAAoB,GAAX,GAAM,IAAnB;CADF,MAAmB;CAAnB,KAEA,IAAA;;AAAc,CAAA;GAAA,WAA8C,cAA9C;CAAA,CAAiB,CAAO,CAAP,YAAjB;CAAA;;CAFd;CAGW,EAAM,EAAjB,IAAkB,CAAR,GAAV;CAAyB,CAAwB,GAAxB,EAAD,QAAA,KAAA;CAAxB,MAAiB;CA/GnB,IA2GsB;;CA3GtB,CAiHmC,CAAV,CAAzB,CAAyB,EAAA,EAAC,aAA1B;CACE,SAAA,QAAA;AAAA,CAAA;WAAA,CAAA;;0BAAA;CAAqC,CAA0B,EAA3B,GAAA,YAAA;CAApC;UAAA;CAAA;uBADuB;CAjHzB,IAiHyB;;CAjHzB,CAoHsC,CAAP,CAA/B,GAA+B,EAAC,mBAAhC;CACU,EAAiC,CAAb,GAArB,CAAP,GAA0C,EAA1C,EAAA;CArHF,IAoH+B;;CApH/B,CAuHuC,CAAR,CAA/B,CAA+B,EAAA,EAAC,mBAAhC;CACE,SAAA,QAAA;AAAA,CAAA;WAAA,CAAA;;0BAAA;CAAqC,CAAmC,EAApC,GAAA,qBAAA;CAApC;UAAA;CAAA;uBAD6B;CAvH/B,IAuH+B;;CAvH/B,CA0H4B,CAAR,CAApB,CAAoB,IAAC,GAAD,KAApB;CAEE,SAAA,gDAAA;;GAF+C,KAAf;QAEhC;CAAA,EAAW,GAAX,EAAA,CAAA;CAAA,EACiB,CAAI,EAArB,QAAA;CADA,EAEa,GAAb,IAAA,EAA+B,EAAlB;AACb,CAAA,EAAA,QAAS,uFAAT;CACE,EAAI,KAAJ,MAAA;CAAA,CACmC,CAArB,CAAA,GAAd,CAAA,GAAc;CADd,CAEgB,CAAZ,CAAI,CAAQ,EAAO,CAAvB,OAAgB;CAFhB,CAG8B,CAAnB,CAAI,IAAf;CAJF,MAHA;CAFkB,YAUlB;CApIF,IA0HoB;;CA1HpB,CAwIsC,CAAR,CAA9B,CAA8B,IAAC,EAAD,GAAA,aAA9B;CACE,SAAA,mCAAA;SAAA,GAAA;;GADkE,KAAjB;QACjD;CAAA,CAAyD,CAArC,CAAC,CAAD,CAApB,KAAoB,MAApB,WAAoB;CAApB,CACuB,CAAA,CAAvB,EAAA,GAAwB,QAAP;CACZ,CAAD,CAAsC,EAAxC,MAA8B,IAA9B;CADF,MAAuB;AAEvB,CAAA,UAAA,6CAAA;kDAAA;AACE,CAAA,CAAO,EAAP,IAAA,MAAA,EAAuB;CACrB,eAAA,CAAO;UAFX;CAAA,MAHA;CAD4B,YAO5B;CA/IF,IAwI8B;;CAxI9B,EAkJgB,CAAhB,GAAgB,EAAC,IAAjB;CACE,SAAA,0BAAA;CAAA,EAAiB,GAAjB,CAAwB,OAAxB;CAAA,EACa,GAAb,IAAA;AACA,CAAA,EAAA,QAAS,uGAAT;CACE,EAAI,KAAJ,MAAA;CAAA,EAC6B,CAAf,GAAQ,CAAtB,EAAA;CADA,EAE6B,CAAf,GAAQ,CAAtB,EAAA;CAHF,MAFA;CAMA,EAAgB,CAAb,EAAH,IAAG;CACA,GAAA,KAAS,MAAV;MADF,EAAA;CAGG,GAAA,KAAS,MAAV;QAVY;CAlJhB,IAkJgB;;CAlJhB,CAgKwB,CAAJ,CAApB,KAAqB,QAArB;CACE,SAAA,sBAAA;CAAA,CAAA,CAAgB,GAAhB,OAAA;CAAA,EACI,GAAJ,EAAI;CACJ,EAAG,CAAA,EAAH,CAAA;CACE,EAA6B,CAAd,CAAD,GAAd,KAAc;CAAd,EAC4B,CAAb,CAAa,GAA5B,KAAc;MAFhB,EAAA;CAIE,EAAI,CAAI,IAAR;CAAA,CAC8B,CAAtB,CAAI,CAAZ,GAAA;CADA,EAEO,CAAP,IAAA;CAFA,CAG8D,CAAjC,CAAd,CAAD,GAAd,KAAc;CAHd,CAI6D,CAAjC,CAAb,CAAyB,GAAxC,KAAc;QAVhB;CADkB,YAYlB;CA5KF,IAgKoB;;CAhKpB,CA8K4B,CAAJ,CAAxB,KAAyB,YAAzB;CACG,CAAqB,EAArB,IAAD,GAAA,EAAA,IAAA;CA/KF,IA8KwB;;CA9KxB,CAkLiC,CAAV,CAAvB,CAAuB,EAAA,EAAC,WAAxB;CACE,SAAA,oHAAA;CAAA,EAAa,CAAY,CAAN,CAAnB,CAAA;CAAA,CACA,CAAK,GAAL,CAAa;CADb,CAEA,CAAK,GAAL,CAAa;CAFb,CAGA,CAAK,EAAM,CAAX,CAAW;CAHX,CAIA,CAAK,EAAM,CAAX,CAAW;CAJX,CAKA,CAAK,GAAL;CACA,CAAG,EAAA,CAAM,CAAT;CACE,CAAA,CAAgB,KAAhB,KAAA;CAAA,CAAA,CAC4B,CAAb,IAAf,KAAc;CADd,CAAA,CAE6B,CAAd,CAAD,GAAd,KAAc;CAFd,CAGqB,CAAb,CAAI,CAAZ,GAAA;CAHA,CAIU,CAAA,CAA8B,CAAzB,EAAf,CAAA;CAJA,CAKU,CAAA,CAA8B,CAAzB,EAAf,CAAA;MANF,EAAA;CAQE,CAAgB,CAAA,CAAC,IAAjB,KAAA,IAAgB;CAAhB,CACQ,CAAA,EAAR,GAAA;CADA,EAEA,CAAqB,IAArB,KAAoB;CAFpB,EAGA,CAAqB,CAAD,GAApB,KAAoB;CAHpB,CAIU,CAAA,EAAA,EAAV,CAAA;CAJA,CAKU,CAAA,EAAA,EAAV,CAAA;QAnBF;CAAA,CAoByD,CAA1C,CAAgB,EAA/B,CAAgB,KAAhB,CAA8B;CApB9B,CAqB0D,CAA3C,CAAgB,CAAD,CAA9B,CAAgB,KAAhB,CAA8B;CArB9B,CAAA,CAsBe,GAAf,MAAA;CACA,CAAG,EAAA,CAAM,CAAT,CAAiB;CACf,CAA6D,CAA7B,CAAlB,CAAD,GAAb,GAAgC,CAAnB;CAAb,CAC4D,CAA7B,CAAjB,IAAd,GAA+B,CAAlB;MAFf,EAAA;CAIE,CAA6D,CAA7B,CAAlB,CAAD,GAAb,GAAgC,CAAnB;CAAb,CAC4D,CAA7B,CAAjB,IAAd,GAA+B,CAAlB;QA5Bf;CADqB,YA8BrB;CAhNF,IAkLuB;;CAlLvB,CAmNiC,CAAV,CAAvB,CAAuB,EAAA,EAAC,WAAxB;CACE,SAAA,6GAAA;CAAA,EAAa,CAAY,CAAN,CAAnB,CAAA;CAAA,CACA,CAAK,GAAL,CAAa;CADb,CAEA,CAAK,GAAL,CAAa;CAFb,CAGA,CAAK,EAAM,CAAX,CAAW;CAHX,CAIA,CAAK,EAAM,CAAX,CAAW;CAJX,CAKA,CAAK,GAAL;CALA,CAMgB,CAAA,CAAC,EAAjB,OAAA,IAAgB;CANhB,CAOQ,CAAA,EAAR,CAAA;CAPA,EAQA,CAAqB,EAArB,OAAoB;CARpB,EASA,CAAqB,CAAD,CAApB,OAAoB;CATpB,CAUY,CAAF,EAAA,CAAV,CAAA;CAVA,CAWY,CAAF,EAAA,CAAV,CAAA;CAXA,CAYmC,CAApB,CAAC,EAAhB,CAAgB,KAAhB;CAZA,CAamC,CAApB,CAAC,EAAhB,CAAgB,KAAhB;CAbA,CAAA,CAce,GAAf,MAAA;CACA,CAAG,EAAA,CAAM,CAAT,CAAiB;CACf,CAA6D,CAA7B,CAAlB,CAAD,GAAb,GAAgC,CAAnB;CAAb,CAC4D,CAA7B,CAAjB,IAAd,GAA+B,CAAlB;MAFf,EAAA;CAIE,CAA6D,CAA7B,CAAlB,CAAD,GAAb,GAAgC,CAAnB;CAAb,CAC4D,CAA7B,CAAjB,IAAd,GAA+B,CAAlB;QApBf;CADqB,YAsBrB;CAzOF,IAmNuB;;CAnNvB,CA2OsC,CAAR,CAA9B,CAA8B,IAAC,kBAA/B;CACG,CAAsC,EAAtC,CAA2B,GAAN,GAAkC,EAAxD,OAAA;CA5OF,IA2O8B;;CA3O9B,CA8OsC,CAAR,CAA9B,CAA8B,IAAC,kBAA/B;CACG,CAAsC,EAAtC,CAA2B,GAAN,GAAkC,EAAxD,OAAA;CA/OF,IA8O8B;;CA9O9B,CAiP0B,CAAR,CAAlB,CAAkB,IAAC,GAAD,GAAlB;CACE,SAAA,YAAA;CAAA,CAAoB,CAAA,CAAY,CAAP,CAAzB,MAAA,IAAA;CACA,GAAG,CAAoB,CAAvB,GAAwB,OAArB;CACD,EAAO,CAAP,IAAA;MADF,EAAA;CAGE,EAAO,CAAP,CAAA,GAAA;QAJF;CAKA,CAAwB,EAArB,CAAoB,CAAvB,MAAuB,IAApB;CACA,CAAmC,EAAnC,CAAD,UAAA,YAAA;MADF,EAAA;CAGG,CAAmC,EAAnC,CAAD,UAAA,YAAA;QATc;CAjPlB,IAiPkB;;CAjPlB,CA4P8C,CAAjB,CAA7B,IAA6B,CAAC,GAAD,EAAA,YAA7B;CACE,SAAA,yDAAA;CAAA,CAAA,CAAQ,EAAR,CAAA;CAAA,EACc,GAAd,KAAA,CAA2B;CAC3B;CAAA,UAAA,OAAA;yDAAA;CACE,EAAW,KAAX,EAA0B,IAAA;CAC1B,CAAO,EAAA,CAAP,GAAA,IAAO,GAAY;CACjB,EAAsB,EAAA,KAAtB,EAAkC,OAAlC;CAAA,GACA,IAAA,EAAA,SAAmB;CACnB,GAAG,CAAY,GAAZ,EAAH;CACE,GAAA,CAAK,OAAL,OAAA;MADF,MAAA;CAGE,CAAiE,CAAzD,CAAc,CAAtB,CAAQ,EAAa,IAArB,EAAqB,KAAA,OAAA;YANzB;UAFF;CAAA,MAFA;CAWA,IAAA,QAAO;CAxQT,IA4P6B;;CA5P7B,EA0QqC,CAArC,KAAsC,KAAD,oBAArC;CACE,SAAA,kEAAA;CAAA,CAAA,CAAQ,EAAR,CAAA;CAAA,IACA,CAAA;;AAAS,CAAA;cAAA,MAAA;;8CAAA;CAAA;CAAA;;CADT;CAEA,EAAkB,CAAf,CAAK,CAAR;CACE,CAAA,aAAO;QAHT;AAIA,CAAA,EAAA,QAAS,wFAAT;AACE,CAAA,EAAA,UAAS,2GAAT;CACE,GAAG,MAAH,+BAAA;CACE,CAAiE,CAAzD,CAAc,CAAtB,CAAQ,MAAR,EAAqB,YAAA;YAFzB;CAAA,QADF;CAAA,MAJA;AAQA,CAAA,EAAA,QAAS,yFAAT;CACE,GAAA,CAAK,EAAM,CAAX;CADF,MARA;CADmC,YAWnC;CArRF,IA0QqC;;CA1QrC,EAuRiB,CAAjB,KAAkB,KAAlB;CACE,MAAA,GAAA;CAAA,EAAc,CAAA,EAAd,CAAA,OAAc;CAAd,CACoB,CAApB,CAAA,CAAA,CAAA,CAAO;CADP,GAEA,EAAA,CAAO;CACC,MAAD,MAAP;CA3RF,IAuRiB;;CAvRjB,CA6RyB,CAAP,CAAlB,IAAkB,CAAC,MAAnB;CACE,MAAA,GAAA;CAAA,EAAc,CAAA,EAAd,CAAA,OAAc;CAAd,CACqB,CAArB,CAAA,EAAA,CAAO;CADP,CAEyC,IAAzC,CAAO,OAAP,EAAA,iBAAA;CAFA,EAGiB,GAAjB,CAAO,CAHP;CAIQ,GAAR,GAAO,MAAP;CAlSF,IA6RkB;;CA7RlB;;CA3MF;;CAAA,CA+eA,CAAyB,CAAzB,EAAM,IAAW;;CA/ejB,CAmfG,CAAA,MAAA;CACD,OAAA,2BAAA;CAAA,EAAW,CAAX,IAAA;CAAA,CACiB,CAAP,CAAV,CAAU,EAAV,CAAU;AACV,CAAA,QAAA,qCAAA;4BAAA;AAA4B,CAAD,KAAO;;QAChC;CAAA,EAA+B,GAA/B,eAAA,EAAsC;CAAtC,EAC8B,CAC9B,EADA,cAAA,EAAqC,OAC9B;CAHT,IAFA;AAOI,CAAJ,GAAA,EAAU,eAAV;CACE,EAA+B,GAA/B,EAA+B,CAAC,YAAhC;CACE,WAAA,YAAA;CAAA,EAAe,CAAA,GAAA,CAAf;CAAA,CACyB,CAAZ,CAAI,IAAjB,EAAA;CADA,CAEA,CAAK,GAAM,EAAX,CAAwB,CAAnB;CACM,EAAW,KAApB,EAAA,OAAA;CADqB,CAEpB,OAFqB,CAAnB;CAFL,EAKW,KAAX,EALA;CAD6B,cAO7B;CAPF,MAA+B;MARjC;AAiBI,CAAJ,GAAA,EAAU,cAAV;CACS,CAAuB,CAAA,GAAxB,GAAyB,IAA/B,OAAA;CACe,CAAb,UAAA,GAAA;CAFJ,MACgC;MAnB/B;CAAA,EAAA;;CAnfH,CA2gBG,CAAA,MAAA;CACD,GAAA,2BAAA,EAAG;CACM,CAAmC,IAApC,EAAwB,CAA9B,IAAA,CAAA;CACE,CAAK,CAAL,KAAA,CAAK;CACH,aAAA,QAAA;CAAA,EAAgB,OAAhB,GAAA,WAAA;CAAA,EACW,CAAD,GAAV,CAA+B,EAA/B,GAAyB;CACzB,EAAkC,CAA9B,EAAa,CAAO,GAAxB,OAAI;CAA8C,GAAR,GAAQ,YAAR;MAA1C,MAAA;CAAA,kBAAiE;YAH9D;CAAL,QAAK;CAAL,CAIK,CAAL,EAAK,GAAL,CAAM;CANV,OACE;MAFD;CAAA,EAAA;CA3gBH" +} \ No newline at end of file diff --git a/activities/Gears.activity/lib/gearsketch/src/gearsketch_main.coffee b/activities/Gears.activity/lib/gearsketch/src/gearsketch_main.coffee new file mode 100644 index 000000000..a0931e986 --- /dev/null +++ b/activities/Gears.activity/lib/gearsketch/src/gearsketch_main.coffee @@ -0,0 +1,863 @@ +# By Frank Leenaars +# University of Twente - Department of Instructional Technology +# Licensed under the MIT license +"use strict" + +# TODO: +# - support IE9? +# - use "for element, i in ..." where appropriate +# - chained comparisons: 1 < x < 100 +# - improve gear, chain & momentum icons +# - disallow chains crossing gears' axes? (if gear on higher level) +# - allow gears to overlap other gears' axes when the larger gear is on a higher level? +# - figure out why drawing with RAF is slower than drawing without RAF on iPad + +# imports +Point = window.gearsketch.Point +ArcSegment = window.gearsketch.ArcSegment +LineSegment = window.gearsketch.LineSegment +Util = window.gearsketch.Util +Gear = window.gearsketch.model.Gear +Chain = window.gearsketch.model.Chain +Board = window.gearsketch.model.Board + +# -- constants -- +FPS = 60 +MIN_GEAR_TEETH = 8 +MIN_MOMENTUM = 0.2 + +# --------------------------- +# -------- GearSketch ------- +# --------------------------- +class GearSketch + # -- imported constants -- + MODULE = Util.MODULE + AXIS_RADIUS = Util.AXIS_RADIUS + + BUTTON_INFO = [ + ["gearButton", "GearIcon.png"] + ["chainButton", "ChainIcon.png"] + ["momentumButton", "MomentumIcon.png"] + ["playButton", "PlayIcon.png"] + ["clearButton", "ClearIcon.png"] + ["cloudButton", "CloudIcon.png"] + ["helpButton", "HelpIcon.png"] + ] + + MovementAction = + PEN_DOWN: "penDown" + PEN_UP: "penUp" + PEN_TAP: "penTap" + + MovementType = + STRAIGHT: "straight" + CIRCLE: "circle" + LEFT_HALF_CIRCLE: "leftHalfCircle" + RIGHT_HALF_CIRCLE: "rightHalfCircle" + + buttons: {} + loadedButtons: 0 + areButtonsLoaded: false + selectedButton: BUTTON_INFO[0][0] + + gearImages: {} + + isPenDown: false + stroke: [] + offset: new Point() + + message: "" + messageColor: "black" + + # usage demo + pointerLocation: new Point() + currentDemoMovement: 0 + movementCompletion: 0 + restTimer: 0 + + # Passing false to showButtons will hide them, unless the demo is + # playing. This comes handy when adding controls outside the canvas. + constructor: (showButtons = true) -> + @loadButtons() + @showButtons = showButtons + @loadDemoPointer() + @loadBoard() + @canvas = document.getElementById("gearsketch_canvas") + @canvasOffsetX = @canvas.getBoundingClientRect().left + @canvasOffsetY = @canvas.getBoundingClientRect().top + @isDemoPlaying = false + @updateCanvasSize() + @addCanvasListeners() + @lastUpdateTime = new Date().getTime() + #@updateAndDraw() + @updateAndDrawNoRAF() + + buttonLoaded: -> + @loadedButtons++ + if @loadedButtons is BUTTON_INFO.length + @areButtonsLoaded = true + + loadButtons: -> + x = y = 20 + for [name, file] in BUTTON_INFO + button = new Image() + button.name = name + button.onload = => @buttonLoaded() + button.src = "img/" + file + button.location = new Point(x, y) + button.padding = 3 + @buttons[name] = button + x += 80 + + loadDemoPointer: -> + image = new Image() + image.onload = => @pointerImage = image + image.src = "img/hand.png" + + loadBoard: -> + @board = + if parent.location.hash.length > 1 + try + hash = parent.location.hash.substr(1) + boardJSON = Util.sendGetRequest("boards/#{hash}.txt") + Board.fromObject(JSON.parse(boardJSON)) + catch error + @displayMessage("Error: could not load board", "red", 2000) + new Board() + else + new Board() + @addGearImage(gear) for id, gear of @board.getGears() + + + displayMessage: (message, color = "black", time = 0) -> + @message = message + @messageColor = color + if time > 0 + setTimeout((=> @clearMessage()), time) + + clearMessage: -> + @message = "" + + selectButton: (buttonName) -> + @selectedButton = buttonName + + shouldShowButtons: -> + return @showButtons or @isDemoPlaying + + # Input callback methods + addCanvasListeners: -> + canvasEventHandler = Hammer(@canvas, {drag_min_distance: 1}) + canvasEventHandler.on("touch", ((e) => @forwardPenDownEvent.call(this, e))) + canvasEventHandler.on("drag", ((e) => @forwardPenMoveEvent.call(this, e))) + canvasEventHandler.on("release", ((e) => @forwardPenUpEvent.call(this, e))) + + forwardPenDownEvent: (event) -> + event.gesture.preventDefault() + if @isDemoPlaying + @stopDemo() + else + x = event.gesture.center.pageX - @canvasOffsetX + y = event.gesture.center.pageY - @canvasOffsetY + @handlePenDown(x, y) + + forwardPenMoveEvent: (event) -> + event.gesture.preventDefault() + unless @isDemoPlaying + x = event.gesture.center.pageX - @canvasOffsetX + y = event.gesture.center.pageY - @canvasOffsetY + @handlePenMove(x, y) + + forwardPenUpEvent: (event) -> + unless @isDemoPlaying + @handlePenUp() + + handlePenDown: (x, y) -> + point = new Point(x, y) + if @isPenDown + # pen released outside of canvas + @handlePenUp() + else + button = @getButtonAt(x, y) + if button + if button.name is "clearButton" + # remove hash from url and clear board + parent.location.hash = "" + @board.clear() + else if button.name is "cloudButton" + @uploadBoard() + else if button.name is "helpButton" + @playDemo() + else + @selectButton(button.name) + else if @selectedButton is "gearButton" + @selectedGear = @board.getTopLevelGearAt(point) + if @selectedGear? + @offset = point.minus(@selectedGear.location) + else if !@board.getGearAt(point)? # don't create stroke if lower level gear selected + @stroke.push(point) + @isPenDown = true + else if @selectedButton is "chainButton" + @stroke.push(point) + @isPenDown = true + else if @selectedButton is "momentumButton" + @selectedGear = @board.getGearAt(point) + if @selectedGear + @selectedGear.momentum = 0 + @selectedGearMomentum = @calculateMomentumFromCoords(@selectedGear, x, y) + @isPenDown = true + + handlePenMove: (x, y) -> + point = new Point(x, y) + if @isPenDown + if @selectedButton is "gearButton" + if @selectedGear + goalLocation = point.minus(@offset) + canPlaceGear = @board.placeGear(@selectedGear, goalLocation) + if canPlaceGear + @goalLocationGear = null + else + @goalLocationGear = + new Gear(goalLocation, @selectedGear.rotation, @selectedGear.numberOfTeeth, @selectedGear.id) + else if @stroke.length > 0 + @stroke.push(point) + else if @selectedButton is "chainButton" + @stroke.push(point) + else if @selectedButton is "momentumButton" + if @selectedGear + @selectedGearMomentum = @calculateMomentumFromCoords(@selectedGear, x, y) + + handlePenUp: -> + if @isPenDown + if @selectedButton is "gearButton" + unless (@selectedGear? or @stroke.length is 0) + @processGearStroke() + else if @selectedButton is "chainButton" + @processChainStroke() + else if @selectedButton is "momentumButton" + if @selectedGear + if Math.abs(@selectedGearMomentum) > MIN_MOMENTUM + @selectedGear.momentum = @selectedGearMomentum + else + @selectedGear.momentum = 0 + @selectedGearMomentum = 0 + @selectedGear = null + @goalLocationGear = null + @isPenDown = false + + isButtonAt: (x, y, button) -> + x > button.location.x and + x < button.location.x + button.width + 2 * button.padding and + y > button.location.y and + y < button.location.y + button.height + 2 * button.padding + + getButtonAt: (x, y) -> + if not @shouldShowButtons() + return null + + for own buttonName, button of @buttons + if @isButtonAt(x, y, button) + return button + null + + normalizeStroke: (stroke) -> + MIN_POINT_DISTANCE = 10 + normalizedStroke = [] + if stroke.length > 0 + [p1, strokeTail...] = stroke + normalizedStroke.push(p1) + for p2 in strokeTail + if p1.distance(p2) > MIN_POINT_DISTANCE + normalizedStroke.push(p2) + p1 = p2 + normalizedStroke + + createGearFromStroke: (stroke) -> + numberOfPoints = stroke.length + if numberOfPoints > 0 + sumX = 0 + sumY = 0 + minX = Number.MAX_VALUE + maxX = Number.MIN_VALUE + minY = Number.MAX_VALUE + maxY = Number.MIN_VALUE + for p in stroke + sumX += p.x + sumY += p.y + minX = Math.min(minX, p.x) + maxX = Math.max(maxX, p.x) + minY = Math.min(minY, p.y) + maxY = Math.max(maxY, p.y) + width = maxX - minX + height = maxY - minY + t = Math.floor(0.5 * (width + height) / MODULE) + + # find area, based on http://stackoverflow.com/questions/451426 + # /how-do-i-calculate-the-surface-area-of-a-2d-polygon + doubleArea = 0 + for i in [0...numberOfPoints] + j = (i + 1) % numberOfPoints + doubleArea += stroke[i].x * stroke[j].y + doubleArea -= stroke[i].y * stroke[j].x + + # create a new gear if the stroke is sufficiently circle-like and large enough + area = Math.abs(doubleArea) / 2 + radius = 0.25 * ((maxX - minX) + (maxY - minY)) + idealTrueAreaRatio = (Math.PI * Math.pow(radius, 2)) / area + if idealTrueAreaRatio > 0.80 and idealTrueAreaRatio < 1.20 and t > MIN_GEAR_TEETH + x = sumX / numberOfPoints + y = sumY / numberOfPoints + return new Gear(new Point(x, y), 0, t) + null + + removeStrokedGears: (stroke) -> + for own id, gear of @board.getTopLevelGears() + if Util.pointPathDistance(gear.location, stroke, false) < gear.innerRadius + @board.removeGear(gear) + + processGearStroke: -> + normalizedStroke = @normalizeStroke(@stroke) + gear = @createGearFromStroke(normalizedStroke) + if gear? + isGearAdded = @board.addGear(gear) + if isGearAdded and !(gear.numberOfTeeth of @gearImages) + @addGearImage(gear) + else + @removeStrokedGears(normalizedStroke) + @stroke = [] + + gearImageLoaded: (numberOfTeeth, image) -> + @gearImages[numberOfTeeth] = image + + addGearImage: (gear) -> + # draw gear on temporary canvas + gearCanvas = document.createElement("canvas") + size = 2 * (gear.outerRadius + MODULE) # slightly larger than gear diameter + gearCanvas.height = size + gearCanvas.width = size + ctx = gearCanvas.getContext("2d") + gearCopy = new Gear(new Point(0.5 * size, 0.5 * size), 0, gear.numberOfTeeth, gear.id) + @drawGear(ctx, gearCopy) + + # convert canvas to png + image = new Image() + image.onload = => @gearImageLoaded(gear.numberOfTeeth, image) + image.src = gearCanvas.toDataURL("image/png") + + removeStrokedChains: (stroke) -> + for own id, chain of @board.getChains() + if chain.intersectsPath(stroke) + @board.removeChain(chain) + + processChainStroke: -> + normalizedStroke = @normalizeStroke(@stroke) + @stroke = [] + gearsInChain = Util.findGearsInsidePolygon(normalizedStroke, @board.getGears()) + if normalizedStroke.length >= 3 and gearsInChain.length > 0 + chain = new Chain(normalizedStroke) + @board.addChain(chain) + else if normalizedStroke.length >= 2 + @removeStrokedChains(normalizedStroke) + + calculateMomentumFromCoords: (gear, x, y) -> + angle = Math.atan2(y - gear.location.y, x - gear.location.x) + angleFromTop = angle + 0.5 * Math.PI + if angleFromTop < Math.PI + angleFromTop + else + angleFromTop - 2 * Math.PI + + # -- updating -- + updateAndDraw: => + setTimeout((=> + requestAnimationFrame(@updateAndDraw) + @update() + @draw() + ), 1000 / FPS) + + updateAndDrawNoRAF: => + @update() + @draw() + setTimeout((=> @updateAndDrawNoRAF()), 1000 / FPS) + + update: => + updateTime = new Date().getTime() + delta = updateTime - @lastUpdateTime + if @selectedButton is "playButton" + @board.rotateAllTurningObjects(delta) + if @isDemoPlaying + @updateDemo(delta) + @lastUpdateTime = updateTime + + # -- rendering -- + drawGear: (ctx, gear, color = "black") -> + {x, y} = gear.location + rotation = gear.rotation + numberOfTeeth = gear.numberOfTeeth + + gearImage = @gearImages[gear.numberOfTeeth] + if color is "black" and gearImage? + # use predrawn image instead of drawing it again + gearImage = @gearImages[gear.numberOfTeeth] + ctx.save() + ctx.translate(x, y) + ctx.rotate(rotation) + ctx.drawImage(gearImage, -0.5 * gearImage.width, -0.5 * gearImage.height) + ctx.restore() + return + + # draw teeth + angleStep = 2 * Math.PI / numberOfTeeth + innerPoints = [] + outerPoints = [] + for i in [0...numberOfTeeth] + for r in [0...4] + if r is 0 or r is 3 + innerPoints.push(Point.polar((i + 0.25 * r) * angleStep, gear.innerRadius)) + else + outerPoints.push(Point.polar((i + 0.25 * r) * angleStep, gear.outerRadius)) + ctx.save() + ctx.fillStyle = "rgba(255, 255, 255, 0.8)" + ctx.strokeStyle = color + ctx.lineWidth = 2 + ctx.translate(x, y) + ctx.rotate(rotation) + ctx.beginPath() + ctx.moveTo(gear.innerRadius, 0) + for i in [0...numberOfTeeth * 2] + if i % 2 is 0 + ctx.lineTo(innerPoints[i].x, innerPoints[i].y) + ctx.lineTo(outerPoints[i].x, outerPoints[i].y) + else + ctx.lineTo(outerPoints[i].x, outerPoints[i].y) + ctx.lineTo(innerPoints[i].x, innerPoints[i].y) + ctx.closePath() + ctx.fill() + ctx.stroke() + + # draw axis + ctx.beginPath() + ctx.moveTo(AXIS_RADIUS, 0) + ctx.arc(0, 0, AXIS_RADIUS, 0, 2 * Math.PI, true) + ctx.closePath() + ctx.stroke() + + # draw rotation indicator line + ctx.beginPath() + ctx.moveTo(AXIS_RADIUS, 0) + ctx.lineTo(gear.innerRadius, 0) + ctx.closePath() + ctx.stroke() + ctx.restore() + + drawButton: (ctx, button) -> + {x, y} = button.location + padding = button.padding + ctx.save() + ctx.translate(x, y) + ctx.beginPath() + + # draw a round rectangle + radius = 10 + width = button.width + 2 * padding + height = button.height + 2 * padding + ctx.moveTo(radius, 0) + ctx.lineTo(width - radius, 0) + ctx.quadraticCurveTo(width, 0, width, radius) + ctx.lineTo(width, height - radius) + ctx.quadraticCurveTo(width, height, width - radius, height) + ctx.lineTo(radius, height) + ctx.quadraticCurveTo(0, height, 0, height - radius); + ctx.lineTo(0, radius) + ctx.quadraticCurveTo(0, 0, radius, 0); + + if button.name is @selectedButton + ctx.fillStyle = "rgba(50, 150, 255, 0.8)" + else + ctx.fillStyle = "rgba(255, 255, 255, 0.8)" + ctx.fill() + ctx.lineWidth = 1 + ctx.strokeStyle = "black" + ctx.stroke() + ctx.drawImage(button, padding, padding) + ctx.restore() + + drawMomentum: (ctx, gear, momentum, color = "red") -> + pitchRadius = gear.pitchRadius + top = new Point(gear.location.x, gear.location.y - pitchRadius) + ctx.save() + ctx.lineWidth = 5 + ctx.lineCap = "round" + ctx.strokeStyle = color + ctx.translate(top.x, top.y) + + # draw arc + ctx.beginPath() + ctx.arc(0, pitchRadius, pitchRadius, -0.5 * Math.PI, momentum - 0.5 * Math.PI, momentum < 0) + ctx.stroke() + + # draw arrow head + length = 15 + angle = 0.2 * Math.PI + headX = -Math.cos(momentum + 0.5 * Math.PI) * pitchRadius + headY = pitchRadius - Math.sin(momentum + 0.5 * Math.PI) * pitchRadius + head = new Point(headX, headY) + sign = Util.sign(momentum) + p1 = head.minus(Point.polar(momentum + angle, sign * length)) + ctx.beginPath() + ctx.moveTo(headX, headY) + ctx.lineTo(p1.x, p1.y) + ctx.stroke() + p2 = head.minus(Point.polar(momentum - angle, sign * length)) + ctx.beginPath() + ctx.moveTo(headX, headY) + ctx.lineTo(p2.x, p2.y) + ctx.stroke() + ctx.restore() + + drawChain: (ctx, chain) -> + ctx.save() + ctx.lineWidth = Chain.WIDTH + ctx.lineCap = "round" + ctx.strokeStyle = "rgb(0, 0, 255)" + ctx.moveTo(chain.segments[0].start.x, chain.segments[0].start.y) + for segment in chain.segments + if segment instanceof ArcSegment + isCounterClockwise = (segment.direction is Util.Direction.COUNTER_CLOCKWISE) + ctx.beginPath() + ctx.arc(segment.center.x, segment.center.y, segment.radius, + segment.startAngle, segment.endAngle, isCounterClockwise) + ctx.stroke() + else + ctx.beginPath() + ctx.moveTo(segment.start.x, segment.start.y) + ctx.lineTo(segment.end.x, segment.end.y) + ctx.stroke() + ctx.fillStyle = "white" + for point in chain.findPointsOnChain(25) + ctx.beginPath() + ctx.arc(point.x, point.y, 3, 0, 2 * Math.PI, true) + ctx.fill() + ctx.restore() + + drawDemoPointer: (ctx, location) -> + ctx.drawImage(@pointerImage, location.x - 0.5 * @pointerImage.width, location.y) + + draw: -> + if @canvas.getContext? + @updateCanvasSize() + ctx = @canvas.getContext("2d") + ctx.clearRect(0, 0, @canvas.width, @canvas.height) + + # draw gears + sortedGears = @board.getGearsSortedByGroupAndLevel() + arrowsToDraw = [] + for i in [0...sortedGears.length] + gear = sortedGears[i] + momentum = gear.momentum + if gear is @selectedGear and @goalLocationGear + @drawGear(ctx, gear, "grey") + if momentum + arrowsToDraw.push([gear, momentum, "grey"]) + else + @drawGear(ctx, gear) + if momentum + arrowsToDraw.push([gear, momentum, "red"]) + + # draw chains and arrows when all the gears in current group on current level are drawn + shouldDrawChainsAndArrows = + (i is sortedGears.length - 1) or + (@board.getLevelScore(gear) isnt @board.getLevelScore(sortedGears[i + 1])) + if shouldDrawChainsAndArrows + for chain in @board.getChainsInGroupOnLevel(gear.group, gear.level) + @drawChain(ctx, chain) + for arrow in arrowsToDraw + @drawMomentum(ctx, arrow[0], arrow[1], arrow[2]) + arrowsToDraw = [] + + # draw goalLocationGear + if @goalLocationGear + @drawGear(ctx, @goalLocationGear, "red") + + # draw selected gear momentum + if @selectedGear? and @selectedGearMomentum + @drawMomentum(ctx, @selectedGear, @selectedGearMomentum) + + # draw stroke + if @stroke.length > 0 + ctx.save() + if @selectedButton is "gearButton" + ctx.strokeStyle = "black" + ctx.lineWidth = 2 + else # chain stroke + ctx.strokeStyle = "blue" + ctx.lineWidth = 4 + ctx.beginPath() + ctx.moveTo(@stroke[0].x, @stroke[0].y) + for i in [1...@stroke.length] + ctx.lineTo(@stroke[i].x, @stroke[i].y) + ctx.stroke() + ctx.restore() + + # draw buttons + if @areButtonsLoaded and @shouldShowButtons() + for own buttonName of @buttons + @drawButton(ctx, @buttons[buttonName]) + + # draw message + if @message.length > 0 + ctx.save() + ctx.fillStyle = @messageColor + ctx.font = "bold 20px Arial" + ctx.fillText(@message, 20, 120) + ctx.restore() + + # draw demo text and pointer + if @isDemoPlaying and @pointerImage + @drawDemoPointer(ctx, @pointerLocation) + + updateCanvasSize: () -> + @canvas.width = @canvas.parentElement.getBoundingClientRect().width + @canvas.height = @canvas.parentElement.getBoundingClientRect().height + @buttons["clearButton"].location.x = Math.max(@canvas.width - 260, @buttons["playButton"].location.x + 80) + @buttons["cloudButton"].location.x = @buttons["clearButton"].location.x + 80 + @buttons["helpButton"].location.x = @buttons["cloudButton"].location.x + 80 + + # -- usage demo -- + loadDemoMovements: -> + @demoMovements = [ + from: @getButtonCenter("helpButton") + to: @getButtonCenter("gearButton") + atEnd: MovementAction.PEN_TAP + type: MovementType.STRAIGHT + duration: 2000 + , + to: new Point(300, 200) + type: MovementType.STRAIGHT + duration: 1500 + , + atStart: MovementAction.PEN_DOWN + atEnd: MovementAction.PEN_UP + type: MovementType.CIRCLE + radius: 100 + duration: 1500 + , + to: new Point(500, 200) + type: MovementType.STRAIGHT + duration: 1000 + , + atStart: MovementAction.PEN_DOWN + atEnd: MovementAction.PEN_UP + type: MovementType.CIRCLE + radius: 40 + duration: 1000 + , + to: new Point(500, 240) + type: MovementType.STRAIGHT + duration: 500 + , + to: new Point(300, 300) + atStart: MovementAction.PEN_DOWN + atEnd: MovementAction.PEN_UP + type: MovementType.STRAIGHT + duration: 1500 + , + to: new Point(100, 180) + type: MovementType.STRAIGHT + duration: 1000 + , + atStart: MovementAction.PEN_DOWN + atEnd: MovementAction.PEN_UP + type: MovementType.CIRCLE + radius: 90 + duration: 1000 + , + to: new Point(100, 260) + type: MovementType.STRAIGHT + duration: 500 + , + to: new Point(180, 260) + atStart: MovementAction.PEN_DOWN + atEnd: MovementAction.PEN_UP + type: MovementType.STRAIGHT + duration: 1500 + , + to: new Point(550, 220) + type: MovementType.STRAIGHT + duration: 1500 + , + atStart: MovementAction.PEN_DOWN + atEnd: MovementAction.PEN_UP + type: MovementType.CIRCLE + radius: 80 + duration: 1000 + , + to: @getButtonCenter("chainButton") + atEnd: MovementAction.PEN_TAP + type: MovementType.STRAIGHT + duration: 1500 + , + to: new Point(280, 150) + type: MovementType.STRAIGHT + duration: 1500 + , + atStart: MovementAction.PEN_DOWN + type: MovementType.LEFT_HALF_CIRCLE + radius: 140 + duration: 1500 + pause: 0 + , + to: new Point(600, 400) + type: MovementType.STRAIGHT + duration: 1000 + pause: 0 + , + type: MovementType.RIGHT_HALF_CIRCLE + radius: 110 + duration: 1000 + pause: 0 + , + to: new Point(280, 150) + atEnd: MovementAction.PEN_UP + type: MovementType.STRAIGHT + duration: 1000 + , + to: @getButtonCenter("momentumButton") + atEnd: MovementAction.PEN_TAP + type: MovementType.STRAIGHT + duration: 1500 + , + to: new Point(185, 180) + type: MovementType.STRAIGHT + duration: 1500 + , + to: new Point(150, 190) + atStart: MovementAction.PEN_DOWN + atEnd: MovementAction.PEN_UP + type: MovementType.STRAIGHT + duration: 1000 + , + to: @getButtonCenter("playButton") + atEnd: MovementAction.PEN_TAP + type: MovementType.STRAIGHT + duration: 1500 + , + to: @getButtonCenter("chainButton") + atEnd: MovementAction.PEN_TAP + type: MovementType.STRAIGHT + duration: 3000 + , + to: new Point(425, 250) + type: MovementType.STRAIGHT + duration: 1000 + , + to: new Point(525, 150) + atStart: MovementAction.PEN_DOWN + atEnd: MovementAction.PEN_UP + type: MovementType.STRAIGHT + duration: 1000 + , + to: @getButtonCenter("gearButton") + atEnd: MovementAction.PEN_TAP + type: MovementType.STRAIGHT + duration: 1500 + , + to: new Point(20, 250) + type: MovementType.STRAIGHT + duration: 1000 + , + to: new Point(650, 300) + atStart: MovementAction.PEN_DOWN + atEnd: MovementAction.PEN_UP + type: MovementType.STRAIGHT + duration: 1500 + , + to: new Point(425, 200) + type: MovementType.STRAIGHT + duration: 1000 + , + to: new Point(200, 400) + atStart: MovementAction.PEN_DOWN + atEnd: MovementAction.PEN_UP + type: MovementType.STRAIGHT + duration: 1500 + ] + + getButtonCenter: (buttonName) -> + button = @buttons[buttonName] + buttonCorner = new Point(button.location.x, button.location.y) + buttonCorner.plus(new Point(0.5 * button.width + button.padding, 0.5 * button.height + button.padding)) + + updateDemo: (delta) -> + # check if resting or if last movement completed + if @restTimer > 0 + @restTimer = Math.max(@restTimer - delta, 0) + return + else if @currentDemoMovement is @demoMovements.length + @stopDemo() + return + + # advance movement + movement = @demoMovements[@currentDemoMovement] + if @movementCompletion is 0 + movement.from ?= @pointerLocation + movement.pause ?= 500 + @pointerLocation = movement.from.clone() + if movement.atStart is MovementAction.PEN_DOWN + @handlePenDown(@pointerLocation.x, @pointerLocation.y) + if @movementCompletion < 1 + @movementCompletion = Math.min(1, @movementCompletion + delta / movement.duration) + @updatePointerLocation(movement, @movementCompletion) + @handlePenMove(@pointerLocation.x, @pointerLocation.y) + if @movementCompletion is 1 + if movement.atEnd is MovementAction.PEN_TAP + @handlePenDown(@pointerLocation.x, @pointerLocation.y) + @handlePenUp() + else if movement.atEnd is MovementAction.PEN_UP + @handlePenUp() + @restTimer = movement.pause + @movementCompletion = 0 + @currentDemoMovement++ + + updatePointerLocation: (movement, movementCompletion) -> + if movement.type is MovementType.STRAIGHT + delta = movement.to.minus(movement.from) + @pointerLocation = movement.from.plus(delta.times(movementCompletion)) + else if movement.type is MovementType.CIRCLE + center = new Point(movement.from.x , movement.from.y + movement.radius) + @pointerLocation = center.plus(Point.polar(Math.PI - (movementCompletion - 0.25) * 2 * Math.PI, movement.radius)) + else if movement.type is MovementType.LEFT_HALF_CIRCLE + center = new Point(movement.from.x , movement.from.y + movement.radius) + angle = 1.5 * Math.PI - movementCompletion * Math.PI + @pointerLocation = center.plus(Point.polar(angle, movement.radius)) + else if movement.type is MovementType.RIGHT_HALF_CIRCLE + center = new Point(movement.from.x , movement.from.y - movement.radius) + angle = 0.5 * Math.PI - movementCompletion * Math.PI + @pointerLocation = center.plus(Point.polar(angle, movement.radius)) + + playDemo: -> + @loadDemoMovements() # load these on each play in case canvas size changed + @boardBackup = @board.clone() + @board.clear() + @currentDemoMovement = 0 + @movementCompletion = 0 + @isDemoPlaying = true + @displayMessage("click anywhere to stop the demo") + + stopDemo: -> + @isDemoPlaying = false + @restTimer = 0 + @stroke = [] + @selectedGear = null + @selectedIcon = "gearIcon" + @board.restoreAfterDemo(@boardBackup) + @clearMessage() + + boardUploaded: (event) -> + parent.location.hash = event.target.responseText.trim() + @displayMessage("Board saved. Share it by copying the text in your address bar.", "black", 4000) + + uploadBoard: -> + boardJSON = JSON.stringify(@board) + Util.sendPostRequest(boardJSON, "upload_board.php", ((event) => @boardUploaded(event))) + +window.gearsketch.GearSketch = GearSketch \ No newline at end of file diff --git a/activities/Gears.activity/lib/gearsketch/src/gearsketch_model.coffee b/activities/Gears.activity/lib/gearsketch/src/gearsketch_model.coffee new file mode 100644 index 000000000..ec658361e --- /dev/null +++ b/activities/Gears.activity/lib/gearsketch/src/gearsketch_model.coffee @@ -0,0 +1,921 @@ +# By Frank Leenaars +# University of Twente - Department of Instructional Technology +# Licensed under the MIT license +"use strict" + +# imports +Point = window.gearsketch.Point +ArcSegment = window.gearsketch.ArcSegment +LineSegment = window.gearsketch.LineSegment +Util = window.gearsketch.Util + +window.gearsketch.model = {} + +# --------------------------- +# ---------- Gear ----------- +# --------------------------- +class Gear + constructor: (@location, @rotation, @numberOfTeeth, @id, + @momentum = 0, @group = 0, @level = 0, @connections = {}) -> + @id ?= Util.createUUID() + @pitchRadius = Util.MODULE * (0.5 * @numberOfTeeth) + @innerRadius = Util.MODULE * (0.5 * @numberOfTeeth - 1.25) + @outerRadius = Util.MODULE * (0.5 * @numberOfTeeth + 1) + + getCircumference: -> + 2 * Math.PI * @pitchRadius + + distanceToPoint: (point) -> + Math.max(0, @location.distance(point) - @pitchRadius) + + edgeDistance: (gear) -> + axisDistance = @location.distance(gear.location) + Math.abs(axisDistance - @pitchRadius - gear.pitchRadius) + + restore: (gear) -> + @location = gear.location + @rotation = gear.rotation + @momentum = gear.momentum + @group = gear.group + @level = gear.level + @connections = gear.connections + + clone: -> + new Gear(@location.clone(), @rotation, @numberOfTeeth, @id, + @momentum, @group, @level, Util.clone(@connections)) + + @fromObject: (obj) -> + new Gear(Point.fromObject(obj.location), obj.rotation, obj.numberOfTeeth, obj.id, + obj.momentum, obj.group, obj.level, obj.connections) + +window.gearsketch.model.Gear = Gear + +# --------------------------- +# ---------- Chain ---------- +# --------------------------- +class Chain + @WIDTH: 8 + + points: [] + segments: [] + + constructor: (stroke, @id, @group = 0, @level = 0, @connections = {}) -> + @id ?= Util.createUUID() + @points = Util.clone(stroke) + @rotation = 0 + + getLength: -> + @segments.reduce(((total, segment) -> total + segment.getLength()), 0) + + getCircumference: -> + @getLength() + + # rotation of the chain is always expressed clockwise + getStartingPoint: -> + if @direction is Util.Direction.CLOCKWISE + @rotation / (2 * Math.PI) * @getLength() + else + -@rotation / (2 * Math.PI) * @getLength() + + findPointOnChain: (distance) -> + length = @getLength() + distanceToGo = Util.mod(distance + @getStartingPoint(), length) + for segment in @segments + segmentLength = segment.getLength() + if distanceToGo < segmentLength + return segment.findPoint(distanceToGo) + else + distanceToGo -= segmentLength + null + + findPointsOnChain: (numberOfPoints) -> + delta = @getLength() / numberOfPoints + @findPointOnChain(p * delta) for p in [0...numberOfPoints] + + distanceToPoint: (point) -> + Math.min.apply(null, (segment.distanceToPoint(point) for segment in @segments)) + + distanceToSegment: (s) -> + Math.min.apply(null, (segment.distanceToSegment(s) for segment in @segments)) + + distanceToChain: (chain) -> + Math.min.apply(null, (chain.distanceToSegment(segment) for segment in @segments)) + + intersectsPath: (path) -> + for i in [0...path.length - 1] + j = i + 1 + if @distanceToSegment(new LineSegment(path[i], path[j])) is 0 + return true + false + + crossesNonSupportingGears: (board) -> + for own id, gear of board.getGears() + if !(id in @supportingGearIds) and !(id of @ignoredGearIds) + if @distanceToPoint(gear.location) < gear.pitchRadius + Util.EPSILON + return true + return false + + # get the incoming or outgoing tangent point on the gear in @supportingGearIds with the given gearIndex + findPointOnSupportingGear: (gearIndex, incoming) -> + if incoming + @points[Util.mod(2 * gearIndex - 1, @points.length)] + else + @points[2 * gearIndex] + + removeGear: (gear, board) -> + while (index = @supportingGearIds.indexOf(gear.id)) isnt -1 + gears = board.getGearsWithIds(@supportingGearIds) # gear to remove will be undefined + numberOfGears = gears.length + beforeIndex = Util.mod((index - 1), numberOfGears) + beforeGear = gears[beforeIndex] + afterIndex = Util.mod((index + 1), numberOfGears) + acknowledgedGears = board.getAcknowledgedGears(@ignoredGearIds) + path = [ + @findPointOnSupportingGear(index, true) + @findPointOnSupportingGear(index, false) + @findPointOnSupportingGear(afterIndex, true) + ] + replacementGears = @findSupportingGearsOnPath(acknowledgedGears, beforeGear, path, 0, false) + gears.splice.apply(gears, [index, 1].concat(replacementGears)) + @removeRepeatedGears(gears) + @supportingGearIds = (g.id for g in gears) + return @update(board) + + findChainTangentSide: (gear) -> + if (@direction is Util.Direction.CLOCKWISE) is (gear.id of @innerGearIds) + Util.Side.LEFT + else + Util.Side.RIGHT + + findReverseChainTangentSide: (gear) -> + if @findChainTangentSide(gear) is Util.Side.LEFT + Util.Side.RIGHT + else + Util.Side.LEFT + + findFirstSupportingGearOnPath: (path, gears) -> + stepSize = 10 + pathLength = Util.getLength(path) + supportingGear = null + a = path[0] + d = 0 + while d < pathLength and !supportingGear? + d += stepSize + b = Util.findPointOnPath(path, d) + supportingGear = Util.findNearestIntersectingGear(gears, new LineSegment(a, b)) + [supportingGear, d] + + findSupportingGearsOnPath: (gears, firstSupportingGear, path, startDistance = 0, isClosed = true) -> + stepSize = 10 + pathLength = Util.getLength(path, isClosed) + supportingGear = firstSupportingGear + supportingGears = [] + a = firstSupportingGear.location + d = startDistance + while d < pathLength + d += stepSize + b = Util.findPointOnPath(path, d) + tangentSide = @findReverseChainTangentSide(supportingGear) + tangentPoint = Util.findGearTangentPoints(b, supportingGear)[tangentSide] + a = tangentPoint if tangentPoint? # in case point b is in gear + lineSegment = new LineSegment(a, b) + nextSupportingGear = Util.findNearestIntersectingGear(gears, lineSegment, Util.makeSet(supportingGear.id)) + if nextSupportingGear? + supportingGear = nextSupportingGear + supportingGears.push(supportingGear) + supportingGears + + removeRepeatedGears: (gearsList) -> + numberOfNoops = 0 + i = 0 + while numberOfNoops < (numberOfGears = gearsList.length) + j = (i + 1) % numberOfGears + g1 = gearsList[i] + g2 = gearsList[j] + if g1 is g2 + gearsList.splice(j, 1) + numberOfNoops = 0 + else + numberOfNoops++ + i = (i + 1) % numberOfGears + gearsList + + containsSuccessiveOverlappingGears: (gearsList) -> + numberOfGears = gearsList.length + for i in [0...numberOfGears] + j = (i + 1) % numberOfGears + g1 = gearsList[i] + g2 = gearsList[j] + if g1.location.distance(g2.location) < (g1.outerRadius + g2.outerRadius) + return true + false + + findSupportingGearIds: (gears) -> + [firstSupportingGear, startDistance] = @findFirstSupportingGearOnPath(@points, gears) + supportingGears = [firstSupportingGear] + nextSupportingGears = @findSupportingGearsOnPath(gears, firstSupportingGear, @points, startDistance) + supportingGears = supportingGears.concat(nextSupportingGears) + # if first point is not on first supporting gear, trace path connecting them + tangentSide = @findChainTangentSide(firstSupportingGear) + tangentPoint = Util.findGearTangentPoints(@points[0], firstSupportingGear)[tangentSide] + if tangentPoint? + finalSegment = [@points[0], tangentPoint] + lastSupportingGear = supportingGears[supportingGears.length-1] + nextSupportingGears = @findSupportingGearsOnPath(gears, lastSupportingGear, finalSegment, 0, false) + supportingGears = supportingGears.concat(nextSupportingGears) + gear.id for gear in @removeRepeatedGears(supportingGears) + + findIgnoredGearIds: (board) -> + # find minimal distance of each level of gears in each group to the chain + gears = board.getGears() + minDistances = {} + for own id, gear of gears + group = gear.group + level = gear.level + d = Util.pointPathDistance(gear.location, @points) - gear.pitchRadius + if !minDistances[group]?[level]? or d < minDistances[group][level] + minDistances[group] ?= {} + minDistances[group][level] = d + + # find the one level of each group of gears that is not ignored + # this is currently the closest, nonoverlapping level, a heuristic that can be further improved + acknowledgedLevels = {} + for own group, levels of minDistances + for own level, distance of levels + currentLevel = acknowledgedLevels[group] + if !currentLevel? + acknowledgedLevels[group] = parseInt(level, 10) + else if distance > 0 + currentDistance = minDistances[group][currentLevel] + if currentDistance < 0 or distance < currentDistance + acknowledgedLevels[group] = parseInt(level, 10) + + # ignore all gears not on an acknowledged level + ignoredGearIds = {} + for own id, gear of gears + if acknowledgedLevels[gear.group] isnt gear.level + ignoredGearIds[id] = true + ignoredGearIds + + findIgnoredGearIdsInTightenedChain: (board) -> + # find groups and levels of the gears in this chain + # there can be more than one group and level due to recently moved gear + groups = {} + for gearId in @supportingGearIds + gear = board.getGearWithId(gearId) + group = gear.group + level = gear.level + if !groups[group]? + groups[group] = {} + groups[group][level] = true + + # ignore all gears that belong to a supporting gear's group but are on a different level + updatedIgnoredGearIds = {} + for own id, gear of board.getGears() + group = gear.group + level = gear.level + if groups[group]? and !groups[group][level]? + updatedIgnoredGearIds[id] = true + @ignoredGearIds = updatedIgnoredGearIds + + # convert a list of segments to a polygon + # used for finding gear centers inside a tightened chain + toPolygon: (segments = @segments)-> + polygon = [] + for segment in segments + if segment instanceof LineSegment + polygon.push(segment.start) + else # ArcSegment + polygon.push(segment.findPoint(0)) + polygon.push(segment.findPoint(0.5 * segment.getLength())) + polygon + + update: (board, gears = board.getGearsWithIds(@supportingGearIds)) -> + if gears.length < 2 + return false + + if @containsSuccessiveOverlappingGears(gears) + return false + + updatedIgnoredGearIds = @findIgnoredGearIdsInTightenedChain(board) + acknowledgedGears = board.getAcknowledgedGears(updatedIgnoredGearIds) + + # first: update gear sequence + i = 0 + while i < (numberOfGears = gears.length) + j = (i + 1) % numberOfGears + k = (i + 2) % numberOfGears + g1 = gears[i] + g2 = gears[j] + g3 = gears[k] + + lineSegment1 = Util.findTangentLine(g1, g2, @innerGearIds, @direction) + lineSegment2 = Util.findTangentLine(g2, g3, @innerGearIds, @direction) + intersection = lineSegment1.findIntersection(lineSegment2) + if intersection? # g2 cannot support chain + tangentSideG1 = @findReverseChainTangentSide(g1) + tangentPointG1 = Util.findGearTangentPoints(intersection, g1)[tangentSideG1] + tangentSideG3 = @findChainTangentSide(g3) + tangentPointG3 = Util.findGearTangentPoints(intersection, g3)[tangentSideG3] + path = [tangentPointG1, intersection, tangentPointG3] + replacementGears = @findSupportingGearsOnPath(acknowledgedGears, g1, path, 0, false) + if g2 in replacementGears # rare bug due to floating point errors + return false + gears.splice.apply(gears, [j, 1].concat(replacementGears)) + @removeRepeatedGears(gears) + return @update(board, gears) # start over + gear = Util.findNearestIntersectingGear(acknowledgedGears, lineSegment1, Util.makeSet(g1.id, g2.id)) + if gear? + gears.splice(j, 0, gear) + if @containsSuccessiveOverlappingGears(gears) + return false + i++ + + # second: update points & segments + updatedPoints = [] + for i in [0...numberOfGears] + j = (i + 1) % numberOfGears + g1 = gears[i] + g2 = gears[j] + tangentLine = Util.findTangentLine(g1, g2, @innerGearIds, @direction) + updatedPoints.push(tangentLine.start, tangentLine.end) + updatedSegments = [] + for i in [0...numberOfGears] + p0 = updatedPoints[2 * i] + p1 = updatedPoints[2 * i + 1] + p2 = updatedPoints[2 * ((i + 1) % numberOfGears)] + gear = gears[(i + 1) % numberOfGears] + lineSegment = new LineSegment(p0, p1) + arcStart = Math.atan2(p1.y - gear.location.y, p1.x - gear.location.x) + arcEnd = Math.atan2(p2.y - gear.location.y, p2.x - gear.location.x) + direction = if (@direction is Util.Direction.CLOCKWISE) is (gear.id of @innerGearIds) + Util.Direction.CLOCKWISE + else + Util.Direction.COUNTER_CLOCKWISE + arcSegment = new ArcSegment(gear.location, gear.pitchRadius, arcStart, arcEnd, direction) + updatedSegments.push(lineSegment, arcSegment) + + # third: check if chain doesn't touch itself + numberOfSegments = updatedSegments.length + for i in [0...numberOfSegments - 2] + for j in [(i + 2)...numberOfSegments] + if i isnt 0 or j isnt numberOfSegments - 1 # don't compare first and last segments + s1 = updatedSegments[i] + s2 = updatedSegments[j] + if s1.distanceToSegment(s2) < Chain.WIDTH + # make sure segments are not connected by a very small ArcSegment + if (i + 2) is j + middleSegment = updatedSegments[i + 1] + if (middleSegment instanceof ArcSegment) and (middleSegment.getLength() < 2 * Chain.WIDTH) + continue + if ((j + 2) % numberOfSegments) is i # note: not else if, both can be true in chain with 4 segments + middleSegment = updatedSegments[(j + 1) % numberOfSegments] + if (middleSegment instanceof ArcSegment) and (middleSegment.getLength() < 2 * Chain.WIDTH) + continue + return false + + # fourth: check if no supporting gears have left innergears + updatedIgnoredGearIds = @findIgnoredGearIdsInTightenedChain(board) + updatedAcknowledgedGears = board.getAcknowledgedGears(updatedIgnoredGearIds) + chainPolygon = @toPolygon(updatedSegments) + updatedInnerGearIds = {} + for own id, gear of updatedAcknowledgedGears when Util.isPointInsidePolygon(gear.location, chainPolygon) + updatedInnerGearIds[id] = true + for own gearId of @innerGearIds + if !(gearId of updatedInnerGearIds) and (gearId in @supportingGearIds) + return false + + # fifth: update chain properties + @points = updatedPoints + @segments = updatedSegments + @ignoredGearIds = updatedIgnoredGearIds + @innerGearIds = updatedInnerGearIds + @supportingGearIds = (gear.id for gear in gears) + true + + tighten: (board) -> + @ignoredGearIds = @findIgnoredGearIds(board) + acknowledgedGears = board.getAcknowledgedGears(@ignoredGearIds) + @innerGearIds = Util.makeSetFromList(gear.id for gear in Util.findGearsInsidePolygon(@points, acknowledgedGears)) + if Object.keys(@innerGearIds).length < 2 + return false + @direction = Util.findDirection(@points) + @supportingGearIds = @findSupportingGearIds(acknowledgedGears) + @update(board) + + clone: -> + copy = new Chain(@points, @id, @group, @level, Util.clone(@connections)) + copy.segments = Util.clone(@segments) + copy.ignoredGearIds = Util.clone(@ignoredGearIds) + copy.innerGearIds = Util.clone(@innerGearIds) + copy.direction = @direction + copy.supportingGearIds = Util.clone(@supportingGearIds) + copy + + @fromObject: (obj) -> + createSegment = (obj) -> if obj.center? then ArcSegment.fromObject(obj) else LineSegment.fromObject(obj) + points = (new Point(p.x, p.y) for p in obj.points) + chain = new Chain(points, obj.id, obj.group, obj.level, obj.connections) + chain.segments = (createSegment(segment) for segment in obj.segments) + chain.ignoredGearIds = obj.ignoredGearIds + chain.innerGearIds = obj.innerGearIds + chain.direction = obj.direction + chain.supportingGearIds = obj.supportingGearIds + chain + +window.gearsketch.model.Chain = Chain + +# --------------------------- +# ---------- Board ---------- +# --------------------------- +class Board + # -- imported constants -- + MODULE = Util.MODULE + AXIS_RADIUS = Util.AXIS_RADIUS + MIN_STACKED_GEARS_TEETH_DIFFERENCE = Util.MIN_STACKED_GEARS_TEETH_DIFFERENCE + SNAPPING_DISTANCE = Util.SNAPPING_DISTANCE + EPSILON = Util.EPSILON + + ConnectionType = # connections are between two gears or a gear and a chain + ANY: "any" + MESHING: "meshing" + AXIS: "axis" + CHAIN_INSIDE: "chain_inside" + CHAIN_OUTSIDE: "chain_outside" + + constructor: (@gears = {}, @chains = {}) -> + + restore: (board) -> + for own id, gear of @gears + gear.restore(board.gears[id]) + @chains = board.chains + + restoreAfterDemo: (board) -> + @gears = board.gears + @chains = board.chains + + clear: -> + @gears = {} + @chains = {} + + getNextGroup: -> + nextGroup = 0 + for own id, gear of @gears + nextGroup = Math.max(nextGroup, gear.group + 1) + nextGroup + + getGears: -> + @gears + + getGearList: -> + gear for own id, gear of @gears + + getAcknowledgedGears: (ignoredGearIds) -> + acknowledgedGears = {} + acknowledgedGears[id] = gear for own id, gear of @gears when !(id of ignoredGearIds) + acknowledgedGears + + getLevelScore: (gear) -> + 1000 * gear.group + gear.level + + getGearsSortedByGroupAndLevel: (gears = @getGearList()) -> + gears.sort((g1, g2) => @getLevelScore(g1) - @getLevelScore(g2)) + + removeConnection: (turningObject1, turningObject2) -> + delete turningObject1.connections[turningObject2.id] + delete turningObject2.connections[turningObject1.id] + + removeAllConnections: (turningObject) -> + for own neighborId of turningObject.connections + neighbor = @getTurningObjects()[neighborId] + @removeConnection(turningObject, neighbor) + @updateGroupsAndLevels() + + findNearestAxis: (gear) -> + nearestAxis = null + shortestDistance = Number.MAX_VALUE + for own id, candidate of @gears + if candidate isnt gear + distance = gear.location.distance(candidate.location) + if !nearestAxis or + distance < (shortestDistance - EPSILON) or + (distance < (shortestDistance + EPSILON) and + candidate.numberOfTeeth < nearestAxis.numberOfTeeth) + nearestAxis = candidate + shortestDistance = distance + nearestAxis + + updateGroupsAndLevelsFrom: (turningObjectId, group, level, updatedGroups, updatedLevels) -> + turningObject = @getTurningObjects()[turningObjectId] + updatedGroups[turningObjectId] = group + updatedLevels[turningObjectId] = level + connections = turningObject.connections + sameLevelConnectionTypes = [ConnectionType.MESHING, ConnectionType.CHAIN_INSIDE, ConnectionType.CHAIN_OUTSIDE] + for own neighborId, connectionType of connections + if !(neighborId of updatedGroups) + if connectionType in sameLevelConnectionTypes + @updateGroupsAndLevelsFrom(neighborId, group, level, updatedGroups, updatedLevels) + else + gear = @gears[turningObjectId] + neighbor = @gears[neighborId] + if gear.numberOfTeeth > neighbor.numberOfTeeth + @updateGroupsAndLevelsFrom(neighborId, group, level + 1, updatedGroups, updatedLevels) + else + @updateGroupsAndLevelsFrom(neighborId, group, level - 1, updatedGroups, updatedLevels) + + updateGroupsAndLevels: -> + updatedGroups = {} + updatedLevels = {} + group = 0 + for own id of @gears # chains are always connected to gears, so will be updated as well + if !(id of updatedGroups) + @updateGroupsAndLevelsFrom(id, group, 0, updatedGroups, updatedLevels) + group++ + for own id, turningObject of @getTurningObjects() + turningObject.group = updatedGroups[id] + turningObject.level = updatedLevels[id] + null + + addConnection: (turningObject1, turningObject2, connectionType) -> + turningObject1.connections[turningObject2.id] = connectionType + turningObject2.connections[turningObject1.id] = connectionType + @updateGroupsAndLevels() + + findMeshingNeighbors: (gear) -> + meshingNeighbors = [] + for own candidateId, candidate of @gears + if candidate isnt gear and gear.edgeDistance(candidate) < EPSILON + if (candidate.group isnt gear.group) or (candidate.level is gear.level) + meshingNeighbors.push(candidate) + meshingNeighbors + + findRelativeAlignment: (gear1, gear2) -> + # shorter names for readability + p1 = gear1.location + r1 = gear1.rotation + p2 = gear2.location + r2 = gear2.rotation + + # get angles of meshing point and phases at that point + angle1 = Math.atan2(p2.y - p1.y, p2.x - p1.x) + angle2 = angle1 + Math.PI + shift1 = Util.mod(angle1 - r1, 2 * Math.PI) + shift2 = Util.mod(angle2 - r2, 2 * Math.PI) + toothAngle1 = (2 * Math.PI ) / gear1.numberOfTeeth + toothAngle2 = (2 * Math.PI) / gear2.numberOfTeeth + phase1 = (shift1 % toothAngle1) / toothAngle1 + phase2 = (shift2 % toothAngle2) / toothAngle2 + + # find (mis)alignment of gear1 relative to gear2 + phaseSum = (phase1 + phase2) % 1 + (phaseSum - 0.25) * toothAngle1 + + alignGearTeeth: (rotatingGear, meshingGear) -> + rotatingGear.rotation += @findRelativeAlignment(rotatingGear, meshingGear) + + areMeshingGearsAligned: (gear1, gear2) -> + Math.abs(@findRelativeAlignment(gear1, gear2)) < EPSILON + + rotateTurningObjectsFrom: (turningObject, angle, rotatedTurningObjectIds) -> + unless turningObject.id of rotatedTurningObjectIds + turningObject.rotation = Util.mod(turningObject.rotation + angle, 2 * Math.PI) + rotatedTurningObjectIds[turningObject.id] = true + + for own neighborId, connectionType of turningObject.connections + neighbor = @getTurningObjects()[neighborId] + unless neighborId of rotatedTurningObjectIds + ratio = @calculateRatio(turningObject, neighbor, connectionType) + @rotateTurningObjectsFrom(neighbor, angle * ratio, rotatedTurningObjectIds) + + alignMeshingGears: (gear) -> + rotatedGearIds = {} + rotatedGearIds[gear.id] = true + neighbors = @findMeshingNeighbors(gear) + for neighbor in neighbors + @addConnection(gear, neighbor, ConnectionType.MESHING) + r = neighbor.rotation + @alignGearTeeth(neighbor, gear) + angle = neighbor.rotation - r + rotatedGearIds[neighbor.id] = true + @rotateTurningObjectsFrom(neighbor, angle, rotatedGearIds) + + connectToAxis: (upperGear, lowerGear) -> + @addConnection(upperGear, lowerGear, ConnectionType.AXIS) + upperGear.location = lowerGear.location.clone() + upperGear.rotation = lowerGear.rotation + @alignMeshingGears(upperGear) + + findNearestNeighbor: (gear, gearIdsToIgnore = {}) -> + nearestNeighbor = null + shortestEdgeDistance = Number.MAX_VALUE + for own neighborId, neighbor of @gears + if neighbor isnt gear and !(neighborId of gearIdsToIgnore) + edgeDistance = gear.edgeDistance(neighbor) + if edgeDistance < shortestEdgeDistance + nearestNeighbor = neighbor + shortestEdgeDistance = edgeDistance + nearestNeighbor + + connectToOneMeshingGear: (gear, meshingGear) -> + delta = gear.location.minus(meshingGear.location) + angle = Math.atan2(delta.y, delta.x) + gear.location = meshingGear.location.plus(Point.polar(angle, gear.pitchRadius + meshingGear.pitchRadius)) + @alignGearTeeth(gear, meshingGear) + @addConnection(gear, meshingGear, ConnectionType.MESHING) + + connectToTwoMeshingGears: (gear, meshingGear1, meshingGear2) -> + # Finding the correct location is finding the intersection of two circles + # Based on http://paulbourke.net/geometry/circlesphere/ + p0 = meshingGear1.location + p1 = meshingGear2.location + r0 = meshingGear1.pitchRadius + gear.pitchRadius + r1 = meshingGear2.pitchRadius + gear.pitchRadius + + d = p0.distance(p1) + + # check whether meshing gears are close enough to each other and not + # on top of each other; connect gear to closest meshing gear otherwise + if r0 + r1 < d or p0.distance(p1) < EPSILON + if gear.edgeDistance(meshingGear1) < gear.edgeDistance(meshingGear2) + @connectToOneMeshingGear(gear, meshingGear1) + return + else + @connectToOneMeshingGear(gear, meshingGear2) + return + + # connect gear to both gears + a = (r0 * r0 - r1 * r1 + d * d) / (2 * d) + h = Math.sqrt(r0 * r0 - a * a) + + p2 = p0.plus(p1.minus(p0).times(a / d)) + p3x1 = p2.x + h * (p1.y - p0.y) / d + p3y1 = p2.y - h * (p1.x - p0.x) / d + p3x2 = p2.x - h * (p1.y - p0.y) / d + p3y2 = p2.y + h * (p1.x - p0.x) / d + p3_1 = new Point(p3x1, p3y1) + p3_2 = new Point(p3x2, p3y2) + + if gear.location.distance(p3_1) < gear.location.distance(p3_2) + gear.location = p3_1 + else + gear.location = p3_2 + + # recursively align meshing gears + # may include other gears besides meshingGear1 and meshingGear2 + @alignMeshingGears(gear) + + doChainsCrossNonSupportingGears: -> + for own id, chain of @chains + if chain.crossesNonSupportingGears(this) + return true + false + + doChainsCrossEachOther: (c1, c2) -> + if (c1.group isnt c2.group) or (c1.level is c2.level) + if c1.distanceToChain(c2) < Chain.WIDTH + return true + false + + doesChainCrossAnyOtherChain: (chain) -> + for own id2, chain2 of @chains + if chain isnt chain2 + if @doChainsCrossEachOther(chain, chain2) + return true + false + + doAnyChainsCrossEachOther: -> + chainList = (chain for own id, chain of @chains) + numberOfChains = chainList.length + if numberOfChains < 2 + return false + for i in [0...numberOfChains - 1] + for j in [(i + 1)...numberOfChains] + c1 = chainList[i] + c2 = chainList[j] + if @doChainsCrossEachOther(c1, c2) + return true + false + + areAllMeshingGearsAligned: -> + gears = @getGearList() + numberOfGears = gears.length + if numberOfGears < 2 + return true + for i in [0...numberOfGears - 1] + for j in [(i + 1)...numberOfGears] + g1 = gears[i] + g2 = gears[j] + if g1.connections[g2.id] is ConnectionType.MESHING + if !@areMeshingGearsAligned(g1, g2) + return false + true + + calculateRatio: (turningObject1, turningObject2, connectionType) -> + if connectionType is ConnectionType.AXIS + 1 + else if (connectionType is ConnectionType.MESHING) or (connectionType is ConnectionType.CHAIN_OUTSIDE) + -turningObject1.getCircumference() / turningObject2.getCircumference() + else # gear inside chain + turningObject1.getCircumference() / turningObject2.getCircumference() + + calculatePathRatio: (path) -> + ratio = 1 + pathLength = path.length + for i in [0...pathLength - 1] + turningObject1 = path[i] + turningObject2 = path[i + 1] + connectionType = turningObject1.connections[turningObject2.id] + ratio *= @calculateRatio(turningObject1, turningObject2, connectionType) + ratio + + areConnectionRatiosConsistent: -> + ratios = {} + paths = Util.findAllSimplePathsBetweenNeighbors(@getTurningObjects()) + for path in paths + pathName = "#{path[0].id}-#{path[path.length - 1].id}" + ratio = @calculatePathRatio(path) + if !ratios[pathName]? + ratios[pathName] = ratio + else + if Math.abs(ratios[pathName] - ratio) > EPSILON + return false + true + + isBoardValid: -> + for own id1, gear1 of @gears + group1 = gear1.group + level1 = gear1.level + for own id2, gear2 of @gears + unless gear1 is gear2 + group2 = gear2.group + level2 = gear2.level + axisDistance = gear1.location.distance(gear2.location) + maxOuterRadius = Math.max(gear1.outerRadius, gear2.outerRadius) + combinedOuterRadius = gear1.outerRadius + gear2.outerRadius + if axisDistance < EPSILON + if (group1 isnt group2) or (level1 is level2) + return false + else if group1 is group2 and level1 is level2 and !gear1.connections[gear2.id]? + if axisDistance < combinedOuterRadius + return false + else if axisDistance < maxOuterRadius + AXIS_RADIUS + return false + !@doChainsCrossNonSupportingGears() and + !@doAnyChainsCrossEachOther() and + @areAllMeshingGearsAligned() and + @areConnectionRatiosConsistent() + + placeGear: (gear, location) -> + oldBoard = @clone() + @removeAllConnections(gear) + gear.location = location.clone() + nearestAxis = @findNearestAxis(gear) + if nearestAxis and + gear.location.distance(nearestAxis.location) < SNAPPING_DISTANCE and + nearestAxis.numberOfTeeth - gear.numberOfTeeth > MIN_STACKED_GEARS_TEETH_DIFFERENCE + # connect gear to axis of larger gear + @connectToAxis(gear, nearestAxis) + else + neighbor1 = @findNearestNeighbor(gear) + if neighbor1 and gear.edgeDistance(neighbor1) < SNAPPING_DISTANCE + # make gear mesh with one or two neighbors + neighbor2 = @findNearestNeighbor(gear, Util.makeSet(neighbor1.id)) + if neighbor2 and gear.edgeDistance(neighbor2) < SNAPPING_DISTANCE + @connectToTwoMeshingGears(gear, neighbor1, neighbor2) + else + @connectToOneMeshingGear(gear, neighbor1) + + # update chains + for own id, chain of @chains + if chain.update(this) + @updateChainConnections(chain) + else + @restore(oldBoard) + return false + + # check if board is valid + if @isBoardValid() + true + else + @restore(oldBoard) + false + + addGearToChains: (gear) -> + for own id, chain of @chains + if Util.isPointInsidePolygon(gear.location, chain.toPolygon()) + chain.innerGearIds[gear.id] = true + + removeGearFromChains: (gear) -> + for own id, chain of @chains + if !chain.removeGear(gear, this) or @doesChainCrossAnyOtherChain(chain) + @removeChain(chain) + else + @updateChainConnections(chain) + + addGear: (gear) -> + oldBoard = @clone() + gear.group = @getNextGroup() + @gears[gear.id] = gear + @addGearToChains(gear) + if !@placeGear(gear, gear.location) + @removeGear(gear) + @restore(oldBoard) + false + else + true + + removeGear: (gear) -> + @removeAllConnections(gear) + delete @gears[gear.id] + @removeGearFromChains(gear) + + getGearAt: (location, candidates = @gears) -> + gear = null + for own id, candidate of candidates + distance = location.distance(candidate.location) + if distance < candidate.outerRadius + if !gear or candidate.numberOfTeeth < gear.numberOfTeeth + gear = candidate + gear + + isTopLevelGear: (gear) -> + for own id, connectionType of gear.connections + if connectionType is ConnectionType.AXIS and @gears[id].level > gear.level + return false + true + + getTopLevelGears: -> + topLevelGears = {} + topLevelGears[id] = gear for own id, gear of @gears when @isTopLevelGear(gear) + topLevelGears + + getTopLevelGearAt: (location) -> + @getGearAt(location, @getTopLevelGears()) + + getGearWithId: (id) -> + @gears[id] + + getGearsWithIds: (ids) -> + @gears[id] for id in ids + + rotateAllTurningObjects: (delta) -> + for own id, gear of @gears + if gear.momentum + angle = gear.momentum * (delta / 1000) + @rotateTurningObjectsFrom(gear, angle, {}) + + addChainConnections: (chain) -> + for gearId in chain.supportingGearIds + if gearId of chain.innerGearIds + @addConnection(chain, @getGearWithId(gearId), ConnectionType.CHAIN_INSIDE) + else + @addConnection(chain, @getGearWithId(gearId), ConnectionType.CHAIN_OUTSIDE) + + updateChainConnections: (chain) -> + @removeAllConnections(chain) + @addChainConnections(chain) + + addChain: (chain) -> + oldBoard = @clone() + @chains[chain.id] = chain + if chain.tighten(this) + @chains[chain.id] = chain + @addChainConnections(chain) + else + @restore(oldBoard) + return false + if @isBoardValid() + true + else + @restore(oldBoard) + false + + removeChain: (chain) -> + @removeAllConnections(chain) + delete @chains[chain.id] + + getChains: -> + @chains + + getChainsInGroupOnLevel: (group, level) -> + chain for own id, chain of @chains when (chain.group is group) and (chain.level is level) + + getTurningObjects: -> + turningObjects = {} + turningObjects[id] = gear for own id, gear of @gears + turningObjects[id] = chain for own id, chain of @chains + turningObjects + + clone: -> + gears: Util.clone(@gears) + chains: Util.clone(@chains) + + @fromObject: (obj) -> + board = new Board() + for id, gear of obj.gears + board.gears[id] = Gear.fromObject(gear) + for id, chain of obj.chains + board.chains[id] = Chain.fromObject(chain) + board + +window.gearsketch.model.Board = Board \ No newline at end of file diff --git a/activities/Gears.activity/lib/gearsketch/src/gearsketch_util.coffee b/activities/Gears.activity/lib/gearsketch/src/gearsketch_util.coffee new file mode 100644 index 000000000..5fccd7df1 --- /dev/null +++ b/activities/Gears.activity/lib/gearsketch/src/gearsketch_util.coffee @@ -0,0 +1,535 @@ +# By Frank Leenaars +# University of Twente - Department of Instructional Technology +# Licensed under the MIT license +"use strict" + +window.gearsketch = {} + +# --------------------------- +# ---------- Point ---------- +# --------------------------- +class Point + constructor: (@x, @y) -> + + plus: (p) -> + new Point(@x + p.x, @y + p.y) + + minus: (p) -> + new Point(@x - p.x, @y - p.y) + + times: (n) -> + new Point(n * @x, n * @y) + + distance: (p) -> + Math.sqrt(Math.pow(@x - p.x, 2) + Math.pow(@y - p.y, 2)) + + cross: (p) -> + @x * p.y - @y * p.x + + clone: -> + new Point(@x, @y) + + @polar: (theta, r) -> + new Point(r * Math.cos(theta), r * Math.sin(theta)) + + @fromObject: (obj) -> + new Point(obj.x, obj.y) + +window.gearsketch.Point = Point + +# --------------------------- +# ------- ArcSegment -------- +# --------------------------- +# Note: ArcSegment should be considered immutable, because @start and @end are +# calculated on construction, but not updated on change +class ArcSegment + constructor: (@center, @radius, @startAngle, @endAngle, @direction) -> + @start = @pointOnCircle(startAngle) + @end = @pointOnCircle(endAngle) + + getLength: -> + angle = if @direction is Util.Direction.CLOCKWISE + Util.mod(@endAngle - @startAngle, 2 * Math.PI) + else + Util.mod(@startAngle - @endAngle, 2 * Math.PI) + angle * @radius + + findPoint: (distanceToGo) -> + angleToGo = distanceToGo / @radius + angle = @startAngle + (if @direction is Util.Direction.CLOCKWISE then angleToGo else -angleToGo) + @center.plus(Point.polar(angle, @radius)) + + pointOnCircle: (angle) -> + @center.plus(Point.polar(angle, @radius)) + + containsAngle: (angle) -> + if @direction is Util.Direction.CLOCKWISE + Util.mod(@endAngle - @startAngle, 2 * Math.PI) > Util.mod(angle - @startAngle, 2 * Math.PI) + else + Util.mod(@startAngle - @endAngle, 2 * Math.PI) > Util.mod(@startAngle - angle, 2 * Math.PI) + + distanceToPoint: (point) -> + angle = Math.atan2(point.y - @center.y, point.x - @center.x) + if @containsAngle(angle) + Math.abs(point.distance(@center) - @radius) + else + Math.min(point.distance(@start), point.distance(@end)) + + intersectsLineSegment: (lineSegment) -> + # check if circle intersects line + # http://mathworld.wolfram.com/Circle-LineIntersection.html + p1 = lineSegment.start.minus(@center) + p2 = lineSegment.end.minus(@center) + dx = p2.x - p1.x + dy = p2.y - p1.y + dr = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) + if dr is 0 + return false + d = p1.x * p2.y - p2.x * p1.y + discriminant = Math.pow(@radius, 2) * Math.pow(dr, 2) - Math.pow(d, 2) + if discriminant < 0 + false + else + i1x = (d * dy + Util.sign(dy) * dx * Math.sqrt(discriminant)) / Math.pow(dr, 2) + i1y = (-d * dx + Math.abs(dy) * Math.sqrt(discriminant)) / Math.pow(dr, 2) + i1 = new Point(i1x, i1y).plus(@center) + if lineSegment.distanceToPoint(i1) < Util.EPSILON and @distanceToPoint(i1) < Util.EPSILON + return true + i2x = (d * dy - Util.sign(dy) * dx * Math.sqrt(discriminant)) / Math.pow(dr, 2) + i2y = (-d * dx - Math.abs(dy) * Math.sqrt(discriminant)) / Math.pow(dr, 2) + i2 = new Point(i2x, i2y).plus(@center) + if lineSegment.distanceToPoint(i2) < Util.EPSILON and @distanceToPoint(i2) < Util.EPSILON + return true + false + + # TODO: fix algorithms (current implementation works for current usage, but is incorrect) + distanceToSegment: (segment) -> + if segment instanceof ArcSegment + if @center.distance(segment.center) > Util.EPSILON + angle1 = Math.atan2(segment.center.y - @center.y, segment.center.x - @center.x) + angle2 = Util.mod(angle1 + Math.PI, 2 * Math.PI) + if @containsAngle(angle1) and segment.containsAngle(angle2) + centerDistance = @center.distance(segment.center) + return Math.max(0, centerDistance - @radius - segment.radius) + Math.min(@distanceToPoint(segment.start) + , @distanceToPoint(segment.end) + , segment.distanceToPoint(@start) + , segment.distanceToPoint(@end)) + else # segment is LineSegment + if @intersectsLineSegment(segment) + 0 + else + pointNearestToCenter = segment.findNearestPoint(@center) + Math.min(@distanceToPoint(pointNearestToCenter) + , @distanceToPoint(segment.start) + , @distanceToPoint(segment.end) + , segment.distanceToPoint(@start) + , segment.distanceToPoint(@end)) + + clone: -> + new ArcSegment(@center.clone(), @radius, @startAngle, @endAngle, @direction) + + @fromObject: (obj) -> + new ArcSegment(Point.fromObject(obj.center), obj.radius, obj.startAngle, obj.endAngle, obj.direction) + +window.gearsketch.ArcSegment = ArcSegment + +# --------------------------- +# ------- LineSegment ------- +# --------------------------- +class LineSegment + constructor: (@start, @end) -> + + getLength: -> + @start.distance(@end) + + findPoint: (distanceToGo) -> + fraction = distanceToGo / @start.distance(@end) + @start.plus(@end.minus(@start).times(fraction)) + + # http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment + findNearestPoint: (p) -> + segmentLength = @getLength() + if segmentLength is 0 + @start + else + t = ((p.x - @start.x) * (@end.x - @start.x) + (p.y - @start.y) * (@end.y - @start.y)) / Math.pow(segmentLength, 2) + if t < 0 + @start + else if t > 1 + @end + else + @start.plus(@end.minus(@start).times(t)) + + distanceToPoint: (point) -> + point.distance(@findNearestPoint(point)) + + # http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect + findIntersection: (lineSegment) -> + p = @start + r = @end.minus(p) + q = lineSegment.start + s = lineSegment.end.minus(q) + crossRS = r.cross(s) + t = q.minus(p).cross(s) / crossRS + u = q.minus(p).cross(r) / crossRS + if Math.abs(crossRS) > Util.EPSILON and 0 <= t and t <= 1 and 0 <= u and u <= 1 + p.plus(r.times(t)) + else + null + + distanceToSegment: (segment) -> + if segment instanceof LineSegment + if @findIntersection(segment) + 0 + else + Math.min( + @distanceToPoint(segment.start) + , @distanceToPoint(segment.end) + , segment.distanceToPoint(@start) + , segment.distanceToPoint(@end)) + else # segment is ArcSegment + segment.distanceToSegment(this) + + clone: -> + new LineSegment(@start.clone(), @end.clone()) + + @fromObject: (obj) -> + new LineSegment(Point.fromObject(obj.start), Point.fromObject(obj.end)) + +window.gearsketch.LineSegment = LineSegment + +# --------------------------- +# ---------- Util ----------- +# --------------------------- +class Util + # imports + Point = window.gearsketch.Point + + # -- constants -- + @MODULE: 6 + @AXIS_RADIUS: 1.5 * @MODULE + @MIN_STACKED_GEARS_TEETH_DIFFERENCE: 4 + @SNAPPING_DISTANCE: 2 * @MODULE + @EPSILON: 0.000001 + + # -- enums -- + @Direction: + CLOCKWISE: "clockwise" + COUNTER_CLOCKWISE: "counterclockwise" + + @Side: + LEFT: "left" + RIGHT: "right" + + # http://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object + @clone: (obj) -> + if !obj? or (typeof obj isnt "object") + return obj + + knownClasses = ["Point", "Gear", "ArcSegment", "LineSegment", "Chain"] + if obj.constructor.name in knownClasses + return obj.clone() + + if obj instanceof Array + copy = [] + for i in [0...obj.length] + copy[i] = @clone(obj[i]) + return copy + + if obj instanceof Object + copy = {} + for own key of obj + copy[key] = @clone(obj[key]) + return copy + + throw new Error("Unable to clone object. Its type is not supported.") + + # http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript + @createUUID: -> + "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) -> + r = Math.random() * 16 | 0 + v = if c is "x" then r else (r & 0x3 | 0x8) + v.toString(16)) + + @mod: (a, b) -> + (a % b + b) % b + + @sign: (x) -> + if x < 0 then -1 else 1 + + @addAll: (set, elements) -> + for element in elements + set[element] = true + set + + @makeSetFromList: (elements) -> + @addAll({}, elements) + + @makeSet: (elements...) -> + @makeSetFromList(elements) + + # find the point on the path at the given distance from its start + @findPointOnPath: (path, distance) -> + distanceToGo = distance + i = 0 + numberOfPoints = path.length + while distanceToGo > 0 + j = (i + 1) % numberOfPoints + segment = new LineSegment(path[i], path[j]) + segmentLength = segment.getLength() + if distanceToGo <= segmentLength + return segment.findPoint(distanceToGo) + else + i = j + distanceToGo -= segmentLength + return null + + @getLength: (path, isPathClosed = true) -> + length = 0 + numberOfPoints = path.length + finalIndex = numberOfPoints - (if isPathClosed then 0 else 1) + for i in [0...finalIndex] + j = (i + 1) % numberOfPoints + length += path[i].distance(path[j]) + length + + # http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + @isPointInsidePolygon: (point, polygon) -> + isPointInPolygon = false + x = point.x + y = point.y + numberOfVertices = polygon.length + j = numberOfVertices - 1 + for i in [0...numberOfVertices] + pix = polygon[i].x + piy = polygon[i].y + pjx = polygon[j].x + pjy = polygon[j].y + if ((piy > y) isnt (pjy > y)) and (x < ((pjx - pix) * (y - piy) / (pjy - piy) + pix)) + isPointInPolygon = !isPointInPolygon + j = i + isPointInPolygon + + @isGearInsidePolygon: (gear, polygon) -> + edgePointAtAngle = (angle) -> + gear.location.plus(Point.polar(angle, gear.innerRadius)) + edgePoints = (edgePointAtAngle(0.25 * Math.PI * i) for i in [0...8]) + edgePoints.every((p) => @isPointInsidePolygon(p, polygon)) + + @findGearsInsidePolygon: (polygon, gears) -> + gear for own id, gear of gears when @isGearInsidePolygon(gear, polygon) + + @doesGearIntersectLineSegment: (gear, segment) -> + segment.distanceToPoint(gear.location) < (gear.pitchRadius + Util.EPSILON) + + @findGearsIntersectingSegment: (gears, segment) -> + gear for own id, gear of gears when @doesGearIntersectLineSegment(gear, segment) + + @pointPathDistance: (point, path, isPathClosed = true) -> + # using points instead of segments + distance = Number.MAX_VALUE + numberOfPoints = path.length + finalIndex = numberOfPoints - (if isPathClosed then 0 else 1) + for i in [0...finalIndex] + j = (i + 1) % numberOfPoints + segment = new LineSegment(path[i], path[j]) + d = Math.max(0, segment.distanceToPoint(point)) + distance = Math.min(distance, d) + distance + + # return gear nearest to lineSegment.start that intersects lineSegment or null if no such gear exists + # if ignoredGears is specified, these gears will never be returned + @findNearestIntersectingGear: (gears, lineSegment, ignoredGearIds = {}) -> + intersectingGears = @findGearsIntersectingSegment(gears, lineSegment) + intersectingGears.sort((g1, g2) => + g1.distanceToPoint(lineSegment.start) - g2.distanceToPoint(lineSegment.start)) + for intersectingGear in intersectingGears + unless intersectingGear.id of ignoredGearIds + return intersectingGear + null + + # http://stackoverflow.com/questions/451426/how-do-i-calculate-the-surface-area-of-a-2d-polygon + @findDirection: (polygon) -> + numberOfPoints = polygon.length + doubleArea = 0 + for i in [0...numberOfPoints] + j = (i + 1) % numberOfPoints + doubleArea += polygon[i].x * polygon[j].y + doubleArea -= polygon[i].y * polygon[j].x + if doubleArea > 0 + @Direction.CLOCKWISE + else + @Direction.COUNTER_CLOCKWISE + + # get the two tangent points on a circle with center c and radius r from a given point p + # tangent points are only valid if |pc| > r + @findTangentPoints: (p, c, r) -> + tangentPoints = {} + d = p.distance(c) + if Math.abs(d - r) < Util.EPSILON # p on circle + tangentPoints[@Side.RIGHT] = p.clone() + tangentPoints[@Side.LEFT] = p.clone() + else + l = Math.sqrt(d * d - r * r) + alpha = Math.atan2(c.y - p.y, c.x - p.x) + beta = Math.asin(r / d) + tangentPoints[@Side.RIGHT] = p.plus(Point.polar(alpha + beta, l)) + tangentPoints[@Side.LEFT] = p.plus(Point.polar(alpha - beta, l)) + tangentPoints + + @findGearTangentPoints: (p, gear) -> + @findTangentPoints(p, gear.location, gear.pitchRadius) + + # http://en.wikipedia.org/wiki/Tangent_lines_to_circles + @findExternalTangents: (centers, radii) -> + largest = if radii[0] >= radii[1] then 0 else 1 + o1 = centers[largest] + o2 = centers[1 - largest] + r1 = radii[largest] + r2 = radii[1 - largest] + r3 = r1 - r2 + if r3 is 0 + tangentPoints = {} + tangentPoints[@Side.LEFT] = o1 + tangentPoints[@Side.RIGHT] = o1 + angle = Math.atan2(o2.y - o1.y, o2.x - o1.x) + offset1 = Point.polar(angle + 0.5 * Math.PI, r1) + offset2 = Point.polar(angle - 0.5 * Math.PI, r1) + else + tangentPoints = @findTangentPoints(o2, o1, r3) + ratio = r2 / r3 + tpl = tangentPoints[@Side.LEFT] + tpr = tangentPoints[@Side.RIGHT] + offset1 = tpl.minus(o1).times(ratio) + offset2 = tpr.minus(o1).times(ratio) + tangentLine1 = [tangentPoints[@Side.LEFT].plus(offset1), o2.plus(offset1)] + tangentLine2 = [tangentPoints[@Side.RIGHT].plus(offset2), o2.plus(offset2)] + tangentLines = {} + if o1 is centers[0] + tangentLines[@Side.RIGHT] = new LineSegment(tangentLine1[0], tangentLine1[1]) + tangentLines[@Side.LEFT] = new LineSegment(tangentLine2[0], tangentLine2[1]) + else + tangentLines[@Side.RIGHT] = new LineSegment(tangentLine2[1], tangentLine2[0]) + tangentLines[@Side.LEFT] = new LineSegment(tangentLine1[1], tangentLine1[0]) + tangentLines + + # http://en.wikipedia.org/wiki/Tangent_lines_to_circles + @findInternalTangents: (centers, radii) -> + largest = if radii[0] >= radii[1] then 0 else 1 + o1 = centers[largest] + o2 = centers[1 - largest] + r1 = radii[largest] + r2 = radii[1 - largest] + r3 = r1 + r2 + tangentPoints = @findTangentPoints(o2, o1, r3) + ratio = r2 / r3 + tpl = tangentPoints[@Side.LEFT] + tpr = tangentPoints[@Side.RIGHT] + offset1 = o1.minus(tpl).times(ratio) + offset2 = o1.minus(tpr).times(ratio) + tangentLine1 = [tpl.plus(offset1), o2.plus(offset1)] + tangentLine2 = [tpr.plus(offset2), o2.plus(offset2)] + tangentLines = {} + if o1 is centers[0] + tangentLines[@Side.RIGHT] = new LineSegment(tangentLine1[0], tangentLine1[1]) + tangentLines[@Side.LEFT] = new LineSegment(tangentLine2[0], tangentLine2[1]) + else + tangentLines[@Side.RIGHT] = new LineSegment(tangentLine1[1], tangentLine1[0]) + tangentLines[@Side.LEFT] = new LineSegment(tangentLine2[1], tangentLine2[0]) + tangentLines + + @findExternalTangentsOfGears: (gear1, gear2) -> + @findExternalTangents([gear1.location, gear2.location], [gear1.pitchRadius, gear2.pitchRadius]) + + @findInternalTangentsOfGears: (gear1, gear2) -> + @findInternalTangents([gear1.location, gear2.location], [gear1.pitchRadius, gear2.pitchRadius]) + + @findTangentLine: (gear1, gear2, innerGearIds, direction) -> + gear1isInnerGear = (gear1.id of innerGearIds) + if gear1isInnerGear is (direction is @Direction.CLOCKWISE) + side = @Side.LEFT + else + side = @Side.RIGHT + if gear1isInnerGear is (gear2.id of innerGearIds) + @findExternalTangentsOfGears(gear1, gear2)[side] + else + @findInternalTangentsOfGears(gear1, gear2)[side] + + @findAllSimplePathsForNodes: (turningObjects, goalNode, nodesVisited) -> + paths = [] + currentNode = nodesVisited[nodesVisited.length - 1] + for own neighborId of currentNode.connections + neighbor = turningObjects[neighborId] + unless neighbor in nodesVisited + updatedNodesVisited = nodesVisited.slice(0) + updatedNodesVisited.push(neighbor) + if neighbor is goalNode + paths.push(updatedNodesVisited) + else + paths = paths.concat(@findAllSimplePathsForNodes(turningObjects, goalNode, updatedNodesVisited)) + return paths + + @findAllSimplePathsBetweenNeighbors: (turningObjects) -> + paths = [] + nodes = (turningObject for own id, turningObject of turningObjects) + if nodes.length < 2 + return [] + for i in [0...nodes.length - 1] + for j in [(i + 1)...nodes.length] + if nodes[i].connections[nodes[j].id]? + paths = paths.concat(@findAllSimplePathsForNodes(turningObjects, nodes[j], [nodes[i]])) + for i in [0...paths.length] + paths.push(paths[i].slice(0).reverse()) + paths + + @sendGetRequest: (url) -> + request = new XMLHttpRequest() + request.open("GET", url, false) + request.send(null) + request.responseText + + @sendPostRequest: (data, url, callback) -> + request = new XMLHttpRequest() + request.open("POST", url, true) + request.setRequestHeader("Content-type", "application/json; charset=UTF-8") + request.onload = callback + request.send(data) + +window.gearsketch.Util = Util + +# requestAnimationFrame polyfill +# http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating +do -> + lastTime = 0 + vendors = ["ms", "moz", "webkit", "o"] + for vendor in vendors when !window.requestAnimationFrame + window.requestAnimationFrame = window[vendor + "RequestAnimationFrame"] + window.cancelAnimationFrame = window[vendor + "CancelAnimationFrame"] or + window[vendor + "CancelRequestAnimationFrame"] + + if !window.requestAnimationFrame + window.requestAnimationFrame = (callback) -> + currTime = new Date().getTime() + timeToCall = Math.max(0, 16 - (currTime - lastTime)) + id = window.setTimeout((-> + callback(currTime + timeToCall) + ), timeToCall) + lastTime = currTime + timeToCall + id + + if !window.cancelAnimationFrame + window.cancelAnimationFrame = (id) -> + clearTimeout(id) + +# Function.name support for IE +# http://matt.scharley.me/2012/03/09/monkey-patch-name-ie.html +do -> + if !Function.prototype.name? and Object.defineProperty? + Object.defineProperty(Function.prototype, "name", + get: -> + funcNameRegex = /function\s([^(]{1,})\(/ + results = (funcNameRegex).exec((this).toString()) + if (results? and results.length > 1) then results[1].trim() else "" + set: (value) -> + ) diff --git a/activities/Gears.activity/lib/mustache.js b/activities/Gears.activity/lib/mustache.js new file mode 100644 index 000000000..932052b4a --- /dev/null +++ b/activities/Gears.activity/lib/mustache.js @@ -0,0 +1,610 @@ +/*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + */ + +/*global define: false*/ + +(function (root, factory) { + if (typeof exports === "object" && exports) { + module.exports = factory; // CommonJS + } else if (typeof define === "function" && define.amd) { + define(factory); // AMD + } else { + root.Mustache = factory; // + + +
+ + +
+
+
+
    + + + + +
    +
    + + diff --git a/activities/GetThingsDone.activity/js/activity.js b/activities/GetThingsDone.activity/js/activity.js new file mode 100644 index 000000000..61814a4a2 --- /dev/null +++ b/activities/GetThingsDone.activity/js/activity.js @@ -0,0 +1,93 @@ +define(function (require) { + var activity = require("sugar-web/activity/activity"); + var datastore = require("sugar-web/datastore"); + + var model = require("activity/model"); + var view = require("activity/view"); + var controller = require("activity/controller"); + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + var todo; + + // Initialize the activity. + activity.setup(); + + activity.write = function (callback) { + console.log("writing..."); + var jsonData = JSON.stringify(todo.model.items); + this.getDatastoreObject().setDataAsText(jsonData); + this.getDatastoreObject().save(function (error) { + if (error === null) { + console.log("write done."); + } + else { + console.log("write failed."); + } + callback(error); + }); + }; + + // Set up a brand new TODO list + + function Todo() { + this.model = new model.Model(); + this.view = new view.View(); + this.controller = new controller.Controller(this.model, this.view); + } + + todo = new Todo(); + var datastoreObject = activity.getDatastoreObject(); + function onLoaded(error, metadata, data) { + todo.controller.loadItems(JSON.parse(data)); + } + datastoreObject.loadAsText(onLoaded); + + var input = document.getElementById("new-todo"); + input.addEventListener('keypress', function (e) { + if (e.keyCode === todo.controller.ENTER_KEY) { + var success = todo.controller.addItem(e.target.value); + if (success) { + e.target.value = ''; + } + } + }); + + var inputButton = document.getElementById("new-todo-button"); + inputButton.addEventListener('click', function (e) { + var success = todo.controller.addItem(input.value); + if (success) { + input.value = ''; + } + }); + + // Find the model ID of the clicked DOM element + + function lookupId(target) { + var lookup = target; + + while (lookup.nodeName !== 'LI') { + lookup = lookup.parentNode; + } + + return lookup.dataset.id; + } + + var list = document.getElementById("todo-list"); + list.addEventListener('click', function (e) { + var target = e.target; + + if (target.className.indexOf('remove') > -1) { + todo.controller.removeItem(lookupId(target)); + } + + if (target.className.indexOf('toggle') > -1) { + todo.controller.toggleComplete(lookupId(target), target); + } + + }); + + }); + +}); diff --git a/activities/GetThingsDone.activity/js/controller.js b/activities/GetThingsDone.activity/js/controller.js new file mode 100644 index 000000000..36962ba8a --- /dev/null +++ b/activities/GetThingsDone.activity/js/controller.js @@ -0,0 +1,43 @@ +define(function (require) { + + var controller = {}; + + // Take a model and view and act as the controller between them. + controller.Controller = function (model, view) { + this.model = model; + this.view = view; + + this.ENTER_KEY = 13; + this.ESCAPE_KEY = 27; + }; + + controller.Controller.prototype.loadItems = function (items) { + this.model.load(items); + var list = document.getElementById("todo-list"); + list.innerHTML = this.view.show(items); + }; + + controller.Controller.prototype.addItem = function (title) { + if (title.trim() === '') { + return false; + } + var item = this.model.create(title); + var list = document.getElementById("todo-list"); + list.innerHTML += this.view.show([item]); + return true; + }; + + controller.Controller.prototype.removeItem = function (id) { + this.model.remove(id); + this.loadItems(this.model.items); + }; + + controller.Controller.prototype.toggleComplete = function (id, checkbox) { + var completed = checkbox.checked ? 1 : 0; + this.model.update(id, {completed: completed}); + this.loadItems(this.model.items); + }; + + return controller; + +}); diff --git a/activities/GetThingsDone.activity/js/loader.js b/activities/GetThingsDone.activity/js/loader.js new file mode 100644 index 000000000..01021e981 --- /dev/null +++ b/activities/GetThingsDone.activity/js/loader.js @@ -0,0 +1,8 @@ +requirejs.config({ + baseUrl: "lib", + paths: { + activity: "../js" + } +}); + +requirejs(["activity/activity"]); diff --git a/activities/GetThingsDone.activity/js/model.js b/activities/GetThingsDone.activity/js/model.js new file mode 100644 index 000000000..f8e028ff9 --- /dev/null +++ b/activities/GetThingsDone.activity/js/model.js @@ -0,0 +1,47 @@ +define(function (require) { + + var model = {}; + + model.Model = function () { + this.items = []; + }; + + model.Model.prototype.load = function (items) { + this.items = items; + }; + + model.Model.prototype.create = function (title) { + title = title || ''; + + var newItem = { + id: new Date().getTime(), + title: title.trim(), + completed: 0 + }; + + this.items.push(newItem); + return newItem; + }; + + model.Model.prototype.remove = function (id) { + for (var i = 0; i < this.items.length; i++) { + if (this.items[i].id == id) { + this.items.splice(i, 1); + break; + } + } + }; + + model.Model.prototype.update = function (id, updateData) { + for (var i = 0; i < this.items.length; i++) { + if (this.items[i].id == id) { + for (var x in updateData) { + this.items[i][x] = updateData[x]; + } + } + } + }; + + return model; + +}); diff --git a/activities/GetThingsDone.activity/js/view.js b/activities/GetThingsDone.activity/js/view.js new file mode 100644 index 000000000..9218a2cc4 --- /dev/null +++ b/activities/GetThingsDone.activity/js/view.js @@ -0,0 +1,40 @@ +define(function (require) { + + var view = {}; + + view.View = function () { + this.template = + '
  • ' + + '
    ' + + '' + + '' + + '' + + '
    ' + + '
  • '; + }; + + // Create a string of HTML
  • elements inside with the given + // items. + view.View.prototype.show = function (items) { + var i, l; + var view = ''; + for (i = 0, l = items.length; i < l; i++) { + var template = this.template; + var completed = ''; + var checked = ''; + if (items[i].completed === 1) { + completed = 'completed'; + checked = 'checked'; + } + template = template.replace('{{id}}', items[i].id); + template = template.replace('{{title}}', items[i].title); + template = template.replace('{{completed}}', completed); + template = template.replace('{{checked}}', checked); + view = view + template; + } + return view; + }; + + return view; + +}); diff --git a/activities/GetThingsDone.activity/lib/domReady.js b/activities/GetThingsDone.activity/lib/domReady.js new file mode 100644 index 000000000..2b5412209 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/domReady.js @@ -0,0 +1,129 @@ +/** + * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/domReady for details + */ +/*jslint */ +/*global require: false, define: false, requirejs: false, + window: false, clearInterval: false, document: false, + self: false, setInterval: false */ + + +define(function () { + 'use strict'; + + var isTop, testDiv, scrollIntervalId, + isBrowser = typeof window !== "undefined" && window.document, + isPageLoaded = !isBrowser, + doc = isBrowser ? document : null, + readyCalls = []; + + function runCallbacks(callbacks) { + var i; + for (i = 0; i < callbacks.length; i += 1) { + callbacks[i](doc); + } + } + + function callReady() { + var callbacks = readyCalls; + + if (isPageLoaded) { + //Call the DOM ready callbacks + if (callbacks.length) { + readyCalls = []; + runCallbacks(callbacks); + } + } + } + + /** + * Sets the page as loaded. + */ + function pageLoaded() { + if (!isPageLoaded) { + isPageLoaded = true; + if (scrollIntervalId) { + clearInterval(scrollIntervalId); + } + + callReady(); + } + } + + if (isBrowser) { + if (document.addEventListener) { + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. + document.addEventListener("DOMContentLoaded", pageLoaded, false); + window.addEventListener("load", pageLoaded, false); + } else if (window.attachEvent) { + window.attachEvent("onload", pageLoaded); + + testDiv = document.createElement('div'); + try { + isTop = window.frameElement === null; + } catch (e) {} + + //DOMContentLoaded approximation that uses a doScroll, as found by + //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, + //but modified by other contributors, including jdalton + if (testDiv.doScroll && isTop && window.external) { + scrollIntervalId = setInterval(function () { + try { + testDiv.doScroll(); + pageLoaded(); + } catch (e) {} + }, 30); + } + } + + //Check if document already complete, and if so, just trigger page load + //listeners. Latest webkit browsers also use "interactive", and + //will fire the onDOMContentLoaded before "interactive" but not after + //entering "interactive" or "complete". More details: + //http://dev.w3.org/html5/spec/the-end.html#the-end + //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded + //Hmm, this is more complicated on further use, see "firing too early" + //bug: https://github.com/requirejs/domReady/issues/1 + //so removing the || document.readyState === "interactive" test. + //There is still a window.onload binding that should get fired if + //DOMContentLoaded is missed. + if (document.readyState === "complete") { + pageLoaded(); + } + } + + /** START OF PUBLIC API **/ + + /** + * Registers a callback for DOM ready. If DOM is already ready, the + * callback is called immediately. + * @param {Function} callback + */ + function domReady(callback) { + if (isPageLoaded) { + callback(doc); + } else { + readyCalls.push(callback); + } + return domReady; + } + + domReady.version = '2.0.1'; + + /** + * Loader Plugin API method + */ + domReady.load = function (name, req, onLoad, config) { + if (config.isBuild) { + onLoad(null); + } else { + domReady(onLoad); + } + }; + + /** END OF PUBLIC API **/ + + return domReady; +}); diff --git a/activities/GetThingsDone.activity/lib/require.js b/activities/GetThingsDone.activity/lib/require.js new file mode 100644 index 000000000..5b2687543 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/require.js @@ -0,0 +1,2000 @@ +/** vim: et:ts=4:sw=4:sts=4 + * @license RequireJS 2.1.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/jrburke/requirejs for details + */ +//Not using strict: uneven strict support in browsers, #392, and causes +//problems with requirejs.exec()/transpiler plugins that may not be strict. +/*jslint regexp: true, nomen: true, sloppy: true */ +/*global window, navigator, document, importScripts, setTimeout, opera */ + +var requirejs, require, define; +(function (global) { + var req, s, head, baseElement, dataMain, src, + interactiveScript, currentlyAddingScript, mainScript, subPath, + version = '2.1.4', + commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, + cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, + jsSuffixRegExp = /\.js$/, + currDirRegExp = /^\.\//, + op = Object.prototype, + ostring = op.toString, + hasOwn = op.hasOwnProperty, + ap = Array.prototype, + apsp = ap.splice, + isBrowser = !!(typeof window !== 'undefined' && navigator && document), + isWebWorker = !isBrowser && typeof importScripts !== 'undefined', + //PS3 indicates loaded and complete, but need to wait for complete + //specifically. Sequence is 'loading', 'loaded', execution, + // then 'complete'. The UA check is unfortunate, but not sure how + //to feature test w/o causing perf issues. + readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ? + /^complete$/ : /^(complete|loaded)$/, + defContextName = '_', + //Oh the tragedy, detecting opera. See the usage of isOpera for reason. + isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]', + contexts = {}, + cfg = {}, + globalDefQueue = [], + useInteractive = false; + + function isFunction(it) { + return ostring.call(it) === '[object Function]'; + } + + function isArray(it) { + return ostring.call(it) === '[object Array]'; + } + + /** + * Helper function for iterating over an array. If the func returns + * a true value, it will break out of the loop. + */ + function each(ary, func) { + if (ary) { + var i; + for (i = 0; i < ary.length; i += 1) { + if (ary[i] && func(ary[i], i, ary)) { + break; + } + } + } + } + + /** + * Helper function for iterating over an array backwards. If the func + * returns a true value, it will break out of the loop. + */ + function eachReverse(ary, func) { + if (ary) { + var i; + for (i = ary.length - 1; i > -1; i -= 1) { + if (ary[i] && func(ary[i], i, ary)) { + break; + } + } + } + } + + function hasProp(obj, prop) { + return hasOwn.call(obj, prop); + } + + function getOwn(obj, prop) { + return hasProp(obj, prop) && obj[prop]; + } + + /** + * Cycles over properties in an object and calls a function for each + * property value. If the function returns a truthy value, then the + * iteration is stopped. + */ + function eachProp(obj, func) { + var prop; + for (prop in obj) { + if (hasProp(obj, prop)) { + if (func(obj[prop], prop)) { + break; + } + } + } + } + + /** + * Simple function to mix in properties from source into target, + * but only if target does not already have a property of the same name. + */ + function mixin(target, source, force, deepStringMixin) { + if (source) { + eachProp(source, function (value, prop) { + if (force || !hasProp(target, prop)) { + if (deepStringMixin && typeof value !== 'string') { + if (!target[prop]) { + target[prop] = {}; + } + mixin(target[prop], value, force, deepStringMixin); + } else { + target[prop] = value; + } + } + }); + } + return target; + } + + //Similar to Function.prototype.bind, but the 'this' object is specified + //first, since it is easier to read/figure out what 'this' will be. + function bind(obj, fn) { + return function () { + return fn.apply(obj, arguments); + }; + } + + function scripts() { + return document.getElementsByTagName('script'); + } + + //Allow getting a global that expressed in + //dot notation, like 'a.b.c'. + function getGlobal(value) { + if (!value) { + return value; + } + var g = global; + each(value.split('.'), function (part) { + g = g[part]; + }); + return g; + } + + /** + * Constructs an error with a pointer to an URL with more information. + * @param {String} id the error ID that maps to an ID on a web page. + * @param {String} message human readable error. + * @param {Error} [err] the original error, if there is one. + * + * @returns {Error} + */ + function makeError(id, msg, err, requireModules) { + var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id); + e.requireType = id; + e.requireModules = requireModules; + if (err) { + e.originalError = err; + } + return e; + } + + if (typeof define !== 'undefined') { + //If a define is already in play via another AMD loader, + //do not overwrite. + return; + } + + if (typeof requirejs !== 'undefined') { + if (isFunction(requirejs)) { + //Do not overwrite and existing requirejs instance. + return; + } + cfg = requirejs; + requirejs = undefined; + } + + //Allow for a require config object + if (typeof require !== 'undefined' && !isFunction(require)) { + //assume it is a config object. + cfg = require; + require = undefined; + } + + function newContext(contextName) { + var inCheckLoaded, Module, context, handlers, + checkLoadedTimeoutId, + config = { + waitSeconds: 7, + baseUrl: './', + paths: {}, + pkgs: {}, + shim: {}, + map: {}, + config: {} + }, + registry = {}, + undefEvents = {}, + defQueue = [], + defined = {}, + urlFetched = {}, + requireCounter = 1, + unnormalizedCounter = 1; + + /** + * Trims the . and .. from an array of path segments. + * It will keep a leading path segment if a .. will become + * the first path segment, to help with module name lookups, + * which act like paths, but can be remapped. But the end result, + * all paths that use this function should look normalized. + * NOTE: this method MODIFIES the input array. + * @param {Array} ary the array of path segments. + */ + function trimDots(ary) { + var i, part; + for (i = 0; ary[i]; i += 1) { + part = ary[i]; + if (part === '.') { + ary.splice(i, 1); + i -= 1; + } else if (part === '..') { + if (i === 1 && (ary[2] === '..' || ary[0] === '..')) { + //End of the line. Keep at least one non-dot + //path segment at the front so it can be mapped + //correctly to disk. Otherwise, there is likely + //no path mapping for a path starting with '..'. + //This can still fail, but catches the most reasonable + //uses of .. + break; + } else if (i > 0) { + ary.splice(i - 1, 2); + i -= 2; + } + } + } + } + + /** + * Given a relative module name, like ./something, normalize it to + * a real name that can be mapped to a path. + * @param {String} name the relative name + * @param {String} baseName a real name that the name arg is relative + * to. + * @param {Boolean} applyMap apply the map config to the value. Should + * only be done if this normalization is for a dependency ID. + * @returns {String} normalized name + */ + function normalize(name, baseName, applyMap) { + var pkgName, pkgConfig, mapValue, nameParts, i, j, nameSegment, + foundMap, foundI, foundStarMap, starI, + baseParts = baseName && baseName.split('/'), + normalizedBaseParts = baseParts, + map = config.map, + starMap = map && map['*']; + + //Adjust any relative paths. + if (name && name.charAt(0) === '.') { + //If have a base name, try to normalize against it, + //otherwise, assume it is a top-level require that will + //be relative to baseUrl in the end. + if (baseName) { + if (getOwn(config.pkgs, baseName)) { + //If the baseName is a package name, then just treat it as one + //name to concat the name with. + normalizedBaseParts = baseParts = [baseName]; + } else { + //Convert baseName to array, and lop off the last part, + //so that . matches that 'directory' and not name of the baseName's + //module. For instance, baseName of 'one/two/three', maps to + //'one/two/three.js', but we want the directory, 'one/two' for + //this normalization. + normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); + } + + name = normalizedBaseParts.concat(name.split('/')); + trimDots(name); + + //Some use of packages may use a . path to reference the + //'main' module name, so normalize for that. + pkgConfig = getOwn(config.pkgs, (pkgName = name[0])); + name = name.join('/'); + if (pkgConfig && name === pkgName + '/' + pkgConfig.main) { + name = pkgName; + } + } else if (name.indexOf('./') === 0) { + // No baseName, so this is ID is resolved relative + // to baseUrl, pull off the leading dot. + name = name.substring(2); + } + } + + //Apply map config if available. + if (applyMap && (baseParts || starMap) && map) { + nameParts = name.split('/'); + + for (i = nameParts.length; i > 0; i -= 1) { + nameSegment = nameParts.slice(0, i).join('/'); + + if (baseParts) { + //Find the longest baseName segment match in the config. + //So, do joins on the biggest to smallest lengths of baseParts. + for (j = baseParts.length; j > 0; j -= 1) { + mapValue = getOwn(map, baseParts.slice(0, j).join('/')); + + //baseName segment has config, find if it has one for + //this name. + if (mapValue) { + mapValue = getOwn(mapValue, nameSegment); + if (mapValue) { + //Match, update name to the new value. + foundMap = mapValue; + foundI = i; + break; + } + } + } + } + + if (foundMap) { + break; + } + + //Check for a star map match, but just hold on to it, + //if there is a shorter segment match later in a matching + //config, then favor over this star map. + if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) { + foundStarMap = getOwn(starMap, nameSegment); + starI = i; + } + } + + if (!foundMap && foundStarMap) { + foundMap = foundStarMap; + foundI = starI; + } + + if (foundMap) { + nameParts.splice(0, foundI, foundMap); + name = nameParts.join('/'); + } + } + + return name; + } + + function removeScript(name) { + if (isBrowser) { + each(scripts(), function (scriptNode) { + if (scriptNode.getAttribute('data-requiremodule') === name && + scriptNode.getAttribute('data-requirecontext') === context.contextName) { + scriptNode.parentNode.removeChild(scriptNode); + return true; + } + }); + } + } + + function hasPathFallback(id) { + var pathConfig = getOwn(config.paths, id); + if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) { + removeScript(id); + //Pop off the first array value, since it failed, and + //retry + pathConfig.shift(); + context.require.undef(id); + context.require([id]); + return true; + } + } + + //Turns a plugin!resource to [plugin, resource] + //with the plugin being undefined if the name + //did not have a plugin prefix. + function splitPrefix(name) { + var prefix, + index = name ? name.indexOf('!') : -1; + if (index > -1) { + prefix = name.substring(0, index); + name = name.substring(index + 1, name.length); + } + return [prefix, name]; + } + + /** + * Creates a module mapping that includes plugin prefix, module + * name, and path. If parentModuleMap is provided it will + * also normalize the name via require.normalize() + * + * @param {String} name the module name + * @param {String} [parentModuleMap] parent module map + * for the module name, used to resolve relative names. + * @param {Boolean} isNormalized: is the ID already normalized. + * This is true if this call is done for a define() module ID. + * @param {Boolean} applyMap: apply the map config to the ID. + * Should only be true if this map is for a dependency. + * + * @returns {Object} + */ + function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) { + var url, pluginModule, suffix, nameParts, + prefix = null, + parentName = parentModuleMap ? parentModuleMap.name : null, + originalName = name, + isDefine = true, + normalizedName = ''; + + //If no name, then it means it is a require call, generate an + //internal name. + if (!name) { + isDefine = false; + name = '_@r' + (requireCounter += 1); + } + + nameParts = splitPrefix(name); + prefix = nameParts[0]; + name = nameParts[1]; + + if (prefix) { + prefix = normalize(prefix, parentName, applyMap); + pluginModule = getOwn(defined, prefix); + } + + //Account for relative paths if there is a base name. + if (name) { + if (prefix) { + if (pluginModule && pluginModule.normalize) { + //Plugin is loaded, use its normalize method. + normalizedName = pluginModule.normalize(name, function (name) { + return normalize(name, parentName, applyMap); + }); + } else { + normalizedName = normalize(name, parentName, applyMap); + } + } else { + //A regular module. + normalizedName = normalize(name, parentName, applyMap); + + //Normalized name may be a plugin ID due to map config + //application in normalize. The map config values must + //already be normalized, so do not need to redo that part. + nameParts = splitPrefix(normalizedName); + prefix = nameParts[0]; + normalizedName = nameParts[1]; + isNormalized = true; + + url = context.nameToUrl(normalizedName); + } + } + + //If the id is a plugin id that cannot be determined if it needs + //normalization, stamp it with a unique ID so two matching relative + //ids that may conflict can be separate. + suffix = prefix && !pluginModule && !isNormalized ? + '_unnormalized' + (unnormalizedCounter += 1) : + ''; + + return { + prefix: prefix, + name: normalizedName, + parentMap: parentModuleMap, + unnormalized: !!suffix, + url: url, + originalName: originalName, + isDefine: isDefine, + id: (prefix ? + prefix + '!' + normalizedName : + normalizedName) + suffix + }; + } + + function getModule(depMap) { + var id = depMap.id, + mod = getOwn(registry, id); + + if (!mod) { + mod = registry[id] = new context.Module(depMap); + } + + return mod; + } + + function on(depMap, name, fn) { + var id = depMap.id, + mod = getOwn(registry, id); + + if (hasProp(defined, id) && + (!mod || mod.defineEmitComplete)) { + if (name === 'defined') { + fn(defined[id]); + } + } else { + getModule(depMap).on(name, fn); + } + } + + function onError(err, errback) { + var ids = err.requireModules, + notified = false; + + if (errback) { + errback(err); + } else { + each(ids, function (id) { + var mod = getOwn(registry, id); + if (mod) { + //Set error on module, so it skips timeout checks. + mod.error = err; + if (mod.events.error) { + notified = true; + mod.emit('error', err); + } + } + }); + + if (!notified) { + req.onError(err); + } + } + } + + /** + * Internal method to transfer globalQueue items to this context's + * defQueue. + */ + function takeGlobalQueue() { + //Push all the globalDefQueue items into the context's defQueue + if (globalDefQueue.length) { + //Array splice in the values since the context code has a + //local var ref to defQueue, so cannot just reassign the one + //on context. + apsp.apply(defQueue, + [defQueue.length - 1, 0].concat(globalDefQueue)); + globalDefQueue = []; + } + } + + handlers = { + 'require': function (mod) { + if (mod.require) { + return mod.require; + } else { + return (mod.require = context.makeRequire(mod.map)); + } + }, + 'exports': function (mod) { + mod.usingExports = true; + if (mod.map.isDefine) { + if (mod.exports) { + return mod.exports; + } else { + return (mod.exports = defined[mod.map.id] = {}); + } + } + }, + 'module': function (mod) { + if (mod.module) { + return mod.module; + } else { + return (mod.module = { + id: mod.map.id, + uri: mod.map.url, + config: function () { + return (config.config && getOwn(config.config, mod.map.id)) || {}; + }, + exports: defined[mod.map.id] + }); + } + } + }; + + function cleanRegistry(id) { + //Clean up machinery used for waiting modules. + delete registry[id]; + } + + function breakCycle(mod, traced, processed) { + var id = mod.map.id; + + if (mod.error) { + mod.emit('error', mod.error); + } else { + traced[id] = true; + each(mod.depMaps, function (depMap, i) { + var depId = depMap.id, + dep = getOwn(registry, depId); + + //Only force things that have not completed + //being defined, so still in the registry, + //and only if it has not been matched up + //in the module already. + if (dep && !mod.depMatched[i] && !processed[depId]) { + if (getOwn(traced, depId)) { + mod.defineDep(i, defined[depId]); + mod.check(); //pass false? + } else { + breakCycle(dep, traced, processed); + } + } + }); + processed[id] = true; + } + } + + function checkLoaded() { + var map, modId, err, usingPathFallback, + waitInterval = config.waitSeconds * 1000, + //It is possible to disable the wait interval by using waitSeconds of 0. + expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(), + noLoads = [], + reqCalls = [], + stillLoading = false, + needCycleCheck = true; + + //Do not bother if this call was a result of a cycle break. + if (inCheckLoaded) { + return; + } + + inCheckLoaded = true; + + //Figure out the state of all the modules. + eachProp(registry, function (mod) { + map = mod.map; + modId = map.id; + + //Skip things that are not enabled or in error state. + if (!mod.enabled) { + return; + } + + if (!map.isDefine) { + reqCalls.push(mod); + } + + if (!mod.error) { + //If the module should be executed, and it has not + //been inited and time is up, remember it. + if (!mod.inited && expired) { + if (hasPathFallback(modId)) { + usingPathFallback = true; + stillLoading = true; + } else { + noLoads.push(modId); + removeScript(modId); + } + } else if (!mod.inited && mod.fetched && map.isDefine) { + stillLoading = true; + if (!map.prefix) { + //No reason to keep looking for unfinished + //loading. If the only stillLoading is a + //plugin resource though, keep going, + //because it may be that a plugin resource + //is waiting on a non-plugin cycle. + return (needCycleCheck = false); + } + } + } + }); + + if (expired && noLoads.length) { + //If wait time expired, throw error of unloaded modules. + err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads); + err.contextName = context.contextName; + return onError(err); + } + + //Not expired, check for a cycle. + if (needCycleCheck) { + each(reqCalls, function (mod) { + breakCycle(mod, {}, {}); + }); + } + + //If still waiting on loads, and the waiting load is something + //other than a plugin resource, or there are still outstanding + //scripts, then just try back later. + if ((!expired || usingPathFallback) && stillLoading) { + //Something is still waiting to load. Wait for it, but only + //if a timeout is not already in effect. + if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) { + checkLoadedTimeoutId = setTimeout(function () { + checkLoadedTimeoutId = 0; + checkLoaded(); + }, 50); + } + } + + inCheckLoaded = false; + } + + Module = function (map) { + this.events = getOwn(undefEvents, map.id) || {}; + this.map = map; + this.shim = getOwn(config.shim, map.id); + this.depExports = []; + this.depMaps = []; + this.depMatched = []; + this.pluginMaps = {}; + this.depCount = 0; + + /* this.exports this.factory + this.depMaps = [], + this.enabled, this.fetched + */ + }; + + Module.prototype = { + init: function (depMaps, factory, errback, options) { + options = options || {}; + + //Do not do more inits if already done. Can happen if there + //are multiple define calls for the same module. That is not + //a normal, common case, but it is also not unexpected. + if (this.inited) { + return; + } + + this.factory = factory; + + if (errback) { + //Register for errors on this module. + this.on('error', errback); + } else if (this.events.error) { + //If no errback already, but there are error listeners + //on this module, set up an errback to pass to the deps. + errback = bind(this, function (err) { + this.emit('error', err); + }); + } + + //Do a copy of the dependency array, so that + //source inputs are not modified. For example + //"shim" deps are passed in here directly, and + //doing a direct modification of the depMaps array + //would affect that config. + this.depMaps = depMaps && depMaps.slice(0); + + this.errback = errback; + + //Indicate this module has be initialized + this.inited = true; + + this.ignore = options.ignore; + + //Could have option to init this module in enabled mode, + //or could have been previously marked as enabled. However, + //the dependencies are not known until init is called. So + //if enabled previously, now trigger dependencies as enabled. + if (options.enabled || this.enabled) { + //Enable this module and dependencies. + //Will call this.check() + this.enable(); + } else { + this.check(); + } + }, + + defineDep: function (i, depExports) { + //Because of cycles, defined callback for a given + //export can be called more than once. + if (!this.depMatched[i]) { + this.depMatched[i] = true; + this.depCount -= 1; + this.depExports[i] = depExports; + } + }, + + fetch: function () { + if (this.fetched) { + return; + } + this.fetched = true; + + context.startTime = (new Date()).getTime(); + + var map = this.map; + + //If the manager is for a plugin managed resource, + //ask the plugin to load it now. + if (this.shim) { + context.makeRequire(this.map, { + enableBuildCallback: true + })(this.shim.deps || [], bind(this, function () { + return map.prefix ? this.callPlugin() : this.load(); + })); + } else { + //Regular dependency. + return map.prefix ? this.callPlugin() : this.load(); + } + }, + + load: function () { + var url = this.map.url; + + //Regular dependency. + if (!urlFetched[url]) { + urlFetched[url] = true; + context.load(this.map.id, url); + } + }, + + /** + * Checks is the module is ready to define itself, and if so, + * define it. + */ + check: function () { + if (!this.enabled || this.enabling) { + return; + } + + var err, cjsModule, + id = this.map.id, + depExports = this.depExports, + exports = this.exports, + factory = this.factory; + + if (!this.inited) { + this.fetch(); + } else if (this.error) { + this.emit('error', this.error); + } else if (!this.defining) { + //The factory could trigger another require call + //that would result in checking this module to + //define itself again. If already in the process + //of doing that, skip this work. + this.defining = true; + + if (this.depCount < 1 && !this.defined) { + if (isFunction(factory)) { + //If there is an error listener, favor passing + //to that instead of throwing an error. + if (this.events.error) { + try { + exports = context.execCb(id, factory, depExports, exports); + } catch (e) { + err = e; + } + } else { + exports = context.execCb(id, factory, depExports, exports); + } + + if (this.map.isDefine) { + //If setting exports via 'module' is in play, + //favor that over return value and exports. After that, + //favor a non-undefined return value over exports use. + cjsModule = this.module; + if (cjsModule && + cjsModule.exports !== undefined && + //Make sure it is not already the exports value + cjsModule.exports !== this.exports) { + exports = cjsModule.exports; + } else if (exports === undefined && this.usingExports) { + //exports already set the defined value. + exports = this.exports; + } + } + + if (err) { + err.requireMap = this.map; + err.requireModules = [this.map.id]; + err.requireType = 'define'; + return onError((this.error = err)); + } + + } else { + //Just a literal value + exports = factory; + } + + this.exports = exports; + + if (this.map.isDefine && !this.ignore) { + defined[id] = exports; + + if (req.onResourceLoad) { + req.onResourceLoad(context, this.map, this.depMaps); + } + } + + //Clean up + delete registry[id]; + + this.defined = true; + } + + //Finished the define stage. Allow calling check again + //to allow define notifications below in the case of a + //cycle. + this.defining = false; + + if (this.defined && !this.defineEmitted) { + this.defineEmitted = true; + this.emit('defined', this.exports); + this.defineEmitComplete = true; + } + + } + }, + + callPlugin: function () { + var map = this.map, + id = map.id, + //Map already normalized the prefix. + pluginMap = makeModuleMap(map.prefix); + + //Mark this as a dependency for this plugin, so it + //can be traced for cycles. + this.depMaps.push(pluginMap); + + on(pluginMap, 'defined', bind(this, function (plugin) { + var load, normalizedMap, normalizedMod, + name = this.map.name, + parentName = this.map.parentMap ? this.map.parentMap.name : null, + localRequire = context.makeRequire(map.parentMap, { + enableBuildCallback: true + }); + + //If current map is not normalized, wait for that + //normalized name to load instead of continuing. + if (this.map.unnormalized) { + //Normalize the ID if the plugin allows it. + if (plugin.normalize) { + name = plugin.normalize(name, function (name) { + return normalize(name, parentName, true); + }) || ''; + } + + //prefix and name should already be normalized, no need + //for applying map config again either. + normalizedMap = makeModuleMap(map.prefix + '!' + name, + this.map.parentMap); + on(normalizedMap, + 'defined', bind(this, function (value) { + this.init([], function () { return value; }, null, { + enabled: true, + ignore: true + }); + })); + + normalizedMod = getOwn(registry, normalizedMap.id); + if (normalizedMod) { + //Mark this as a dependency for this plugin, so it + //can be traced for cycles. + this.depMaps.push(normalizedMap); + + if (this.events.error) { + normalizedMod.on('error', bind(this, function (err) { + this.emit('error', err); + })); + } + normalizedMod.enable(); + } + + return; + } + + load = bind(this, function (value) { + this.init([], function () { return value; }, null, { + enabled: true + }); + }); + + load.error = bind(this, function (err) { + this.inited = true; + this.error = err; + err.requireModules = [id]; + + //Remove temp unnormalized modules for this module, + //since they will never be resolved otherwise now. + eachProp(registry, function (mod) { + if (mod.map.id.indexOf(id + '_unnormalized') === 0) { + cleanRegistry(mod.map.id); + } + }); + + onError(err); + }); + + //Allow plugins to load other code without having to know the + //context or how to 'complete' the load. + load.fromText = bind(this, function (text, textAlt) { + /*jslint evil: true */ + var moduleName = map.name, + moduleMap = makeModuleMap(moduleName), + hasInteractive = useInteractive; + + //As of 2.1.0, support just passing the text, to reinforce + //fromText only being called once per resource. Still + //support old style of passing moduleName but discard + //that moduleName in favor of the internal ref. + if (textAlt) { + text = textAlt; + } + + //Turn off interactive script matching for IE for any define + //calls in the text, then turn it back on at the end. + if (hasInteractive) { + useInteractive = false; + } + + //Prime the system by creating a module instance for + //it. + getModule(moduleMap); + + //Transfer any config to this other module. + if (hasProp(config.config, id)) { + config.config[moduleName] = config.config[id]; + } + + try { + req.exec(text); + } catch (e) { + return onError(makeError('fromtexteval', + 'fromText eval for ' + id + + ' failed: ' + e, + e, + [id])); + } + + if (hasInteractive) { + useInteractive = true; + } + + //Mark this as a dependency for the plugin + //resource + this.depMaps.push(moduleMap); + + //Support anonymous modules. + context.completeLoad(moduleName); + + //Bind the value of that module to the value for this + //resource ID. + localRequire([moduleName], load); + }); + + //Use parentName here since the plugin's name is not reliable, + //could be some weird string with no path that actually wants to + //reference the parentName's path. + plugin.load(map.name, localRequire, load, config); + })); + + context.enable(pluginMap, this); + this.pluginMaps[pluginMap.id] = pluginMap; + }, + + enable: function () { + this.enabled = true; + + //Set flag mentioning that the module is enabling, + //so that immediate calls to the defined callbacks + //for dependencies do not trigger inadvertent load + //with the depCount still being zero. + this.enabling = true; + + //Enable each dependency + each(this.depMaps, bind(this, function (depMap, i) { + var id, mod, handler; + + if (typeof depMap === 'string') { + //Dependency needs to be converted to a depMap + //and wired up to this module. + depMap = makeModuleMap(depMap, + (this.map.isDefine ? this.map : this.map.parentMap), + false, + !this.skipMap); + this.depMaps[i] = depMap; + + handler = getOwn(handlers, depMap.id); + + if (handler) { + this.depExports[i] = handler(this); + return; + } + + this.depCount += 1; + + on(depMap, 'defined', bind(this, function (depExports) { + this.defineDep(i, depExports); + this.check(); + })); + + if (this.errback) { + on(depMap, 'error', this.errback); + } + } + + id = depMap.id; + mod = registry[id]; + + //Skip special modules like 'require', 'exports', 'module' + //Also, don't call enable if it is already enabled, + //important in circular dependency cases. + if (!hasProp(handlers, id) && mod && !mod.enabled) { + context.enable(depMap, this); + } + })); + + //Enable each plugin that is used in + //a dependency + eachProp(this.pluginMaps, bind(this, function (pluginMap) { + var mod = getOwn(registry, pluginMap.id); + if (mod && !mod.enabled) { + context.enable(pluginMap, this); + } + })); + + this.enabling = false; + + this.check(); + }, + + on: function (name, cb) { + var cbs = this.events[name]; + if (!cbs) { + cbs = this.events[name] = []; + } + cbs.push(cb); + }, + + emit: function (name, evt) { + each(this.events[name], function (cb) { + cb(evt); + }); + if (name === 'error') { + //Now that the error handler was triggered, remove + //the listeners, since this broken Module instance + //can stay around for a while in the registry. + delete this.events[name]; + } + } + }; + + function callGetModule(args) { + //Skip modules already defined. + if (!hasProp(defined, args[0])) { + getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]); + } + } + + function removeListener(node, func, name, ieName) { + //Favor detachEvent because of IE9 + //issue, see attachEvent/addEventListener comment elsewhere + //in this file. + if (node.detachEvent && !isOpera) { + //Probably IE. If not it will throw an error, which will be + //useful to know. + if (ieName) { + node.detachEvent(ieName, func); + } + } else { + node.removeEventListener(name, func, false); + } + } + + /** + * Given an event from a script node, get the requirejs info from it, + * and then removes the event listeners on the node. + * @param {Event} evt + * @returns {Object} + */ + function getScriptData(evt) { + //Using currentTarget instead of target for Firefox 2.0's sake. Not + //all old browsers will be supported, but this one was easy enough + //to support and still makes sense. + var node = evt.currentTarget || evt.srcElement; + + //Remove the listeners once here. + removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange'); + removeListener(node, context.onScriptError, 'error'); + + return { + node: node, + id: node && node.getAttribute('data-requiremodule') + }; + } + + function intakeDefines() { + var args; + + //Any defined modules in the global queue, intake them now. + takeGlobalQueue(); + + //Make sure any remaining defQueue items get properly processed. + while (defQueue.length) { + args = defQueue.shift(); + if (args[0] === null) { + return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1])); + } else { + //args are id, deps, factory. Should be normalized by the + //define() function. + callGetModule(args); + } + } + } + + context = { + config: config, + contextName: contextName, + registry: registry, + defined: defined, + urlFetched: urlFetched, + defQueue: defQueue, + Module: Module, + makeModuleMap: makeModuleMap, + nextTick: req.nextTick, + + /** + * Set a configuration for the context. + * @param {Object} cfg config object to integrate. + */ + configure: function (cfg) { + //Make sure the baseUrl ends in a slash. + if (cfg.baseUrl) { + if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') { + cfg.baseUrl += '/'; + } + } + + //Save off the paths and packages since they require special processing, + //they are additive. + var pkgs = config.pkgs, + shim = config.shim, + objs = { + paths: true, + config: true, + map: true + }; + + eachProp(cfg, function (value, prop) { + if (objs[prop]) { + if (prop === 'map') { + mixin(config[prop], value, true, true); + } else { + mixin(config[prop], value, true); + } + } else { + config[prop] = value; + } + }); + + //Merge shim + if (cfg.shim) { + eachProp(cfg.shim, function (value, id) { + //Normalize the structure + if (isArray(value)) { + value = { + deps: value + }; + } + if ((value.exports || value.init) && !value.exportsFn) { + value.exportsFn = context.makeShimExports(value); + } + shim[id] = value; + }); + config.shim = shim; + } + + //Adjust packages if necessary. + if (cfg.packages) { + each(cfg.packages, function (pkgObj) { + var location; + + pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj; + location = pkgObj.location; + + //Create a brand new object on pkgs, since currentPackages can + //be passed in again, and config.pkgs is the internal transformed + //state for all package configs. + pkgs[pkgObj.name] = { + name: pkgObj.name, + location: location || pkgObj.name, + //Remove leading dot in main, so main paths are normalized, + //and remove any trailing .js, since different package + //envs have different conventions: some use a module name, + //some use a file name. + main: (pkgObj.main || 'main') + .replace(currDirRegExp, '') + .replace(jsSuffixRegExp, '') + }; + }); + + //Done with modifications, assing packages back to context config + config.pkgs = pkgs; + } + + //If there are any "waiting to execute" modules in the registry, + //update the maps for them, since their info, like URLs to load, + //may have changed. + eachProp(registry, function (mod, id) { + //If module already has init called, since it is too + //late to modify them, and ignore unnormalized ones + //since they are transient. + if (!mod.inited && !mod.map.unnormalized) { + mod.map = makeModuleMap(id); + } + }); + + //If a deps array or a config callback is specified, then call + //require with those args. This is useful when require is defined as a + //config object before require.js is loaded. + if (cfg.deps || cfg.callback) { + context.require(cfg.deps || [], cfg.callback); + } + }, + + makeShimExports: function (value) { + function fn() { + var ret; + if (value.init) { + ret = value.init.apply(global, arguments); + } + return ret || (value.exports && getGlobal(value.exports)); + } + return fn; + }, + + makeRequire: function (relMap, options) { + options = options || {}; + + function localRequire(deps, callback, errback) { + var id, map, requireMod; + + if (options.enableBuildCallback && callback && isFunction(callback)) { + callback.__requireJsBuild = true; + } + + if (typeof deps === 'string') { + if (isFunction(callback)) { + //Invalid call + return onError(makeError('requireargs', 'Invalid require call'), errback); + } + + //If require|exports|module are requested, get the + //value for them from the special handlers. Caveat: + //this only works while module is being defined. + if (relMap && hasProp(handlers, deps)) { + return handlers[deps](registry[relMap.id]); + } + + //Synchronous access to one module. If require.get is + //available (as in the Node adapter), prefer that. + if (req.get) { + return req.get(context, deps, relMap); + } + + //Normalize module name, if it contains . or .. + map = makeModuleMap(deps, relMap, false, true); + id = map.id; + + if (!hasProp(defined, id)) { + return onError(makeError('notloaded', 'Module name "' + + id + + '" has not been loaded yet for context: ' + + contextName + + (relMap ? '' : '. Use require([])'))); + } + return defined[id]; + } + + //Grab defines waiting in the global queue. + intakeDefines(); + + //Mark all the dependencies as needing to be loaded. + context.nextTick(function () { + //Some defines could have been added since the + //require call, collect them. + intakeDefines(); + + requireMod = getModule(makeModuleMap(null, relMap)); + + //Store if map config should be applied to this require + //call for dependencies. + requireMod.skipMap = options.skipMap; + + requireMod.init(deps, callback, errback, { + enabled: true + }); + + checkLoaded(); + }); + + return localRequire; + } + + mixin(localRequire, { + isBrowser: isBrowser, + + /** + * Converts a module name + .extension into an URL path. + * *Requires* the use of a module name. It does not support using + * plain URLs like nameToUrl. + */ + toUrl: function (moduleNamePlusExt) { + var ext, url, + index = moduleNamePlusExt.lastIndexOf('.'), + segment = moduleNamePlusExt.split('/')[0], + isRelative = segment === '.' || segment === '..'; + + //Have a file extension alias, and it is not the + //dots from a relative path. + if (index !== -1 && (!isRelative || index > 1)) { + ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length); + moduleNamePlusExt = moduleNamePlusExt.substring(0, index); + } + + url = context.nameToUrl(normalize(moduleNamePlusExt, + relMap && relMap.id, true), ext || '.fake'); + return ext ? url : url.substring(0, url.length - 5); + }, + + defined: function (id) { + return hasProp(defined, makeModuleMap(id, relMap, false, true).id); + }, + + specified: function (id) { + id = makeModuleMap(id, relMap, false, true).id; + return hasProp(defined, id) || hasProp(registry, id); + } + }); + + //Only allow undef on top level require calls + if (!relMap) { + localRequire.undef = function (id) { + //Bind any waiting define() calls to this context, + //fix for #408 + takeGlobalQueue(); + + var map = makeModuleMap(id, relMap, true), + mod = getOwn(registry, id); + + delete defined[id]; + delete urlFetched[map.url]; + delete undefEvents[id]; + + if (mod) { + //Hold on to listeners in case the + //module will be attempted to be reloaded + //using a different config. + if (mod.events.defined) { + undefEvents[id] = mod.events; + } + + cleanRegistry(id); + } + }; + } + + return localRequire; + }, + + /** + * Called to enable a module if it is still in the registry + * awaiting enablement. A second arg, parent, the parent module, + * is passed in for context, when this method is overriden by + * the optimizer. Not shown here to keep code compact. + */ + enable: function (depMap) { + var mod = getOwn(registry, depMap.id); + if (mod) { + getModule(depMap).enable(); + } + }, + + /** + * Internal method used by environment adapters to complete a load event. + * A load event could be a script load or just a load pass from a synchronous + * load call. + * @param {String} moduleName the name of the module to potentially complete. + */ + completeLoad: function (moduleName) { + var found, args, mod, + shim = getOwn(config.shim, moduleName) || {}, + shExports = shim.exports; + + takeGlobalQueue(); + + while (defQueue.length) { + args = defQueue.shift(); + if (args[0] === null) { + args[0] = moduleName; + //If already found an anonymous module and bound it + //to this name, then this is some other anon module + //waiting for its completeLoad to fire. + if (found) { + break; + } + found = true; + } else if (args[0] === moduleName) { + //Found matching define call for this script! + found = true; + } + + callGetModule(args); + } + + //Do this after the cycle of callGetModule in case the result + //of those calls/init calls changes the registry. + mod = getOwn(registry, moduleName); + + if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) { + if (config.enforceDefine && (!shExports || !getGlobal(shExports))) { + if (hasPathFallback(moduleName)) { + return; + } else { + return onError(makeError('nodefine', + 'No define call for ' + moduleName, + null, + [moduleName])); + } + } else { + //A script that does not call define(), so just simulate + //the call for it. + callGetModule([moduleName, (shim.deps || []), shim.exportsFn]); + } + } + + checkLoaded(); + }, + + /** + * Converts a module name to a file path. Supports cases where + * moduleName may actually be just an URL. + * Note that it **does not** call normalize on the moduleName, + * it is assumed to have already been normalized. This is an + * internal API, not a public one. Use toUrl for the public API. + */ + nameToUrl: function (moduleName, ext) { + var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url, + parentPath; + + //If a colon is in the URL, it indicates a protocol is used and it is just + //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?) + //or ends with .js, then assume the user meant to use an url and not a module id. + //The slash is important for protocol-less URLs as well as full paths. + if (req.jsExtRegExp.test(moduleName)) { + //Just a plain path, not module name lookup, so just return it. + //Add extension if it is included. This is a bit wonky, only non-.js things pass + //an extension, this method probably needs to be reworked. + url = moduleName + (ext || ''); + } else { + //A module that needs to be converted to a path. + paths = config.paths; + pkgs = config.pkgs; + + syms = moduleName.split('/'); + //For each module name segment, see if there is a path + //registered for it. Start with most specific name + //and work up from it. + for (i = syms.length; i > 0; i -= 1) { + parentModule = syms.slice(0, i).join('/'); + pkg = getOwn(pkgs, parentModule); + parentPath = getOwn(paths, parentModule); + if (parentPath) { + //If an array, it means there are a few choices, + //Choose the one that is desired + if (isArray(parentPath)) { + parentPath = parentPath[0]; + } + syms.splice(0, i, parentPath); + break; + } else if (pkg) { + //If module name is just the package name, then looking + //for the main module. + if (moduleName === pkg.name) { + pkgPath = pkg.location + '/' + pkg.main; + } else { + pkgPath = pkg.location; + } + syms.splice(0, i, pkgPath); + break; + } + } + + //Join the path parts together, then figure out if baseUrl is needed. + url = syms.join('/'); + url += (ext || (/\?/.test(url) ? '' : '.js')); + url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url; + } + + return config.urlArgs ? url + + ((url.indexOf('?') === -1 ? '?' : '&') + + config.urlArgs) : url; + }, + + //Delegates to req.load. Broken out as a separate function to + //allow overriding in the optimizer. + load: function (id, url) { + req.load(context, id, url); + }, + + /** + * Executes a module callack function. Broken out as a separate function + * solely to allow the build system to sequence the files in the built + * layer in the right sequence. + * + * @private + */ + execCb: function (name, callback, args, exports) { + return callback.apply(exports, args); + }, + + /** + * callback for script loads, used to check status of loading. + * + * @param {Event} evt the event from the browser for the script + * that was loaded. + */ + onScriptLoad: function (evt) { + //Using currentTarget instead of target for Firefox 2.0's sake. Not + //all old browsers will be supported, but this one was easy enough + //to support and still makes sense. + if (evt.type === 'load' || + (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) { + //Reset interactive script so a script node is not held onto for + //to long. + interactiveScript = null; + + //Pull out the name of the module and the context. + var data = getScriptData(evt); + context.completeLoad(data.id); + } + }, + + /** + * Callback for script errors. + */ + onScriptError: function (evt) { + var data = getScriptData(evt); + if (!hasPathFallback(data.id)) { + return onError(makeError('scripterror', 'Script error', evt, [data.id])); + } + } + }; + + context.require = context.makeRequire(); + return context; + } + + /** + * Main entry point. + * + * If the only argument to require is a string, then the module that + * is represented by that string is fetched for the appropriate context. + * + * If the first argument is an array, then it will be treated as an array + * of dependency string names to fetch. An optional function callback can + * be specified to execute when all of those dependencies are available. + * + * Make a local req variable to help Caja compliance (it assumes things + * on a require that are not standardized), and to give a short + * name for minification/local scope use. + */ + req = requirejs = function (deps, callback, errback, optional) { + + //Find the right context, use default + var context, config, + contextName = defContextName; + + // Determine if have config object in the call. + if (!isArray(deps) && typeof deps !== 'string') { + // deps is a config object + config = deps; + if (isArray(callback)) { + // Adjust args if there are dependencies + deps = callback; + callback = errback; + errback = optional; + } else { + deps = []; + } + } + + if (config && config.context) { + contextName = config.context; + } + + context = getOwn(contexts, contextName); + if (!context) { + context = contexts[contextName] = req.s.newContext(contextName); + } + + if (config) { + context.configure(config); + } + + return context.require(deps, callback, errback); + }; + + /** + * Support require.config() to make it easier to cooperate with other + * AMD loaders on globally agreed names. + */ + req.config = function (config) { + return req(config); + }; + + /** + * Execute something after the current tick + * of the event loop. Override for other envs + * that have a better solution than setTimeout. + * @param {Function} fn function to execute later. + */ + req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) { + setTimeout(fn, 4); + } : function (fn) { fn(); }; + + /** + * Export require as a global, but only if it does not already exist. + */ + if (!require) { + require = req; + } + + req.version = version; + + //Used to filter out dependencies that are already paths. + req.jsExtRegExp = /^\/|:|\?|\.js$/; + req.isBrowser = isBrowser; + s = req.s = { + contexts: contexts, + newContext: newContext + }; + + //Create default context. + req({}); + + //Exports some context-sensitive methods on global require. + each([ + 'toUrl', + 'undef', + 'defined', + 'specified' + ], function (prop) { + //Reference from contexts instead of early binding to default context, + //so that during builds, the latest instance of the default context + //with its config gets used. + req[prop] = function () { + var ctx = contexts[defContextName]; + return ctx.require[prop].apply(ctx, arguments); + }; + }); + + if (isBrowser) { + head = s.head = document.getElementsByTagName('head')[0]; + //If BASE tag is in play, using appendChild is a problem for IE6. + //When that browser dies, this can be removed. Details in this jQuery bug: + //http://dev.jquery.com/ticket/2709 + baseElement = document.getElementsByTagName('base')[0]; + if (baseElement) { + head = s.head = baseElement.parentNode; + } + } + + /** + * Any errors that require explicitly generates will be passed to this + * function. Intercept/override it if you want custom error handling. + * @param {Error} err the error object. + */ + req.onError = function (err) { + throw err; + }; + + /** + * Does the request to load a module for the browser case. + * Make this a separate function to allow other environments + * to override it. + * + * @param {Object} context the require context to find state. + * @param {String} moduleName the name of the module. + * @param {Object} url the URL to the module. + */ + req.load = function (context, moduleName, url) { + var config = (context && context.config) || {}, + node; + if (isBrowser) { + //In the browser so use a script tag + node = config.xhtml ? + document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') : + document.createElement('script'); + node.type = config.scriptType || 'text/javascript'; + node.charset = 'utf-8'; + node.async = true; + + node.setAttribute('data-requirecontext', context.contextName); + node.setAttribute('data-requiremodule', moduleName); + + //Set up load listener. Test attachEvent first because IE9 has + //a subtle issue in its addEventListener and script onload firings + //that do not match the behavior of all other browsers with + //addEventListener support, which fire the onload event for a + //script right after the script execution. See: + //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution + //UNFORTUNATELY Opera implements attachEvent but does not follow the script + //script execution mode. + if (node.attachEvent && + //Check if node.attachEvent is artificially added by custom script or + //natively supported by browser + //read https://github.com/jrburke/requirejs/issues/187 + //if we can NOT find [native code] then it must NOT natively supported. + //in IE8, node.attachEvent does not have toString() + //Note the test for "[native code" with no closing brace, see: + //https://github.com/jrburke/requirejs/issues/273 + !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) && + !isOpera) { + //Probably IE. IE (at least 6-8) do not fire + //script onload right after executing the script, so + //we cannot tie the anonymous define call to a name. + //However, IE reports the script as being in 'interactive' + //readyState at the time of the define call. + useInteractive = true; + + node.attachEvent('onreadystatechange', context.onScriptLoad); + //It would be great to add an error handler here to catch + //404s in IE9+. However, onreadystatechange will fire before + //the error handler, so that does not help. If addEvenListener + //is used, then IE will fire error before load, but we cannot + //use that pathway given the connect.microsoft.com issue + //mentioned above about not doing the 'script execute, + //then fire the script load event listener before execute + //next script' that other browsers do. + //Best hope: IE10 fixes the issues, + //and then destroys all installs of IE 6-9. + //node.attachEvent('onerror', context.onScriptError); + } else { + node.addEventListener('load', context.onScriptLoad, false); + node.addEventListener('error', context.onScriptError, false); + } + node.src = url; + + //For some cache cases in IE 6-8, the script executes before the end + //of the appendChild execution, so to tie an anonymous define + //call to the module name (which is stored on the node), hold on + //to a reference to this node, but clear after the DOM insertion. + currentlyAddingScript = node; + if (baseElement) { + head.insertBefore(node, baseElement); + } else { + head.appendChild(node); + } + currentlyAddingScript = null; + + return node; + } else if (isWebWorker) { + //In a web worker, use importScripts. This is not a very + //efficient use of importScripts, importScripts will block until + //its script is downloaded and evaluated. However, if web workers + //are in play, the expectation that a build has been done so that + //only one script needs to be loaded anyway. This may need to be + //reevaluated if other use cases become common. + importScripts(url); + + //Account for anonymous modules + context.completeLoad(moduleName); + } + }; + + function getInteractiveScript() { + if (interactiveScript && interactiveScript.readyState === 'interactive') { + return interactiveScript; + } + + eachReverse(scripts(), function (script) { + if (script.readyState === 'interactive') { + return (interactiveScript = script); + } + }); + return interactiveScript; + } + + //Look for a data-main script attribute, which could also adjust the baseUrl. + if (isBrowser) { + //Figure out baseUrl. Get it from the script tag with require.js in it. + eachReverse(scripts(), function (script) { + //Set the 'head' where we can append children by + //using the script's parent. + if (!head) { + head = script.parentNode; + } + + //Look for a data-main attribute to set main script for the page + //to load. If it is there, the path to data main becomes the + //baseUrl, if it is not already set. + dataMain = script.getAttribute('data-main'); + if (dataMain) { + //Set final baseUrl if there is not already an explicit one. + if (!cfg.baseUrl) { + //Pull off the directory of data-main for use as the + //baseUrl. + src = dataMain.split('/'); + mainScript = src.pop(); + subPath = src.length ? src.join('/') + '/' : './'; + + cfg.baseUrl = subPath; + dataMain = mainScript; + } + + //Strip off any trailing .js since dataMain is now + //like a module name. + dataMain = dataMain.replace(jsSuffixRegExp, ''); + + //Put the data-main script in the files to load. + cfg.deps = cfg.deps ? cfg.deps.concat(dataMain) : [dataMain]; + + return true; + } + }); + } + + /** + * The function that handles definitions of modules. Differs from + * require() in that a string for the module should be the first argument, + * and the function to execute after dependencies are loaded should + * return a value to define the module corresponding to the first argument's + * name. + */ + define = function (name, deps, callback) { + var node, context; + + //Allow for anonymous modules + if (typeof name !== 'string') { + //Adjust args appropriately + callback = deps; + deps = name; + name = null; + } + + //This module may not have dependencies + if (!isArray(deps)) { + callback = deps; + deps = []; + } + + //If no name, and callback is a function, then figure out if it a + //CommonJS thing with dependencies. + if (!deps.length && isFunction(callback)) { + //Remove comments from the callback string, + //look for require calls, and pull them into the dependencies, + //but only if there are function args. + if (callback.length) { + callback + .toString() + .replace(commentRegExp, '') + .replace(cjsRequireRegExp, function (match, dep) { + deps.push(dep); + }); + + //May be a CommonJS thing even without require calls, but still + //could use exports, and module. Avoid doing exports and module + //work though if it just needs require. + //REQUIRES the function to expect the CommonJS variables in the + //order listed below. + deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps); + } + } + + //If in IE 6-8 and hit an anonymous define() call, do the interactive + //work. + if (useInteractive) { + node = currentlyAddingScript || getInteractiveScript(); + if (node) { + if (!name) { + name = node.getAttribute('data-requiremodule'); + } + context = contexts[node.getAttribute('data-requirecontext')]; + } + } + + //Always save off evaluating the def call until the script onload handler. + //This allows multiple modules to be in a file without prematurely + //tracing dependencies, and allows for anonymous module support, + //where the module name is not known until the script onload event + //occurs. If no context, use the global queue, and get it processed + //in the onscript load callback. + (context ? context.defQueue : globalDefQueue).push([name, deps, callback]); + }; + + define.amd = { + jQuery: true + }; + + + /** + * Executes the text. Normally just uses eval, but can be modified + * to use a better, environment-specific call. Only used for transpiling + * loader plugins, not for plain JS modules. + * @param {String} text the text to execute/evaluate. + */ + req.exec = function (text) { + /*jslint evil: true */ + return eval(text); + }; + + //Set up with config info. + req(cfg); +}(this)); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/LICENSE b/activities/GetThingsDone.activity/lib/sugar-web/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/activities/GetThingsDone.activity/lib/sugar-web/README.md b/activities/GetThingsDone.activity/lib/sugar-web/README.md new file mode 100644 index 000000000..de87a7250 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/README.md @@ -0,0 +1,7 @@ +Sugar Web +========= + +These are the tools that a developer can use to make a web +activity. + +For details see: http://developer.sugarlabs.org/ diff --git a/activities/GetThingsDone.activity/lib/sugar-web/activity/activity.js b/activities/GetThingsDone.activity/lib/sugar-web/activity/activity.js new file mode 100644 index 000000000..9a407d99a --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/activity/activity.js @@ -0,0 +1,114 @@ +define(["webL10n", + "sugar-web/activity/shortcut", + "sugar-web/bus", + "sugar-web/env", + "sugar-web/datastore", + "sugar-web/graphics/icon", + "sugar-web/graphics/activitypalette"], function ( + l10n, shortcut, bus, env, datastore, icon, activitypalette) { + + var datastoreObject = null; + + var activity = {}; + + activity.setup = function () { + bus.listen(); + + l10n.start(); + + function onPause() { + activity.write(function () {}); + } + + function onStop() { + function onDataStored(error, result) { + activity.close(function () {}); + } + activity.write(onDataStored); + } + + bus.onNotification("activity.pause", onPause); + bus.onNotification("activity.stop", onStop); + + datastoreObject = new datastore.DatastoreObject(); + + var activityButton = document.getElementById("activity-button"); + + var activityPalette = new activitypalette.ActivityPalette( + activityButton, datastoreObject); + + // Colorize the activity icon. + activity.getXOColor(function (error, colors) { + icon.colorize(activityButton, colors); + invokerElem = + document.querySelector("#activity-palette .palette-invoker"); + icon.colorize(invokerElem, colors); + }); + + // Make the activity stop with the stop button. + var stopButton = document.getElementById("stop-button"); + stopButton.addEventListener('click', onStop); + + shortcut.add("Ctrl", "Q", this.close); + + env.getEnvironment(function (error, environment) { + if (!environment.objectId) { + datastoreObject.setMetadata({ + "title": environment.activityName + " Activity", + "title_set_by_user": "0", + "activity": environment.bundleId, + "activity_id": environment.activityId + }); + } + datastoreObject.save(function () { + datastoreObject.getMetadata(function (error, metadata) { + activityPalette.setTitleDescription(metadata); + }); + }); + }); + }; + + activity.getDatastoreObject = function () { + return datastoreObject; + }; + + activity.getXOColor = function (callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null, { + stroke: result[0][0], + fill: result[0][1] + }); + } else { + callback(null, { + stroke: "#00A0FF", + fill: "#8BFF7A" + }); + } + } + + bus.sendMessage("activity.get_xo_color", [], onResponseReceived); + }; + + // Activities should override this function in order to store + // data. + activity.write = function (callback) { + setTimeout(function () { + callback(null); + }, 0); + }; + + activity.close = function (callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null); + } else { + console.log("activity.close called"); + } + } + + bus.sendMessage("activity.close", [], onResponseReceived); + }; + + return activity; +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/activity/shortcut.js b/activities/GetThingsDone.activity/lib/sugar-web/activity/shortcut.js new file mode 100644 index 000000000..403af03e5 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/activity/shortcut.js @@ -0,0 +1,57 @@ +define(function () { + var shortcut = {}; + + shortcut._allShortcuts = []; + + shortcut.add = function (modifiersString, key, callback) { + // Parse the modifiers. For example "Ctrl+Alt" will become + // {'ctrlKey': true, 'altKey': true, 'shiftKey': false} + var modifiersList = modifiersString.toLowerCase().split("+"); + var modifiers = { + 'ctrlKey': modifiersList.indexOf('ctrl') >= 0, + 'altKey': modifiersList.indexOf('alt') >= 0, + 'shiftKey': modifiersList.indexOf('shift') >= 0 + }; + + this._allShortcuts.push({ + 'modifiers': modifiers, + 'key': key.toLowerCase(), + 'callback': callback + }); + }; + + document.onkeypress = function (e) { + e = e || window.event; + + var modifiers = { + 'ctrlKey': e.ctrlKey, + 'altKey': e.altKey, + 'shiftKey': e.shiftKey + }; + + // Obtain the key + var charCode; + if (typeof e.which == "number") { + charCode = e.which; + } else { + charCode = e.keyCode; + } + var key = String.fromCharCode(charCode).toLowerCase(); + + // Search for a matching shortcut + for (i = 0; i < shortcut._allShortcuts.length; i += 1) { + var currentShortcut = shortcut._allShortcuts[i]; + + var match = currentShortcut.key == key && + currentShortcut.modifiers.ctrlKey == modifiers.ctrlKey && + currentShortcut.modifiers.altKey == modifiers.altKey && + currentShortcut.modifiers.shiftKey == modifiers.shiftKey; + if (match) { + currentShortcut.callback(); + return; + } + } + }; + + return shortcut; +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/bus.js b/activities/GetThingsDone.activity/lib/sugar-web/bus.js new file mode 100644 index 000000000..c1b5cbb74 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/bus.js @@ -0,0 +1,229 @@ +define(["sugar-web/env"], function (env) { + var lastId = 0; + var callbacks = {}; + var notificationCallbacks = {}; + var client = null; + var inputStreams = []; + + function WebSocketClient(environment) { + this.queue = []; + this.socket = null; + + var that = this; + + env.getEnvironment(function (error, environment) { + var port = environment.apiSocketPort; + var socket = new WebSocket("ws://localhost:" + port); + + socket.binaryType = "arraybuffer"; + + socket.onopen = function () { + var params = [environment.activityId, + environment.apiSocketKey]; + + socket.send(JSON.stringify({ + "method": "authenticate", + "id": "authenticate", + "params": params + })); + + while (that.queue.length > 0) { + socket.send(that.queue.shift()); + } + }; + + socket.onmessage = function (message) { + that.onMessage(message); + }; + + that.socket = socket; + }); + } + + WebSocketClient.prototype.send = function (data) { + + if (this.socket && this.socket.readyState == WebSocket.OPEN) { + this.socket.send(data); + } else { + this.queue.push(data); + } + }; + + WebSocketClient.prototype.close = function () { + this.socket.close(); + }; + + var bus = {}; + + function InputStream() { + this.streamId = null; + this.readCallback = null; + } + + InputStream.prototype.open = function (callback) { + var that = this; + bus.sendMessage("open_stream", [], function (error, result) { + that.streamId = result[0]; + inputStreams[that.streamId] = that; + callback(error); + }); + }; + + InputStream.prototype.read = function (count, callback) { + if (this.readCallback) { + throw Error("Read already in progress"); + } + + this.readCallback = callback; + + var buffer = new ArrayBuffer(8); + + var headerView = new Uint8Array(buffer, 0, 1); + headerView[0] = this.streamId; + + var bodyView = new Uint32Array(buffer, 4, 1); + bodyView[0] = count; + + bus.sendBinary(buffer); + }; + + InputStream.prototype.gotData = function (buffer) { + var callback = this.readCallback; + + this.readCallback = null; + + callback(null, buffer); + }; + + InputStream.prototype.close = function (callback) { + var that = this; + + function onStreamClosed(error, result) { + if (callback) { + callback(error); + } + delete inputStreams[that.streamId]; + } + + bus.sendMessage("close_stream", [this.streamId], onStreamClosed); + }; + + function OutputStream() { + this.streamId = null; + } + + OutputStream.prototype.open = function (callback) { + var that = this; + bus.sendMessage("open_stream", [], function (error, result) { + that.streamId = result[0]; + callback(error); + }); + }; + + OutputStream.prototype.write = function (data) { + var buffer = new ArrayBuffer(data.byteLength + 1); + + var bufferView = new Uint8Array(buffer); + bufferView[0] = this.streamId; + bufferView.set(new Uint8Array(data), 1); + + bus.sendBinary(buffer); + }; + + OutputStream.prototype.close = function (callback) { + bus.sendMessage("close_stream", [this.streamId], callback); + }; + + bus.createInputStream = function (callback) { + return new InputStream(); + }; + + bus.createOutputStream = function (callback) { + return new OutputStream(); + }; + + bus.sendMessage = function (method, params, callback) { + + var message = { + "method": method, + "id": lastId, + "params": params + }; + + /*if (callback) { + callbacks[lastId] = callback; + } + lastId++; + client.send(JSON.stringify(message));*/ + + console.log("ACTIVITY CALL "+JSON.stringify(message)); + if (method == "activity.close") { + window.location = "../.."; + } else if (method == "activity.get_xo_color") { + var color = JSON.parse(window.localStorage.getItem("settings")).colorvalue; + callback(null, [[color.fill, color.stroke]]); + } + + + }; + + bus.onNotification = function (method, callback) { + notificationCallbacks[method] = callback; + }; + + bus.sendBinary = function (buffer, callback) { + client.send(buffer); + }; + + bus.listen = function (customClient) { + if (customClient) { + client = customClient; + } else { + client = new WebSocketClient(); + } + + client.onMessage = function (message) { + if (typeof message.data != "string") { + var dataView = new Uint8Array(message.data); + var streamId = dataView[0]; + + if (streamId in inputStreams) { + var inputStream = inputStreams[streamId]; + inputStream.gotData(message.data.slice(1)); + } + + return; + } + + var parsed = JSON.parse(message.data); + var responseId = parsed.id; + + if (parsed.method) { + var notificationCallback = notificationCallbacks[parsed.method]; + if (notificationCallback !== undefined) { + notificationCallback(parsed.params); + } + return; + } + + if (responseId in callbacks) { + var callback = callbacks[responseId]; + + if (parsed.error === null) { + callback(null, parsed.result); + } else { + callback(new Error(parsed.error), null); + } + + delete callbacks[responseId]; + } + }; + }; + + bus.close = function () { + client.close(); + client = null; + }; + + return bus; +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/datastore.js b/activities/GetThingsDone.activity/lib/sugar-web/datastore.js new file mode 100644 index 000000000..929177960 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/datastore.js @@ -0,0 +1,221 @@ +define(["sugar-web/bus", "sugar-web/env"], function (bus, env) { + var datastore = {}; + + function DatastoreObject(objectId) { + this.objectId = objectId; + this.newMetadata = {}; + + this.ensureObjectId = function (callback) { + var that = this; + + env.getEnvironment(function (error, environment) { + if (environment.objectId !== null) { + that.objectId = environment.objectId; + } + callback(); + }); + }; + + this.blobToText = function (blob, callback) { + var reader = new FileReader(); + reader.onload = function (e) { + callback(e.target.result); + }; + reader.readAsText(blob); + }; + + this.blobToArrayBuffer = function (blob, callback) { + var reader = new FileReader(); + reader.onload = function (e) { + callback(e.target.result); + }; + reader.readAsArrayBuffer(blob); + }; + + this.saveText = function (metadata, callback) { + var that = this; + + function onSaved(error, outputStream) { + var blob = new Blob([that.newDataAsText]); + + that.blobToArrayBuffer(blob, function (buffer) { + outputStream.write(buffer); + outputStream.close(callback); + }); + } + + datastore.save(this.objectId, metadata, onSaved); + }; + + this.applyChanges = function (metadata, callback) { + for (var key in this.newMetadata) { + metadata[key] = this.newMetadata[key]; + } + + if (this.newDataAsText !== undefined) { + this.saveText(metadata, callback); + } else { + datastore.setMetadata(this.objectId, metadata, callback); + } + }; + } + + DatastoreObject.prototype.getMetadata = function (callback) { + var that = this; + + this.ensureObjectId(function () { + datastore.getMetadata(that.objectId, callback); + }); + }; + + DatastoreObject.prototype.loadAsText = function (callback) { + var that = this; + var inputStream = null; + var arrayBuffers = []; + var metadata = null; + + function onRead(error, data) { + if (data.byteLength === 0) { + var blob = new Blob(arrayBuffers); + + that.blobToText(blob, function (text) { + callback(null, metadata, text); + }); + + inputStream.close(); + + return; + } + + arrayBuffers.push(data); + + inputStream.read(8192, onRead); + } + + function onLoad(error, loadedMetadata, loadedInputStream) { + metadata = loadedMetadata; + inputStream = loadedInputStream; + + inputStream.read(8192, onRead); + } + + this.ensureObjectId(function () { + datastore.load(that.objectId, onLoad); + }); + }; + + DatastoreObject.prototype.setMetadata = function (metadata) { + for (var key in metadata) { + this.newMetadata[key] = metadata[key]; + } + }; + + DatastoreObject.prototype.setDataAsText = function (text) { + this.newDataAsText = text; + }; + + DatastoreObject.prototype.save = function (callback) { + if (callback === undefined) { + callback = function () {}; + } + + var that = this; + + function onCreated(error, objectId) { + that.objectId = objectId; + that.applyChanges({}, callback); + } + + function onGotMetadata(error, metadata) { + that.applyChanges(metadata, callback); + } + + this.ensureObjectId(function () { + if (that.objectId === undefined) { + datastore.create(that.newMetadata, onCreated); + } else { + datastore.getMetadata(that.objectId, onGotMetadata); + } + }); + }; + + datastore.DatastoreObject = DatastoreObject; + + + datastore.setMetadata = function (objectId, metadata, callback) { + function onResponseReceived(error, result) { + if (callback) { + if (error === null) { + callback(null); + } else { + callback(error); + } + } + } + + var params = [objectId, metadata]; + bus.sendMessage("datastore.set_metadata", params, onResponseReceived); + }; + + datastore.getMetadata = function (objectId, callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null, result[0]); + } else { + callback(error, null); + } + } + + var params = [objectId]; + bus.sendMessage("datastore.get_metadata", params, onResponseReceived); + }; + + datastore.load = function (objectId, callback) { + inputStream = bus.createInputStream(); + + inputStream.open(function (error) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, result[0], inputStream); + } else { + callback(responseError, null, null); + } + } + + var params = [objectId, inputStream.streamId]; + bus.sendMessage("datastore.load", params, onResponseReceived); + }); + }; + + datastore.create = function (metadata, callback) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, result[0]); + } else { + callback(responseError, null); + } + } + + var params = [metadata]; + bus.sendMessage("datastore.create", params, onResponseReceived); + }; + + datastore.save = function (objectId, metadata, callback) { + outputStream = bus.createOutputStream(); + + outputStream.open(function (error) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, outputStream); + } else { + callback(responseError, null); + } + } + + var params = [objectId, metadata, outputStream.streamId]; + bus.sendMessage("datastore.save", params, onResponseReceived); + }); + }; + + return datastore; +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/env.js b/activities/GetThingsDone.activity/lib/sugar-web/env.js new file mode 100644 index 000000000..1a948eb30 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/env.js @@ -0,0 +1,27 @@ +define(function () { + + var env = {}; + + env.getEnvironment = function (callback) { + var sugar; + + if (window.top.sugar) { + sugar = window.top.sugar; + } else { + sugar = {}; + window.top.sugar = sugar; + } + + if (sugar.environment) { + setTimeout(function () { + callback(null, sugar.environment); + }, 0); + } else { + sugar.onEnvironmentSet = function () { + callback(null, sugar.environment); + }; + } + }; + + return env; +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/README.md b/activities/GetThingsDone.activity/lib/sugar-web/graphics/README.md new file mode 100644 index 000000000..e3b7091ba --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/README.md @@ -0,0 +1,39 @@ +Sugar Graphics +============== + +Sugar widgets and graphics, implementing the [Sugar Interface +Guidelines](http://wiki.sugarlabs.org/go/Human_Interface_Guidelines). + +Modifying the CSS +----------------- + +We use [LESS](http://lesscss.org) and then compile the CSS. This is +to be able to use calculations and variables for colors and measures. +And to be able to output different CSS files for different screen +resolutions, which is planned. + +To compile the CSS do: + + lessc graphics/css/sugar.less graphics/css/sugar.css + +Be sure to compile it before commit. + +The grid helper +--------------- + +The grid is a visual debug tool that allows you to check if the +elements have the correct size. To activate the grid, open the +inspector console and paste the following. + +If RequireJS is not in the page head (ie. in the sugar-web-samples), +load it: + + var script = document.createElement('script'); + script.src = 'lib/require.js'; + document.head.appendChild(script); + + requirejs.config({ baseUrl: "lib" }); + +Then do: + + require(["sugar-web/graphics/grid"], function (grid) { grid.addGrid(11) }); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/activitypalette.js b/activities/GetThingsDone.activity/lib/sugar-web/graphics/activitypalette.js new file mode 100644 index 000000000..aca036ebe --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/activitypalette.js @@ -0,0 +1,70 @@ +define(["sugar-web/graphics/palette"], function (palette) { + + var activitypalette = {}; + + activitypalette.ActivityPalette = function (activityButton, + datastoreObject) { + + palette.Palette.call(this, activityButton); + + var activityTitle; + var descriptionLabel; + var descriptionBox; + + this.getPalette().id = "activity-palette"; + + this.template = + '
    ' + + '' + + '
    ' + + '
    ' + + '' + + '
    ' + + '
    ' + + '' + + '
    '; + + var containerElem = document.createElement('div'); + containerElem.innerHTML = this.template; + this.setContent([containerElem]); + + this.titleElem = containerElem.querySelector('#title'); + this.descriptionElem = containerElem.querySelector('#description'); + + this.titleElem.onblur = function () { + datastoreObject.setMetadata({ + "title": this.value, + "title_set_by_user": "1" + }); + datastoreObject.save(); + }; + + this.descriptionElem.onblur = function () { + datastoreObject.setMetadata({ + "description": this.value + }); + datastoreObject.save(); + }; + }; + + // Fill the text inputs with the received metadata. + var setTitleDescription = function (metadata) { + this.titleElem.value = metadata.title; + + if (metadata.description !== undefined) { + this.descriptionElem.value = metadata.description; + } + }; + + activitypalette.ActivityPalette.prototype = + Object.create(palette.Palette.prototype, { + setTitleDescription: { + value: setTitleDescription, + enumerable: true, + configurable: true, + writable: true + } + }); + + return activitypalette; +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/css/sugar.css b/activities/GetThingsDone.activity/lib/sugar-web/graphics/css/sugar.css new file mode 100644 index 000000000..cc1419d65 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/css/sugar.css @@ -0,0 +1,456 @@ +* { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +html { + height: 100%; +} +body { + margin: 0; + height: 100%; + background-color: #c0c0c0; + position: relative; + font-family: sans-serif; + font-size: 10pt; +} +.unselectable { + -moz-user-select: none; + -webkit-user-select: none; +} +.pull-right { + float: right; +} +a { + color: #0076c3; + text-decoration: none; +} +/* Toolbar */ +.toolbar { + color: white; + background-color: #282828; + padding: 0 57px; + height: 55px; + -moz-user-select: none; + -webkit-user-select: none; +} +/* Toolbar separator */ +.toolbar hr { + display: inline-block; + height: 33px; + border: 1px solid #808080; + margin: 2.5px; + margin-bottom: -17px; +} +/* Toolbar toolbutton */ +.toolbar .toolbutton { + background-color: transparent; + background-position: center; + background-size: contain; + background-repeat: no-repeat; + color: white; + color: transparent; + border: 0; + border-radius: 5.5px; + margin: 4px 2px; + width: 47px; + height: 47px; + position: relative; +} +.toolbar .toolbutton:hover { + background-color: black; +} +.toolbar .toolbutton:active, +.toolbar .toolbutton.active { + background-color: #808080; +} +.toolbar .toolbutton img { + width: 100%; + height: 100%; +} +.toolbar .toolbutton:before { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: 55px; + height: 11px; + bottom: -4px; + right: -4px; +} +.toolbar .toolbutton.invoker:before { + background-image: url('../icons/emblems/arrow-down.svg'); +} +.toolbar #stop-button { + background-image: url('../icons/actions/activity-stop.svg'); +} +/* Canvas */ +#canvas { + color: black; + background-color: #c0c0c0; + position: absolute; + bottom: 0; + top: 55px; + overflow-y: auto; + width: 100%; +} +/* Button */ +button { + background-color: #808080; + color: white; + border: 2px solid transparent; + border-radius: 22px; + line-height: 22px; + padding: 2px 4px; + -moz-user-select: none; + -webkit-user-select: none; +} +button:hover { + background-color: #a2a2a2; + border-color: #a2a2a2; +} +button:active { + background-color: white; + color: black; + border-color: #808080; +} +button:focus { + border-color: white; +} +.toolbar button { + margin-top: 12.5px; +} +/* Button with icon */ +button.icon { + position: relative; + padding-left: 26px; +} +button.icon span.ok { + background-image: url(../icons/actions/dialog-ok.svg); +} +button.icon:active span.ok { + background-image: url(../icons/actions/dialog-ok-active.svg); +} +button.icon span.cancel { + background-image: url(../icons/actions/dialog-cancel.svg); +} +button.icon:active span.cancel { + background-image: url(../icons/actions/dialog-cancel-active.svg); +} +button.icon span { + display: inline-block; + width: 22px; + height: 22px; + background-color: transparent; + background-position: center; + background-size: 22px 22px; + background-repeat: no-repeat; + position: absolute; +} +button.icon span { + top: 2px; + left: 2px; +} +/* One line text input */ +input[type='text'] { + background-color: #e5e5e5; + border: 2px solid #e5e5e5; + border-radius: 22px; + padding: 4px; + width: 165px; + line-height: 22px; + outline: 0; +} +input[type='text']:focus { + background-color: white; +} +input[type='text']:disabled { + border-color: #808080; + background-color: #808080; +} +.toolbar input[type='text'], +.palette .row input[type='text'] { + margin-top: 10.5px; +} +.palette .row input[type='text']:nth-last-child(1) { + margin-top: 8.5px; +} +input[type='text'].expand { + width: calc(100% - 12px); +} +/* One line text input with buttons inside */ +.icon-input { + display: inline-block; + position: relative; +} +.icon-input input[type='text'] { + width: 135px; + padding-right: 34px; +} +.icon-input.expand { + width: 100%; +} +.icon-input.expand input[type='text'] { + width: calc(100% - 42px); +} +.icon-input button { + width: 34px; + height: 34px; + padding: 0; + position: absolute; + background-size: 22px 22px; +} +.icon-input button.right { + margin: 0 0 0 -34px; + border-radius: 0 22px 22px 0; + right: 0; +} +.icon-input button { + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + border: 0; +} +.icon-input button { + top: 2px; +} +.toolbar .icon-input button:hover { + background-color: transparent; +} +.toolbar .icon-input button { + top: 10.5px; +} +button.cancel { + background-image: url(../icons/actions/entry-cancel.svg); +} +button.cancel:active { + background-image: url(../icons/actions/entry-cancel-active.svg); +} +button.cancel:disabled { + background-image: url(../icons/actions/entry-cancel-disabled.svg); +} +/* Slider */ +/* FIXME this is not fully Sugarized yet */ +input[type='range'] { + -webkit-appearance: none !important; + background-color: #808080; + border-radius: 22px; + height: 11px; + cursor: pointer; +} +input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none !important; + background-color: #c0c0c0; + border: 4px solid #808080; + border-radius: 11px; + height: 22px; + width: 22px; +} +input[type='range']::-webkit-slider-thumb:hover { + background-color: #e2e2e2; + border-color: #a2a2a2; +} +.toolbar input[type='range'] { + margin-top: 22px; +} +/* Label */ +label { + -moz-user-select: none; + -webkit-user-select: none; +} +/* Palette */ +.palette { + color: white; + background-color: transparent; + position: absolute; + pointer-events: none; +} +.palette-invoker { + width: 51px; + height: 53px; + background-color: black; + border: 2px solid #808080; + border-bottom: 0; + background-position: 2px 2px; + background-size: 47px; + background-repeat: no-repeat; +} +.palette-invoker:after { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: 55px; + height: 11px; + top: 44px; + left: 0; + background-image: url('../icons/emblems/arrow-up.svg'); +} +.palette-invoker:before { + content: ""; + background-color: black; + position: absolute; + display: block; + top: 55px; + width: 51px; + left: 2px; + height: 2px; +} +.palette .wrapper { + background-color: black; + border: 2px solid #808080; + max-width: 271px; + min-width: 161px; + min-height: 51px; + pointer-events: auto; +} +.palette .header { + height: 51px; + line-height: 51px; + -moz-user-select: none; + -webkit-user-select: none; + margin: 0 5.5px; + font-weight: bold; +} +.palette hr { + border: 1px solid #808080; +} +.palette hr.header-separator { + margin-top: 0; +} +.palette .row { + height: 55px; + margin: 0 5.5px; +} +.palette .row:nth-last-child(1) { + height: 51px; +} +.palette .row.small { + height: 22px; +} +.palette .row.expand { + height: auto; +} +/* Palette menu */ +.palette .menu { + list-style-type: none; + padding: 0; + margin-top: 11px; + margin-bottom: 11px; +} +.palette .menu button { + background-color: transparent; + border-radius: 0; + border: 0; + width: 100%; + text-align: left; + line-height: 33px; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; +} +.palette .menu button:hover { + background-color: #808080; + color: white; +} +.palette .menu button.icon { + padding-left: 37px; +} +.palette .menu button.icon span { + top: 0; + left: 0; + width: 33px; + height: 33px; +} +/* Scrollbar */ +::-webkit-scrollbar { + background-color: #808080; + width: 11px; +} +::-webkit-scrollbar-thumb { + background-color: white; + border: 2px solid #dddddd; + border-radius: 11px; +} +/* Grid for visual debugging and layout */ +.grid { + background-color: transparent; + position: absolute; + top: 0; + left: 0; +} +/* Checkbox and radio */ +input[type='checkbox'], +input[type='radio'] { + background-position: center; + background-size: contain; + width: 22px; + height: 22px; + margin: 2px 2px 4px 2px; + vertical-align: middle; + -moz-appearance: none; + appearance: none; + -webkit-appearance: none; +} +.toolbar input[type='checkbox'], +.toolbar input[type='radio'] { + margin-top: 14.5px; + margin-bottom: 18.5px; +} +input[type='checkbox'] { + background-image: url(../icons/actions/checkbox-unchecked.svg); +} +input[type='checkbox']:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} +input[type='checkbox']:checked:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} +input[type='checkbox']:checked { + background-image: url(../icons/actions/checkbox-checked.svg); +} +input[type='radio'] { + background-image: url(../icons/actions/radio.svg); +} +input[type='radio']:active { + background-image: url(../icons/actions/radio-selected.svg); +} +input[type='radio']:checked:active { + background-image: url(../icons/actions/radio-active-selected.svg); +} +input[type='radio']:checked { + background-image: url(../icons/actions/radio-active.svg); +} +/* Textarea */ +textarea { + border: 2px solid #808080; + margin: 2px; +} +textarea.expand { + width: calc(100% - 12px); +} +/* Lists */ +ul.flat-list { + list-style-type: none; + padding: 0; + margin: 0; +} +ul.flat-list li { + border-bottom: 2px dotted #c0c0c0; + background-color: white; + height: 31px; + line-height: 31px; +} +ul.flat-list li:nth-last-child(1) { + border-bottom: none; +} +ul.flat-list.big li { + height: 42px; + line-height: 42px; +} +ul.flat-list.striped li:nth-child(odd) { + background-color: #e5e5e5; +} +/* ActivityPalette */ +#activity-palette .wrapper { + width: 271px; +} diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/css/sugar.less b/activities/GetThingsDone.activity/lib/sugar-web/graphics/css/sugar.less new file mode 100644 index 000000000..8fa9766d6 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/css/sugar.less @@ -0,0 +1,589 @@ +@toolbar-grey: #282828; +@button-grey: #808080; +@panel-grey: #C0C0C0; +@text-field-grey: #E5E5E5; +@link-blue: #0076C3; + +@line-width: 2px; +@subcell-size: 11px; +@cell-size: 5 * @subcell-size; +@font-size: 10pt; + +@toolbar-height: @cell-size; +@icon-small-size: 2 * @subcell-size; + +@toolbutton-size: @toolbar-height - (4 * @line-width); +@toolbar-button-margin-top: (@toolbar-height / 2) - (2 * @line-width) - + (@icon-small-size / 2); + +@input-text-padding: (2 * @line-width); +@input-text-line-height: 2 * @subcell-size; +@input-text-width: 3 * @cell-size; +@toolbar-input-height: (@toolbar-height / 2) - (@input-text-line-height / 2) - + (@input-text-padding + @line-width); + +@button-small-size: @input-text-line-height + (2 * @input-text-padding) + + (2 * @line-width); + +* { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +html { + height: 100%; +} + +body { + margin: 0; + height: 100%; + background-color: @panel-grey; + position: relative; + font-family: sans-serif; + font-size: @font-size; +} + +.unselectable { + -moz-user-select: none; + -webkit-user-select: none; +} + +.pull-right { + float: right; +} + +a { + color: @link-blue; + text-decoration: none; +} + +/* Toolbar */ + +.toolbar { + color: white; + background-color: @toolbar-grey; + padding: 0 @toolbutton-size + (5 * @line-width); + height: @toolbar-height; + .unselectable; +} + +/* Toolbar separator */ + +.toolbar hr { + display: inline-block; + height: 3 * @subcell-size; + border: (@line-width / 2) solid @button-grey; + margin: (@subcell-size - (@line-width * 3)) / 2; + margin-bottom: -1 * (@subcell-size + (@line-width * 3)); +} + +/* Toolbar toolbutton */ + +.toolbar .toolbutton { + background-color: transparent; + background-position: center; + background-size: contain; + background-repeat: no-repeat; + color: white; + color: transparent; + border: 0; + border-radius: @subcell-size / 2; + margin: (2 * @line-width) @line-width; + width: @toolbutton-size; + height: @toolbutton-size; + position: relative; +} + +.toolbar .toolbutton:hover { + background-color: black; +} + +.toolbar .toolbutton:active, .toolbar .toolbutton.active { + background-color: @button-grey; +} + +.toolbar .toolbutton img { + width: 100%; + height: 100%; +} + +.toolbar .toolbutton:before { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: @cell-size; + height: @subcell-size; + bottom: -2 * @line-width; + right: -2 * @line-width; +} + +.toolbar .toolbutton.invoker:before { + background-image: url('../icons/emblems/arrow-down.svg'); +} + +.toolbar #stop-button { + background-image: url('../icons/actions/activity-stop.svg'); +} + +/* Canvas */ + +#canvas { + color: black; + background-color: @panel-grey; + position: absolute; + bottom: 0; + top: @toolbar-height; + overflow-y: auto; + width: 100%; +} + +/* Button */ + +button { + background-color: @button-grey; + color: white; + border: @line-width solid transparent; + border-radius: 2 * @subcell-size; + line-height: @icon-small-size; + padding: @line-width (2 * @line-width); + .unselectable; +} + +button:hover { + background-color: @button-grey + #222; + border-color: @button-grey + #222; +} + +button:active { + background-color: white; + color: black; + border-color: @button-grey; +} + +button:focus { + border-color: white; +} + +.toolbar button { + margin-top: @toolbar-button-margin-top; +} + +/* Button with icon */ + +button.icon { + position: relative; + padding-left: @icon-small-size + (2 * @line-width); +} + +button.icon span.ok { + background-image: url(../icons/actions/dialog-ok.svg); +} + +button.icon:active span.ok { + background-image: url(../icons/actions/dialog-ok-active.svg); +} + +button.icon span.cancel { + background-image: url(../icons/actions/dialog-cancel.svg); +} + +button.icon:active span.cancel { + background-image: url(../icons/actions/dialog-cancel-active.svg); +} + +button.icon span { + display: inline-block; + width: @icon-small-size; + height: @icon-small-size; + background-color: transparent; + background-position: center; + background-size: @icon-small-size @icon-small-size; + background-repeat: no-repeat; + position: absolute; +} + +button.icon span { + top: @line-width; + left: @line-width; +} + +/* One line text input */ + +input[type='text'] { + background-color: @text-field-grey; + border: @line-width solid @text-field-grey; + border-radius: 2 * @subcell-size; + padding: @input-text-padding; + width: @input-text-width; + line-height: @input-text-line-height; + outline: 0; +} + +input[type='text']:focus { + background-color: white; +} + +input[type='text']:disabled { + border-color: @button-grey; + background-color: @button-grey; +} + +.toolbar input[type='text'], .palette .row input[type='text'] { + margin-top: @toolbar-input-height; +} + +.palette .row input[type='text']:nth-last-child(1) { + margin-top: @toolbar-input-height - @line-width; +} + +input[type='text'].expand { + width: calc(~"100%" - (2 * @input-text-padding + 2 * @line-width)); +} + +/* One line text input with buttons inside */ + +.icon-input { + display: inline-block; + position: relative; +} + +.icon-input input[type='text'] { + width: @input-text-width + @input-text-padding - @button-small-size; + padding-right: @button-small-size; +} + +.icon-input.expand { + width: 100%; +} + +.icon-input.expand input[type='text'] { + width: calc(~"100%" - (@input-text-padding + 2 * @line-width + + @button-small-size)); +} + +.icon-input button { + width: @button-small-size; + height: @button-small-size; + padding: 0; + position: absolute; + background-size: @icon-small-size @icon-small-size; +} + +.icon-input button.right { + margin: 0 0 0 -@button-small-size; + border-radius: 0 (2 * @subcell-size) (2 * @subcell-size) 0; + right: 0; +} + +.icon-input button { + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + border: 0; +} + +.icon-input button { + top: @line-width; +} + +.toolbar .icon-input button:hover { + background-color: transparent; +} + +.toolbar .icon-input button { + top: @toolbar-input-height; +} + +button.cancel { + background-image: url(../icons/actions/entry-cancel.svg); +} + +button.cancel:active { + background-image: url(../icons/actions/entry-cancel-active.svg); +} + +button.cancel:disabled { + background-image: url(../icons/actions/entry-cancel-disabled.svg); +} + +/* Slider */ +/* FIXME this is not fully Sugarized yet */ + +input[type='range'] { + -webkit-appearance: none !important; + background-color: @button-grey; + border-radius: 2 * @subcell-size; + height: @subcell-size; + cursor: pointer; +} + +input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none !important; + background-color: @panel-grey; + border: (2 * @line-width) solid @button-grey; + border-radius: @subcell-size; + height: @icon-small-size; + width: @icon-small-size; +} + +input[type='range']::-webkit-slider-thumb:hover { + background-color: @panel-grey + #222; + border-color: @button-grey + #222; +} + +.toolbar input[type='range'] { + margin-top: (@toolbar-height / 2) - (@subcell-size / 2); +} + +/* Label */ + +label { + .unselectable; +} + +/* Palette */ + +.palette { + color: white; + background-color: transparent; + position: absolute; + pointer-events: none; +} + +.palette-invoker { + width: @cell-size - 2 * @line-width; + height: @cell-size - @line-width; + background-color: black; + border: @line-width solid @button-grey; + border-bottom: 0; + background-position: @line-width @line-width; + background-size: @toolbutton-size; + background-repeat: no-repeat; +} + +.palette-invoker:after { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: @cell-size; + height: @subcell-size; + top: 4 * @subcell-size; + left: 0; + background-image: url('../icons/emblems/arrow-up.svg'); +} + +.palette-invoker:before { + content: ""; + background-color: black; + position: absolute; + display: block; + top: @cell-size; + width: @cell-size - 2 * @line-width; + left: @line-width; + height: @line-width; +} + +.palette .wrapper { + background-color: black; + border: @line-width solid @button-grey; + max-width: 5 * @cell-size - 2 * @line-width; + min-width: 3 * @cell-size - 2 * @line-width; + min-height: @cell-size - 2 * @line-width; + pointer-events: auto; +} + +.palette .header { + height: @cell-size - 2 * @line-width; + line-height: @cell-size - 2 * @line-width; + .unselectable; + margin: 0 (@subcell-size / 2); + font-weight: bold; +} + +.palette hr { + border: (@line-width / 2) solid @button-grey; +} + +.palette hr.header-separator { + margin-top: 0; +} + +.palette .row { + height: @cell-size; + margin: 0 (@subcell-size / 2); +} + +.palette .row:nth-last-child(1) { + height: @cell-size - 2 * @line-width; +} + +.palette .row.small { + height: 2 * @subcell-size; +} + +.palette .row.expand { + height: auto; +} + +/* Palette menu */ + +.palette .menu { + list-style-type: none; + padding: 0; + margin-top: @subcell-size; + margin-bottom: @subcell-size; +} + +.palette .menu button { + background-color: transparent; + border-radius: 0; + border: 0; + width: 100%; + text-align: left; + line-height: 3 * @subcell-size; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; +} + +.palette .menu button:hover { + background-color: @button-grey; + color: white; +} + +.palette .menu button.icon { + padding-left: 3 * @subcell-size + 2 * @line-width; +} + +.palette .menu button.icon span { + top: 0; + left: 0; + width: 3 * @subcell-size; + height: 3 * @subcell-size; +} + +/* Scrollbar */ + +::-webkit-scrollbar { + background-color: @button-grey; + width: @subcell-size; +} + +::-webkit-scrollbar-thumb { + background-color: white; + border: @line-width solid #ddd; + border-radius: @subcell-size; +} + +/* Grid for visual debugging and layout */ + +.grid { + background-color: transparent; + position: absolute; + top: 0; + left: 0; +} + +.appearance(@value) { + -moz-appearance: @value; + appearance: @value; + -webkit-appearance: @value; +} + +/* Checkbox and radio */ + +input[type='checkbox'], +input[type='radio'] { + background-position: center; + background-size: contain; + width: @icon-small-size; + height: @icon-small-size; + margin: @line-width @line-width (2 * @line-width) @line-width; + vertical-align: middle; + .appearance(none); +} + +.toolbar input[type='checkbox'], +.toolbar input[type='radio'] { + margin-top: (@toolbar-height - @icon-small-size) / 2 - @line-width; + margin-bottom: (@toolbar-height - @icon-small-size) / 2 + @line-width; +} + +input[type='checkbox'] { + background-image: url(../icons/actions/checkbox-unchecked.svg); +} + +input[type='checkbox']:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} + +input[type='checkbox']:checked:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} + +input[type='checkbox']:checked { + background-image: url(../icons/actions/checkbox-checked.svg); +} + +input[type='radio'] { + background-image: url(../icons/actions/radio.svg); +} + +input[type='radio']:active { + background-image: url(../icons/actions/radio-selected.svg); +} + +input[type='radio']:checked:active { + background-image: url(../icons/actions/radio-active-selected.svg); +} + +input[type='radio']:checked { + background-image: url(../icons/actions/radio-active.svg); +} + +/* Textarea */ + +textarea { + border: @line-width solid @button-grey; + margin: @line-width; +} + +textarea.expand { + width: calc(~"100%" - (6 * (@line-width))); +} + +/* Lists */ + +ul.flat-list { + list-style-type: none; + padding: 0; + margin: 0; +} + +ul.flat-list li { + border-bottom: @line-width dotted @panel-grey; + background-color: white; + height: 3 * @subcell-size - @line-width; + line-height: 3 * @subcell-size - @line-width; +} + +ul.flat-list li:nth-last-child(1) { + border-bottom: none; +} + +ul.flat-list.big li { + height: 4 * @subcell-size - @line-width; + line-height: 4 * @subcell-size - @line-width; +} + +ul.flat-list.striped li:nth-child(odd) { + background-color: @text-field-grey; +} + +/* ActivityPalette */ + +#activity-palette .wrapper { + width: 5 * @cell-size - 2 * @line-width; +} \ No newline at end of file diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/grid.js b/activities/GetThingsDone.activity/lib/sugar-web/graphics/grid.js new file mode 100644 index 000000000..503713ab7 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/grid.js @@ -0,0 +1,54 @@ +define(function () { + var grid = {}; + + // Add a grid overlay with lines spaced by subcellSize, for visual + // debugging. This is useful while doing the activity layout or + // while developing widgets. + grid.addGrid = function (subcellSize) { + var canvas = document.createElement('canvas'); + canvas.className = "grid"; + document.body.appendChild(canvas); + + var updateGrid = function () { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + var ctx = canvas.getContext("2d"); + ctx.strokeStyle = "#00FFFF"; + + var subcellsVertical = window.innerHeight / subcellSize; + for (i = 0; i < subcellsVertical; i++) { + if ((i + 1) % 5 === 0) { + ctx.lineWidth = 1; + } else { + ctx.lineWidth = 0.5; + } + ctx.beginPath(); + ctx.moveTo(0, subcellSize * (i + 1)); + ctx.lineTo(canvas.width, subcellSize * (i + 1)); + ctx.stroke(); + } + + var subcellsHorizontal = window.innerWidth / subcellSize; + for (i = 0; i < subcellsHorizontal; i++) { + if ((i + 1) % 5 === 0) { + ctx.lineWidth = 1; + } else { + ctx.lineWidth = 0.5; + } + ctx.beginPath(); + ctx.moveTo(subcellSize * (i + 1), 0); + ctx.lineTo(subcellSize * (i + 1), canvas.height); + ctx.stroke(); + } + }; + + updateGrid(); + + window.onresize = function (event) { + updateGrid(); + }; + }; + + return grid; +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icon.js b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icon.js new file mode 100644 index 000000000..f2c07915a --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icon.js @@ -0,0 +1,81 @@ +define(function () { + var icon = {}; + + function changeColors(iconData, fillColor, strokeColor) { + var re; + + if (fillColor) { + re = /()/; + iconData = iconData.replace(re, "$1" + fillColor + "$3"); + } + + if (strokeColor) { + re = /()/; + iconData = iconData.replace(re, "$1" + strokeColor + "$3"); + } + + return iconData; + } + + icon.load = function (iconInfo, callback) { + var source; + var dataHeader = "data:image/svg+xml,"; + + if ("uri" in iconInfo) { + source = iconInfo.uri; + } else if ("name" in iconInfo) { + source = "lib/graphics/icons/" + iconInfo.name + ".svg"; + } + + var fillColor = iconInfo.fillColor; + var strokeColor = iconInfo.strokeColor; + + // If source is already a data uri, read it instead of doing + // the XMLHttpRequest + if (source.substring(0, 4) == 'data') { + var iconData = unescape(source.slice(dataHeader.length)); + var newData = changeColors(iconData, fillColor, strokeColor); + callback(dataHeader + escape(newData)); + return; + } + + var client = new XMLHttpRequest(); + + client.onload = function () { + var iconData = this.responseText; + var newData = changeColors(iconData, fillColor, strokeColor); + callback(dataHeader + escape(newData)); + }; + + client.open("GET", source); + client.send(); + }; + + function getBackgroundURL(elem) { + var style = elem.currentStyle || window.getComputedStyle(elem, ''); + // Remove prefix 'url(' and suffix ')' before return + return style.backgroundImage.slice(4, -1); + } + + function setBackgroundURL(elem, url) { + elem.style.backgroundImage = "url('" + url + "')"; + } + + icon.colorize = function (elem, colors, callback) { + var iconInfo = { + "uri": getBackgroundURL(elem), + "strokeColor": colors.stroke, + "fillColor": colors.fill + }; + + icon.load(iconInfo, function (url) { + setBackgroundURL(elem, url); + if (callback) { + callback(); + } + }); + + }; + + return icon; +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg new file mode 100644 index 000000000..11b82e817 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg new file mode 100644 index 000000000..8ec1223a7 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg @@ -0,0 +1,27 @@ + + + +]> + + + + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg new file mode 100644 index 000000000..3cfce18f6 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg @@ -0,0 +1,27 @@ + + + +]> + + + + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg new file mode 100644 index 000000000..2263279eb --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg @@ -0,0 +1,22 @@ + + +]> + + + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg new file mode 100644 index 000000000..e1587823e --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg @@ -0,0 +1,22 @@ + + +]> + + + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg new file mode 100644 index 000000000..dc0d688f6 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg @@ -0,0 +1,6 @@ + + +]> + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg new file mode 100644 index 000000000..dab4ae2d9 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg new file mode 100644 index 000000000..45de8401a --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg @@ -0,0 +1,6 @@ + + +]> + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg new file mode 100644 index 000000000..69e5a2a13 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg new file mode 100644 index 000000000..467509e75 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg @@ -0,0 +1,23 @@ + + + +]> + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg new file mode 100644 index 000000000..55b4cdb87 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg @@ -0,0 +1,21 @@ + + + +]> + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg new file mode 100644 index 000000000..5339a7e94 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg @@ -0,0 +1,21 @@ + + + +]> + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg new file mode 100644 index 000000000..c1d1085a0 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg @@ -0,0 +1,31 @@ + + + +]> + + + + + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg new file mode 100644 index 000000000..a5fe59156 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg @@ -0,0 +1,31 @@ + + + +]> + + + + + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg new file mode 100644 index 000000000..a24b97e65 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg @@ -0,0 +1,26 @@ + + + +]> + + + + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio.svg new file mode 100644 index 000000000..d25028624 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/radio.svg @@ -0,0 +1,26 @@ + + + +]> + + + + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg new file mode 100644 index 000000000..b88462ff5 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg new file mode 100644 index 000000000..5578fecbf --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg new file mode 100644 index 000000000..8d3f8d134 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg new file mode 100644 index 000000000..2de1a9e34 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg @@ -0,0 +1,20 @@ + + + +]> + + + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg new file mode 100644 index 000000000..a977f4a86 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg @@ -0,0 +1,20 @@ + + + +]> + + + + + diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/menupalette.js b/activities/GetThingsDone.activity/lib/sugar-web/graphics/menupalette.js new file mode 100644 index 000000000..df61cb2a7 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/menupalette.js @@ -0,0 +1,64 @@ +define(["sugar-web/graphics/palette", "mustache"], function (palette, mustache) { + + var menupalette = {}; + + menupalette.MenuPalette = function (invoker, primaryText, menuData) { + palette.Palette.call(this, invoker, primaryText); + + this.selectItemEvent = new CustomEvent( + "selectItem", { + detail: { + item: undefined + }, + bubbles: true, + cancelable: true + }); + + this.template = + '{{#.}}' + + '
  • ' + + '{{ #icon }}{{ /icon }}' + + '{{ label }}
  • ' + + '{{/.}}'; + + var menuElem = document.createElement('ul'); + menuElem.className = "menu"; + menuElem.innerHTML = mustache.render(this.template, menuData); + this.setContent([menuElem]); + + // Pop-down the palette when a item in the menu is clicked. + + this.buttons = menuElem.querySelectorAll('button'); + + var that = this; + + function popDownOnButtonClick(event) { + that.selectItemEvent.detail.target = event.target; + that.getPalette().dispatchEvent(that.selectItemEvent); + that.popDown(); + } + + for (var i = 0; i < this.buttons.length; i++) { + this.buttons[i].addEventListener('click', popDownOnButtonClick); + } + }; + + var addEventListener = function (type, listener, useCapture) { + return this.getPalette().addEventListener(type, listener, useCapture); + }; + + menupalette.MenuPalette.prototype = + Object.create(palette.Palette.prototype, { + addEventListener: { + value: addEventListener, + enumerable: true, + configurable: true, + writable: true + } + }); + + return menupalette; +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/palette.js b/activities/GetThingsDone.activity/lib/sugar-web/graphics/palette.js new file mode 100644 index 000000000..9a194a148 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/palette.js @@ -0,0 +1,179 @@ +define(function () { + var palettesGroup = []; + + function getOffset(elem) { + // Ugly hack to consider the palette margin. + var style = elem.currentStyle || window.getComputedStyle(elem, ''); + + // Remove 'px' from the strings. + var x = -2 * style.marginLeft.slice(0, -2); + var y = -1 * style.marginTop.slice(0, -2); + + var rect = elem.getBoundingClientRect(); + x += rect.left; + y += rect.top; + return { + top: y, + left: x, + width: rect.width, + height: rect.height + }; + } + + var palette = {}; + + palette.Palette = function (invoker, primaryText) { + this.invoker = invoker; + if (this.invoker.classList.contains("toolbutton")) { + this.invoker.classList.add("invoker"); + } + this.primaryText = primaryText; + var paletteElem; + var wrapperElem; + var headerElem; + var headerSeparatorElem; + var containerElem; + var that = this; + palettesGroup.push(this); + + invoker.addEventListener('click', function (event) { + if (!that.invoker.classList.contains("toolbutton")) { + updatePosition(event.x, event.y); + } + that.toggle(); + }); + + function updatePosition(clickX, clickY) { + var paletteX; + var paletteY; + + if (typeof (clickX) !== 'undefined' && + typeof (clickY) !== 'undefined') { + paletteX = clickX; + paletteY = clickY; + } else { + var invokerOffset = getOffset(that.invoker); + paletteX = invokerOffset.left; + paletteY = invokerOffset.top; + } + + paletteElem.style.left = paletteX + "px"; + paletteElem.style.top = paletteY + "px"; + } + + // A palette element can have a header, content, one or both. + + function createPaletteElement() { + if (paletteElem !== undefined) { + return; + } + paletteElem = document.createElement('div'); + paletteElem.className = "palette"; + paletteElem.style.visibility = "hidden"; + document.body.appendChild(paletteElem); + + if (that.invoker.classList.contains("toolbutton")) { + invokerElem = document.createElement('div'); + invokerElem.className = "palette-invoker"; + var style = that.invoker.currentStyle || + window.getComputedStyle(that.invoker, ''); + invokerElem.style.backgroundImage = style.backgroundImage; + + invokerElem.addEventListener('click', function (e) { + that.toggle(); + }); + + paletteElem.appendChild(invokerElem); + + } + + wrapperElem = document.createElement('div'); + wrapperElem.className = "wrapper"; + paletteElem.appendChild(wrapperElem); + + if (that.primaryText !== undefined) { + headerElem = document.createElement('div'); + headerElem.className = "header"; + headerElem.innerText = that.primaryText; + wrapperElem.appendChild(headerElem); + } + + headerSeparatorElem = document.createElement('hr'); + headerSeparatorElem.className = "header-separator"; + headerSeparatorElem.style.display = "none"; + wrapperElem.appendChild(headerSeparatorElem); + + containerElem = document.createElement('div'); + containerElem.className = "container"; + wrapperElem.appendChild(containerElem); + + updatePosition(); + } + + this.getPalette = function () { + if (paletteElem === undefined) { + createPaletteElement(); + } + return paletteElem; + }; + + this.setContent = function (elementsList) { + if (paletteElem === undefined) { + createPaletteElement(); + } + + (function removePreviousContent() { + for (var i = 0; i < containerElem.children.length; i++) { + var child = containerElem.children[i]; + containerElem.removeChild(child); + } + }()); + + (function addNewContent() { + for (var i = 0; i < elementsList.length; i++) { + var child = elementsList[i]; + containerElem.appendChild(child); + } + }()); + + // The header separator will be visible only if there are + // both, header and content. + if (elementsList.length > 0 && this.primaryText !== undefined) { + headerSeparatorElem.style.display = "block"; + } else { + headerSeparatorElem.style.display = "none"; + } + }; + + this.isDown = function () { + return paletteElem === undefined || + paletteElem.style.visibility == "hidden"; + }; + + }; + + palette.Palette.prototype.popUp = function () { + for (var i = 0; i < palettesGroup.length; i++) { + otherPalette = palettesGroup[i]; + if (otherPalette != this) { + otherPalette.popDown(); + } + } + this.getPalette().style.visibility = "visible"; + }; + + palette.Palette.prototype.popDown = function () { + this.getPalette().style.visibility = "hidden"; + }; + + palette.Palette.prototype.toggle = function () { + if (this.isDown()) { + this.popUp(); + } else { + this.popDown(); + } + }; + + return palette; + +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/radiobuttonsgroup.js b/activities/GetThingsDone.activity/lib/sugar-web/graphics/radiobuttonsgroup.js new file mode 100644 index 000000000..712681bff --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/radiobuttonsgroup.js @@ -0,0 +1,59 @@ +define(function () { + var radioButtonsGroup = {}; + + // ## RadioButtonsGroup + // + // A group of elements where only one can be active at the same + // time. + // + // When an element is clicked, it becomes the active one. The + // active element gains the 'active' CSS class. + // + // Parameters: + // + // * **elems** Array of elements of the group. + radioButtonsGroup.RadioButtonsGroup = function (elems) { + this.elems = elems; + var active; + + for (i = 0; i < elems.length; i++) { + var elem = elems[i]; + elem.addEventListener("click", clickHandler); + + // The first element that has 'active' CSS class is made + // the active of the group on startup. + if (active === undefined && elem.classList.contains('active')) { + active = elem; + } + } + + // If no element has 'active' CSS class, the first element of + // the array is made the active. + if (active === undefined) { + active = elems[0]; + updateClasses(); + } + + function clickHandler(evt) { + active = evt.target; + updateClasses(); + } + + function updateClasses() { + for (i = 0; i < elems.length; i++) { + var elem = elems[i]; + elem.classList.remove('active'); + } + active.classList.add('active'); + } + + // Get the active element. + this.getActive = function () { + return active; + }; + + }; + + return radioButtonsGroup; + +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/graphics/xocolor.js b/activities/GetThingsDone.activity/lib/sugar-web/graphics/xocolor.js new file mode 100644 index 000000000..1184ffa5c --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/graphics/xocolor.js @@ -0,0 +1,729 @@ +define(function () { + + var xocolor = {}; + + xocolor.colors = [ + { + stroke: '#B20008', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#B20008' + }, + { + stroke: '#E6000A', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#E6000A' + }, + { + stroke: '#FFADCE', + fill: '#FF2B34' + }, + { + stroke: '#9A5200', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#FF2B34' + }, + { + stroke: '#807500', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#FF2B34' + }, + { + stroke: '#008009', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#FF2B34' + }, + { + stroke: '#00588C', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#FF2B34' + }, + { + stroke: '#5E008C', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#FF2B34' + }, + { + stroke: '#9A5200', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#9A5200' + }, + { + stroke: '#C97E00', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#C97E00' + }, + { + stroke: '#FFC169', + fill: '#FF8F00' + }, + { + stroke: '#807500', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#FF8F00' + }, + { + stroke: '#008009', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#FF8F00' + }, + { + stroke: '#00588C', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#FF8F00' + }, + { + stroke: '#5E008C', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#5E008C' + }, + { + stroke: '#A700FF', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#A700FF' + }, + { + stroke: '#D1A3FF', + fill: '#FF8F00' + }, + { + stroke: '#B20008', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#FF8F00' + }, + { + stroke: '#807500', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#BE9E00' + }, + { + stroke: '#FFFA00', + fill: '#EDDE00' + }, + { + stroke: '#008009', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#008009' + }, + { + stroke: '#00EA11', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00EA11' + }, + { + stroke: '#8BFF7A', + fill: '#F8E800' + }, + { + stroke: '#00588C', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00588C' + }, + { + stroke: '#00A0FF', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00A0FF' + }, + { + stroke: '#BCCEFF', + fill: '#F8E800' + }, + { + stroke: '#5E008C', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#5E008C' + }, + { + stroke: '#AC32FF', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#AC32FF' + }, + { + stroke: '#D1A3FF', + fill: '#F8E800' + }, + { + stroke: '#B20008', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#F8E800' + }, + { + stroke: '#9A5200', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#F8E800' + }, + { + stroke: '#008009', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#00EA11' + }, + { + stroke: '#00588C', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#00EA11' + }, + { + stroke: '#5E008C', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#00EA11' + }, + { + stroke: '#B20008', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#00EA11' + }, + { + stroke: '#9A5200', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#00EA11' + }, + { + stroke: '#807500', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#00EA11' + }, + { + stroke: '#00588C', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#00A0FF' + }, + { + stroke: '#5E008C', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#5E008C' + }, + { + stroke: '#9900E6', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#9900E6' + }, + { + stroke: '#D1A3FF', + fill: '#00A0FF' + }, + { + stroke: '#B20008', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#00A0FF' + }, + { + stroke: '#9A5200', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#00A0FF' + }, + { + stroke: '#807500', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#00A0FF' + }, + { + stroke: '#008009', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#00A0FF' + }, + { + stroke: '#5E008C', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#AC32FF' + }, + { + stroke: '#B20008', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#AC32FF' + }, + { + stroke: '#9A5200', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#AC32FF' + }, + { + stroke: '#807500', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#AC32FF' + }, + { + stroke: '#008009', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#AC32FF' + }, + { + stroke: '#00588C', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#AC32FF' + } + ]; + + return xocolor; +}); diff --git a/activities/GetThingsDone.activity/lib/sugar-web/package.json b/activities/GetThingsDone.activity/lib/sugar-web/package.json new file mode 100644 index 000000000..000de4e20 --- /dev/null +++ b/activities/GetThingsDone.activity/lib/sugar-web/package.json @@ -0,0 +1,9 @@ +{ + "volo": { + "baseUrl": "lib", + "dependencies": { + "webL10n": "github:sugarlabs/webL10n", + "mustache": "github:janl/mustache.js/0.7.2" + } + } +} \ No newline at end of file diff --git a/activities/GetThingsDone.activity/lib/webL10n.js b/activities/GetThingsDone.activity/lib/webL10n.js new file mode 100644 index 000000000..f76d66a5e --- /dev/null +++ b/activities/GetThingsDone.activity/lib/webL10n.js @@ -0,0 +1,1029 @@ +/** + * Copyright (c) 2011-2013 Fabien Cazenave, Mozilla. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/*jshint browser: true, devel: true, es5: true, globalstrict: true */ +'use strict'; + +define(function (require) { + var gL10nData = {}; + var gTextData = ''; + var gTextProp = 'textContent'; + var gLanguage = ''; + var gMacros = {}; + var gReadyState = 'loading'; + + /** + * Synchronously loading l10n resources significantly minimizes flickering + * from displaying the app with non-localized strings and then updating the + * strings. Although this will block all script execution on this page, we + * expect that the l10n resources are available locally on flash-storage. + * + * As synchronous XHR is generally considered as a bad idea, we're still + * loading l10n resources asynchronously -- but we keep this in a setting, + * just in case... and applications using this library should hide their + * content until the `localized' event happens. + */ + + var gAsyncResourceLoading = true; // read-only + + + /** + * Debug helpers + * + * gDEBUG == 0: don't display any console message + * gDEBUG == 1: display only warnings, not logs + * gDEBUG == 2: display all console messages + */ + + var gDEBUG = 1; + + function consoleLog(message) { + if (gDEBUG >= 2) { + console.log('[l10n] ' + message); + } + }; + + function consoleWarn(message) { + if (gDEBUG) { + console.warn('[l10n] ' + message); + } + }; + + + /** + * DOM helpers for the so-called "HTML API". + * + * These functions are written for modern browsers. For old versions of IE, + * they're overridden in the 'startup' section at the end of this file. + */ + + function getL10nResourceLinks() { + return document.querySelectorAll('link[type="application/l10n"]'); + } + + function getL10nDictionary() { + var script = document.querySelector('script[type="application/l10n"]'); + // TODO: support multiple and external JSON dictionaries + return script ? JSON.parse(script.innerHTML) : null; + } + + function getTranslatableChildren(element) { + return element ? element.querySelectorAll('*[data-l10n-id]') : []; + } + + function getL10nAttributes(element) { + if (!element) + return {}; + + var l10nId = element.getAttribute('data-l10n-id'); + var l10nArgs = element.getAttribute('data-l10n-args'); + var args = {}; + if (l10nArgs) { + try { + args = JSON.parse(l10nArgs); + } catch (e) { + consoleWarn('could not parse arguments for #' + l10nId); + } + } + return { id: l10nId, args: args }; + } + + function fireL10nReadyEvent(lang) { + var evtObject = document.createEvent('Event'); + evtObject.initEvent('localized', true, false); + evtObject.language = lang; + document.dispatchEvent(evtObject); + } + + function xhrLoadText(url, onSuccess, onFailure, asynchronous) { + onSuccess = onSuccess || function _onSuccess(data) {}; + onFailure = onFailure || function _onFailure() { + consoleWarn(url + ' not found.'); + }; + + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, asynchronous); + if (xhr.overrideMimeType) { + xhr.overrideMimeType('text/plain; charset=utf-8'); + } + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + if (xhr.status == 200 || xhr.status === 0) { + onSuccess(xhr.responseText); + } else { + onFailure(); + } + } + }; + xhr.onerror = onFailure; + xhr.ontimeout = onFailure; + + // in Firefox OS with the app:// protocol, trying to XHR a non-existing + // URL will raise an exception here -- hence this ugly try...catch. + try { + xhr.send(null); + } catch (e) { + onFailure(); + } + } + + + /** + * l10n resource parser: + * - reads (async XHR) the l10n resource matching `lang'; + * - imports linked resources (synchronously) when specified; + * - parses the text data (fills `gL10nData' and `gTextData'); + * - triggers success/failure callbacks when done. + * + * @param {string} href + * URL of the l10n resource to parse. + * + * @param {string} lang + * locale (language) to parse. + * + * @param {Function} successCallback + * triggered when the l10n resource has been successully parsed. + * + * @param {Function} failureCallback + * triggered when the an error has occured. + * + * @return {void} + * uses the following global variables: gL10nData, gTextData, gTextProp. + */ + + function parseResource(href, lang, successCallback, failureCallback) { + var baseURL = href.replace(/[^\/]*$/, '') || './'; + + // handle escaped characters (backslashes) in a string + function evalString(text) { + if (text.lastIndexOf('\\') < 0) + return text; + return text.replace(/\\\\/g, '\\') + .replace(/\\n/g, '\n') + .replace(/\\r/g, '\r') + .replace(/\\t/g, '\t') + .replace(/\\b/g, '\b') + .replace(/\\f/g, '\f') + .replace(/\\{/g, '{') + .replace(/\\}/g, '}') + .replace(/\\"/g, '"') + .replace(/\\'/g, "'"); + } + + // parse *.properties text data into an l10n dictionary + function parseProperties(text) { + var dictionary = []; + + // token expressions + var reBlank = /^\s*|\s*$/; + var reComment = /^\s*#|^\s*$/; + var reSection = /^\s*\[(.*)\]\s*$/; + var reImport = /^\s*@import\s+url\((.*)\)\s*$/i; + var reSplit = /^([^=\s]*)\s*=\s*(.+)$/; // TODO: escape EOLs with '\' + + // parse the *.properties file into an associative array + function parseRawLines(rawText, extendedSyntax) { + var entries = rawText.replace(reBlank, '').split(/[\r\n]+/); + var currentLang = '*'; + var genericLang = lang.replace(/-[a-z]+$/i, ''); + var skipLang = false; + var match = ''; + + for (var i = 0; i < entries.length; i++) { + var line = entries[i]; + + // comment or blank line? + if (reComment.test(line)) + continue; + + // the extended syntax supports [lang] sections and @import rules + if (extendedSyntax) { + if (reSection.test(line)) { // section start? + match = reSection.exec(line); + currentLang = match[1]; + skipLang = (currentLang !== '*') && + (currentLang !== lang) && (currentLang !== genericLang); + continue; + } else if (skipLang) { + continue; + } + if (reImport.test(line)) { // @import rule? + match = reImport.exec(line); + loadImport(baseURL + match[1]); // load the resource synchronously + } + } + + // key-value pair + var tmp = line.match(reSplit); + if (tmp && tmp.length == 3) { + dictionary[tmp[1]] = evalString(tmp[2]); + } + } + } + + // import another *.properties file + function loadImport(url) { + xhrLoadText(url, function(content) { + parseRawLines(content, false); // don't allow recursive imports + }, null, false); // load synchronously + } + + // fill the dictionary + parseRawLines(text, true); + return dictionary; + } + + // load and parse l10n data (warning: global variables are used here) + xhrLoadText(href, function(response) { + gTextData += response; // mostly for debug + + // parse *.properties text data into an l10n dictionary + var data = parseProperties(response); + + // find attribute descriptions, if any + for (var key in data) { + var id, prop, index = key.lastIndexOf('.'); + if (index > 0) { // an attribute has been specified + id = key.substring(0, index); + prop = key.substr(index + 1); + } else { // no attribute: assuming text content by default + id = key; + prop = gTextProp; + } + if (!gL10nData[id]) { + gL10nData[id] = {}; + } + gL10nData[id][prop] = data[key]; + } + + // trigger callback + if (successCallback) { + successCallback(); + } + }, failureCallback, gAsyncResourceLoading); + }; + + // load and parse all resources for the specified locale + function loadLocale(lang, callback) { + callback = callback || function _callback() {}; + + clear(); + gLanguage = lang; + + // check all nodes + // and load the resource files + var langLinks = getL10nResourceLinks(); + var langCount = langLinks.length; + if (langCount == 0) { + // we might have a pre-compiled dictionary instead + var dict = getL10nDictionary(); + if (dict && dict.locales && dict.default_locale) { + consoleLog('using the embedded JSON directory, early way out'); + gL10nData = dict.locales[lang] || dict.locales[dict.default_locale]; + callback(); + } else { + consoleLog('no resource to load, early way out'); + } + // early way out + fireL10nReadyEvent(lang); + gReadyState = 'complete'; + return; + } + + // start the callback when all resources are loaded + var onResourceLoaded = null; + var gResourceCount = 0; + onResourceLoaded = function() { + gResourceCount++; + if (gResourceCount >= langCount) { + callback(); + fireL10nReadyEvent(lang); + gReadyState = 'complete'; + } + }; + + // load all resource files + function l10nResourceLink(link) { + var href = link.href; + var type = link.type; + this.load = function(lang, callback) { + var applied = lang; + parseResource(href, lang, callback, function() { + consoleWarn(href + ' not found.'); + applied = ''; + }); + return applied; // return lang if found, an empty string if not found + }; + } + + for (var i = 0; i < langCount; i++) { + var resource = new l10nResourceLink(langLinks[i]); + var rv = resource.load(lang, onResourceLoaded); + if (rv != lang) { // lang not found, used default resource instead + consoleWarn('"' + lang + '" resource not found'); + gLanguage = ''; + } + } + } + + // clear all l10n data + function clear() { + gL10nData = {}; + gTextData = ''; + gLanguage = ''; + // TODO: clear all non predefined macros. + // There's no such macro /yet/ but we're planning to have some... + } + + + /** + * Get rules for plural forms (shared with JetPack), see: + * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p + * + * @param {string} lang + * locale (language) used. + * + * @return {Function} + * returns a function that gives the plural form name for a given integer: + * var fun = getPluralRules('en'); + * fun(1) -> 'one' + * fun(0) -> 'other' + * fun(1000) -> 'other'. + */ + + function getPluralRules(lang) { + var locales2rules = { + 'af': 3, + 'ak': 4, + 'am': 4, + 'ar': 1, + 'asa': 3, + 'az': 0, + 'be': 11, + 'bem': 3, + 'bez': 3, + 'bg': 3, + 'bh': 4, + 'bm': 0, + 'bn': 3, + 'bo': 0, + 'br': 20, + 'brx': 3, + 'bs': 11, + 'ca': 3, + 'cgg': 3, + 'chr': 3, + 'cs': 12, + 'cy': 17, + 'da': 3, + 'de': 3, + 'dv': 3, + 'dz': 0, + 'ee': 3, + 'el': 3, + 'en': 3, + 'eo': 3, + 'es': 3, + 'et': 3, + 'eu': 3, + 'fa': 0, + 'ff': 5, + 'fi': 3, + 'fil': 4, + 'fo': 3, + 'fr': 5, + 'fur': 3, + 'fy': 3, + 'ga': 8, + 'gd': 24, + 'gl': 3, + 'gsw': 3, + 'gu': 3, + 'guw': 4, + 'gv': 23, + 'ha': 3, + 'haw': 3, + 'he': 2, + 'hi': 4, + 'hr': 11, + 'hu': 0, + 'id': 0, + 'ig': 0, + 'ii': 0, + 'is': 3, + 'it': 3, + 'iu': 7, + 'ja': 0, + 'jmc': 3, + 'jv': 0, + 'ka': 0, + 'kab': 5, + 'kaj': 3, + 'kcg': 3, + 'kde': 0, + 'kea': 0, + 'kk': 3, + 'kl': 3, + 'km': 0, + 'kn': 0, + 'ko': 0, + 'ksb': 3, + 'ksh': 21, + 'ku': 3, + 'kw': 7, + 'lag': 18, + 'lb': 3, + 'lg': 3, + 'ln': 4, + 'lo': 0, + 'lt': 10, + 'lv': 6, + 'mas': 3, + 'mg': 4, + 'mk': 16, + 'ml': 3, + 'mn': 3, + 'mo': 9, + 'mr': 3, + 'ms': 0, + 'mt': 15, + 'my': 0, + 'nah': 3, + 'naq': 7, + 'nb': 3, + 'nd': 3, + 'ne': 3, + 'nl': 3, + 'nn': 3, + 'no': 3, + 'nr': 3, + 'nso': 4, + 'ny': 3, + 'nyn': 3, + 'om': 3, + 'or': 3, + 'pa': 3, + 'pap': 3, + 'pl': 13, + 'ps': 3, + 'pt': 3, + 'rm': 3, + 'ro': 9, + 'rof': 3, + 'ru': 11, + 'rwk': 3, + 'sah': 0, + 'saq': 3, + 'se': 7, + 'seh': 3, + 'ses': 0, + 'sg': 0, + 'sh': 11, + 'shi': 19, + 'sk': 12, + 'sl': 14, + 'sma': 7, + 'smi': 7, + 'smj': 7, + 'smn': 7, + 'sms': 7, + 'sn': 3, + 'so': 3, + 'sq': 3, + 'sr': 11, + 'ss': 3, + 'ssy': 3, + 'st': 3, + 'sv': 3, + 'sw': 3, + 'syr': 3, + 'ta': 3, + 'te': 3, + 'teo': 3, + 'th': 0, + 'ti': 4, + 'tig': 3, + 'tk': 3, + 'tl': 4, + 'tn': 3, + 'to': 0, + 'tr': 0, + 'ts': 3, + 'tzm': 22, + 'uk': 11, + 'ur': 3, + 've': 3, + 'vi': 0, + 'vun': 3, + 'wa': 4, + 'wae': 3, + 'wo': 0, + 'xh': 3, + 'xog': 3, + 'yo': 0, + 'zh': 0, + 'zu': 3 + }; + + // utility functions for plural rules methods + function isIn(n, list) { + return list.indexOf(n) !== -1; + } + function isBetween(n, start, end) { + return start <= n && n <= end; + } + + // list of all plural rules methods: + // map an integer to the plural form name to use + var pluralRules = { + '0': function(n) { + return 'other'; + }, + '1': function(n) { + if ((isBetween((n % 100), 3, 10))) + return 'few'; + if (n === 0) + return 'zero'; + if ((isBetween((n % 100), 11, 99))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '2': function(n) { + if (n !== 0 && (n % 10) === 0) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '3': function(n) { + if (n == 1) + return 'one'; + return 'other'; + }, + '4': function(n) { + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '5': function(n) { + if ((isBetween(n, 0, 2)) && n != 2) + return 'one'; + return 'other'; + }, + '6': function(n) { + if (n === 0) + return 'zero'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '7': function(n) { + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '8': function(n) { + if ((isBetween(n, 3, 6))) + return 'few'; + if ((isBetween(n, 7, 10))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '9': function(n) { + if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '10': function(n) { + if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) + return 'few'; + if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19))) + return 'one'; + return 'other'; + }, + '11': function(n) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if ((n % 10) === 0 || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 11, 14))) + return 'many'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '12': function(n) { + if ((isBetween(n, 2, 4))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '13': function(n) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if (n != 1 && (isBetween((n % 10), 0, 1)) || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 12, 14))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '14': function(n) { + if ((isBetween((n % 100), 3, 4))) + return 'few'; + if ((n % 100) == 2) + return 'two'; + if ((n % 100) == 1) + return 'one'; + return 'other'; + }, + '15': function(n) { + if (n === 0 || (isBetween((n % 100), 2, 10))) + return 'few'; + if ((isBetween((n % 100), 11, 19))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '16': function(n) { + if ((n % 10) == 1 && n != 11) + return 'one'; + return 'other'; + }, + '17': function(n) { + if (n == 3) + return 'few'; + if (n === 0) + return 'zero'; + if (n == 6) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '18': function(n) { + if (n === 0) + return 'zero'; + if ((isBetween(n, 0, 2)) && n !== 0 && n != 2) + return 'one'; + return 'other'; + }, + '19': function(n) { + if ((isBetween(n, 2, 10))) + return 'few'; + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '20': function(n) { + if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !( + isBetween((n % 100), 10, 19) || + isBetween((n % 100), 70, 79) || + isBetween((n % 100), 90, 99) + )) + return 'few'; + if ((n % 1000000) === 0 && n !== 0) + return 'many'; + if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92])) + return 'two'; + if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91])) + return 'one'; + return 'other'; + }, + '21': function(n) { + if (n === 0) + return 'zero'; + if (n == 1) + return 'one'; + return 'other'; + }, + '22': function(n) { + if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) + return 'one'; + return 'other'; + }, + '23': function(n) { + if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) + return 'one'; + return 'other'; + }, + '24': function(n) { + if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) + return 'few'; + if (isIn(n, [2, 12])) + return 'two'; + if (isIn(n, [1, 11])) + return 'one'; + return 'other'; + } + }; + + // return a function that gives the plural form name for a given integer + var index = locales2rules[lang.replace(/-.*$/, '')]; + if (!(index in pluralRules)) { + consoleWarn('plural form unknown for [' + lang + ']'); + return function() { return 'other'; }; + } + return pluralRules[index]; + } + + // pre-defined 'plural' macro + gMacros.plural = function(str, param, key, prop) { + var n = parseFloat(param); + if (isNaN(n)) + return str; + + // TODO: support other properties (l20n still doesn't...) + if (prop != gTextProp) + return str; + + // initialize _pluralRules + if (!gMacros._pluralRules) { + gMacros._pluralRules = getPluralRules(gLanguage); + } + var index = '[' + gMacros._pluralRules(n) + ']'; + + // try to find a [zero|one|two] key if it's defined + if (n === 0 && (key + '[zero]') in gL10nData) { + str = gL10nData[key + '[zero]'][prop]; + } else if (n == 1 && (key + '[one]') in gL10nData) { + str = gL10nData[key + '[one]'][prop]; + } else if (n == 2 && (key + '[two]') in gL10nData) { + str = gL10nData[key + '[two]'][prop]; + } else if ((key + index) in gL10nData) { + str = gL10nData[key + index][prop]; + } else if ((key + '[other]') in gL10nData) { + str = gL10nData[key + '[other]'][prop]; + } + + return str; + }; + + + /** + * l10n dictionary functions + */ + + // fetch an l10n object, warn if not found, apply `args' if possible + function getL10nData(key, args) { + var data = gL10nData[key]; + if (!data) { + consoleWarn('#' + key + ' missing for [' + gLanguage + ']'); + } + + /** This is where l10n expressions should be processed. + * The plan is to support C-style expressions from the l20n project; + * until then, only two kinds of simple expressions are supported: + * {[ index ]} and {{ arguments }}. + */ + var rv = {}; + for (var prop in data) { + var str = data[prop]; + str = substIndexes(str, args, key, prop); + str = substArguments(str, args); + rv[prop] = str; + } + return rv; + } + + // replace {[macros]} with their values + function substIndexes(str, args, key, prop) { + var reIndex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)\s*\]\}/; + var reMatch = reIndex.exec(str); + if (!reMatch || !reMatch.length) + return str; + + // an index/macro has been found + // Note: at the moment, only one parameter is supported + var macroName = reMatch[1]; + var paramName = reMatch[2]; + var param; + if (args && paramName in args) { + param = args[paramName]; + } else if (paramName in gL10nData) { + param = gL10nData[paramName]; + } + + // there's no macro parser yet: it has to be defined in gMacros + if (macroName in gMacros) { + var macro = gMacros[macroName]; + str = macro(str, param, key, prop); + } + return str; + } + + // replace {{arguments}} with their values + function substArguments(str, args) { + var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/; + var match = reArgs.exec(str); + while (match) { + if (!match || match.length < 2) + return str; // argument key not found + + var arg = match[1]; + var sub = ''; + if (arg in args) { + sub = args[arg]; + } else if (arg in gL10nData) { + sub = gL10nData[arg][gTextProp]; + } else { + consoleWarn('could not find argument {{' + arg + '}}'); + return str; + } + + str = str.substring(0, match.index) + sub + + str.substr(match.index + match[0].length); + match = reArgs.exec(str); + } + return str; + } + + // translate an HTML element + function translateElement(element) { + var l10n = getL10nAttributes(element); + if (!l10n.id) + return; + + // get the related l10n object + var data = getL10nData(l10n.id, l10n.args); + if (!data) { + consoleWarn('#' + l10n.id + ' missing for [' + gLanguage + ']'); + return; + } + + // translate element (TODO: security checks?) + // for the node content, replace the content of the first child textNode + // and clear other child textNodes + if (data[gTextProp]) { // XXX + if (getChildElementCount(element) === 0) { + element[gTextProp] = data[gTextProp]; + } else { + var children = element.childNodes, + found = false; + for (var i = 0, l = children.length; i < l; i++) { + if (children[i].nodeType === 3 && + /\S/.test(children[i].textContent)) { // XXX + // using nodeValue seems cross-browser + if (found) { + children[i].nodeValue = ''; + } else { + children[i].nodeValue = data[gTextProp]; + found = true; + } + } + } + if (!found) { + consoleWarn('unexpected error, could not translate element content'); + } + } + delete data[gTextProp]; + } + + for (var k in data) { + element[k] = data[k]; + } + } + + // webkit browsers don't currently support 'children' on SVG elements... + function getChildElementCount(element) { + if (element.children) { + return element.children.length; + } + if (typeof element.childElementCount !== 'undefined') { + return element.childElementCount; + } + var count = 0; + for (var i = 0; i < element.childNodes.length; i++) { + count += element.nodeType === 1 ? 1 : 0; + } + return count; + } + + // translate an HTML subtree + function translateFragment(element) { + element = element || document.documentElement; + + // check all translatable children (= w/ a `data-l10n-id' attribute) + var children = getTranslatableChildren(element); + var elementCount = children.length; + for (var i = 0; i < elementCount; i++) { + translateElement(children[i]); + } + + // translate element itself if necessary + translateElement(element); + } + + // Startup & Public API + + function l10nStartup() { + gReadyState = 'interactive'; + consoleLog('loading [' + navigator.language + '] resources, ' + + (gAsyncResourceLoading ? 'asynchronously.' : 'synchronously.')); + + // load the default locale and translate the document if required + if (document.documentElement.lang === navigator.language) { + loadLocale(navigator.language); + } else { + loadLocale(navigator.language, translateFragment); + } + } + + // public API + var l10n = { + start: function() { + if (document.readyState === 'complete' || + document.readyState === 'interactive') { + window.setTimeout(l10nStartup); + } else { + document.addEventListener('DOMContentLoaded', l10nStartup); + } + }, + + // get a localized string + get: function l10n_get(key, args, fallback) { + var data = getL10nData(key, args) || fallback; + if (data) { + return 'textContent' in data ? data.textContent : ''; + } + return '{{' + key + '}}'; + }, + + // get|set the document language and direction + get language() { + return { + // get|set the document language (ISO-639-1) + get code() { return gLanguage; }, + set code(lang) { loadLocale(lang, translateFragment); }, + + // get the direction (ltr|rtl) of the current language + get direction() { + // http://www.w3.org/International/questions/qa-scripts + // Arabic, Hebrew, Farsi, Pashto, Urdu + var rtlList = ['ar', 'he', 'fa', 'ps', 'ur']; + return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr'; + } + }; + }, + + // translate an element or document fragment + translate: translateFragment, + + // get (a clone of) the dictionary for the current locale + get dictionary() { return JSON.parse(JSON.stringify(gL10nData)); }, + + // this can be used to prevent race conditions + get readyState() { return gReadyState; }, + ready: function l10n_ready(callback) { + if (!callback) + return; + + if (gReadyState == 'complete') { + window.setTimeout(callback); + } else { + window.addEventListener('localized', callback); + } + } + }; + + return l10n; +}); diff --git a/activities/GetThingsDone.activity/package.json b/activities/GetThingsDone.activity/package.json new file mode 100644 index 000000000..4361fdc35 --- /dev/null +++ b/activities/GetThingsDone.activity/package.json @@ -0,0 +1,11 @@ +{ + "amd": {}, + "volo": { + "baseUrl": "lib", + "dependencies": { + "sugar-web": "github:sugarlabs/sugar-web/master", + "domReady": "github:requirejs/domReady/2.0.1", + "webL10n": "github:sugarlabs/webL10n/master" + } + } +} \ No newline at end of file diff --git a/activities/GetThingsDone.activity/setup.py b/activities/GetThingsDone.activity/setup.py new file mode 100644 index 000000000..ad218b212 --- /dev/null +++ b/activities/GetThingsDone.activity/setup.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +from sugar3.activity import bundlebuilder + +bundlebuilder.start() diff --git a/activities/LastOneLoses.activity/activity/activity-icon.svg b/activities/LastOneLoses.activity/activity/activity-icon.svg new file mode 100644 index 000000000..dad571af1 --- /dev/null +++ b/activities/LastOneLoses.activity/activity/activity-icon.svg @@ -0,0 +1,111 @@ + + + +]> + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/activities/LastOneLoses.activity/activity/activity.info b/activities/LastOneLoses.activity/activity/activity.info new file mode 100644 index 000000000..63767b0a8 --- /dev/null +++ b/activities/LastOneLoses.activity/activity/activity.info @@ -0,0 +1,6 @@ +[Activity] +name = Last One Loses Activity +activity_version = 1 +bundle_id = org.olpc-france.LOLActivity +exec = sugar-activity-web +icon = activity-icon diff --git a/activities/LastOneLoses.activity/audio/applause.ogg b/activities/LastOneLoses.activity/audio/applause.ogg new file mode 100644 index 0000000000000000000000000000000000000000..b0f7f295a7c00cd749524de82ac5e07770f72f35 GIT binary patch literal 80234 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 F$_9Ny;oJZK literal 0 HcmV?d00001 diff --git a/activities/LastOneLoses.activity/audio/disappointed.ogg b/activities/LastOneLoses.activity/audio/disappointed.ogg new file mode 100644 index 0000000000000000000000000000000000000000..31722dec1a95fd8cd36389a0b250cb8edb0ae6e1 GIT binary patch literal 37748 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 literal 0 HcmV?d00001 diff --git a/activities/LastOneLoses.activity/css/activity.css b/activities/LastOneLoses.activity/css/activity.css new file mode 100644 index 000000000..d7d2a0b31 --- /dev/null +++ b/activities/LastOneLoses.activity/css/activity.css @@ -0,0 +1,26 @@ +#main-toolbar #activity-button { + background-image: url(../activity/activity-icon.svg); +} + +#main-toolbar #new-game-button { + margin-left: 40px; + background-image: url(../icons/new-game-icon.svg); +} + +#main-toolbar #level-easy-button { + margin-left: 40px; + background-image: url(../icons/level-easy.svg); +} + +#main-toolbar #level-medium-button { + background-image: url(../icons/level-medium.svg); +} + +#main-toolbar #level-hard-button { + background-image: url(../icons/level-hard.svg); +} + +#main-toolbar #switch-player-button { + margin-left: 40px; + background-image: url(../icons/switch-player.svg); +} diff --git a/activities/LastOneLoses.activity/css/styles.css b/activities/LastOneLoses.activity/css/styles.css new file mode 100644 index 000000000..986e065ff --- /dev/null +++ b/activities/LastOneLoses.activity/css/styles.css @@ -0,0 +1,101 @@ + + +.level { + display: inline-block; + margin-left: 20px; + margin-bottom: 20px; +} + +.player { + display: inline-block; + margin-left: 200px; + margin-bottom: 20px; +} + +.playboard { + display: inline-block; + margin-top: 100px; +} + +.player-image { + display: inline-block; + width: 200px; + height: 565px; + background-image: url(../icons/player-icon.svg); + background-repeat: no-repeat; + background-position: center; +} + +.computer-image { + display: inline-block; + width: 200px; + height: 565px; + background-image: url(../icons/computer-icon.svg); + background-repeat: no-repeat; + background-position: center; +} + +.empty-image { + background-image: url(../icons/empty-icon.svg); +} + +.switch { + display: inline-block; + margin-left: 20px; + margin-bottom: 20px; +} + +.play { + position: absolute; + left: 574px; + bottom: 40px; +} + +.renew { + position: absolute; + left: 400px; + bottom: 20px; + font-weight: bold; + font-size: 20px; +} + +.lol-box { + width: 800px; + display: inline-block; + text-align: center; +} + +.lol-item { + width: 35px; + height: 565px; + margin-top: 10px; + margin-left: 22px; + background-color: brown; + display: inline-block; + border-width: 1px; + border-style: inset; + border-color: darkgray; + box-shadow: 3px -4px 4px 2px black; +} + +.lol-item-selected { + background-color: blue; +} + +.end-message { + position: absolute; + left: 510px; + top: 320px; + width: 300px; + height: 300px; + background-repeat: no-repeat; + background-position: center; +} + +.end-message-win { + background-image: url(../icons/player-win.svg); +} + +.end-message-lost { + background-image: url(../icons/computer-win.svg); +} \ No newline at end of file diff --git a/activities/LastOneLoses.activity/icons/computer-icon.svg b/activities/LastOneLoses.activity/icons/computer-icon.svg new file mode 100644 index 000000000..1b386d814 --- /dev/null +++ b/activities/LastOneLoses.activity/icons/computer-icon.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/activities/LastOneLoses.activity/icons/computer-win.svg b/activities/LastOneLoses.activity/icons/computer-win.svg new file mode 100644 index 000000000..8f58fbce9 --- /dev/null +++ b/activities/LastOneLoses.activity/icons/computer-win.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/activities/LastOneLoses.activity/icons/empty-icon.svg b/activities/LastOneLoses.activity/icons/empty-icon.svg new file mode 100644 index 000000000..d8254c838 --- /dev/null +++ b/activities/LastOneLoses.activity/icons/empty-icon.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + diff --git a/activities/LastOneLoses.activity/icons/level-easy.svg b/activities/LastOneLoses.activity/icons/level-easy.svg new file mode 100644 index 000000000..9e3eb1583 --- /dev/null +++ b/activities/LastOneLoses.activity/icons/level-easy.svg @@ -0,0 +1,96 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/activities/LastOneLoses.activity/icons/level-hard.svg b/activities/LastOneLoses.activity/icons/level-hard.svg new file mode 100644 index 000000000..5b2443f4c --- /dev/null +++ b/activities/LastOneLoses.activity/icons/level-hard.svg @@ -0,0 +1,96 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/activities/LastOneLoses.activity/icons/level-medium.svg b/activities/LastOneLoses.activity/icons/level-medium.svg new file mode 100644 index 000000000..3c7aa2589 --- /dev/null +++ b/activities/LastOneLoses.activity/icons/level-medium.svg @@ -0,0 +1,91 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/activities/LastOneLoses.activity/icons/new-game-icon.svg b/activities/LastOneLoses.activity/icons/new-game-icon.svg new file mode 100644 index 000000000..18765762d --- /dev/null +++ b/activities/LastOneLoses.activity/icons/new-game-icon.svg @@ -0,0 +1,112 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/activities/LastOneLoses.activity/icons/play.png b/activities/LastOneLoses.activity/icons/play.png new file mode 100644 index 0000000000000000000000000000000000000000..0822b9e78c5f44132fdeb4d494d9c97857389dea GIT binary patch literal 1396 zcmV-)1&jKLP)WdLerbuJ(`AZ=lCb09J>F*6`CGCDFeIx{#RD=;xIFh(=8t^fc42XskI zMF-sk2onh;q2X!I00009a7bBm000XT000XT0n*)m`~Uy|1ZP1_K>z@;j|==^1poj5 zv`|b`MgRZ*(9qDVtgM%pmx6+VZEbB+Q&TxPIVB|}&CSjG`}@Me!gO?WF)=Y38X7Dt zEVQ(=^z`(wu&`xiWsHoBsi~U^@-(Fr` z>FMd)+uOdrzG7lxo}QjbNlB29kh{CPnwpx|*Vp3W;^yY&OiWDi@$uBu)I&oCp+ev-AG%b&!TaOKHpUM{LVSlO}0eMd8Wu zJ#Zh3sjg2K5BK|URDLPRU9&0iZ;8dP{avBk7%pZpwaaJii9k;o>oA<5 zV+-u0iybDDKCDn!8UF@7)3qEa^&7DMa78J92GW}>DfJ6rT}iKbfOgVzddeqgvMDzn zLGSk_q|~FqzGZXGQB$WGDRnT2jG|?US{_YFigH757O{cCO20Hm8OU7M+^I+M>KQK8 z5TJL#tqqh|ESW6(h%7Sk7$_Vy^m2m0TM1GUM^Q{B4X9Z9FN~ zuH8b3MzI*ao@8%^cq^Il)XV+JQjk*+UHO}Fp0m$9^-=)_V^&&3IwN1bVpdgK{X~U4 zo#YLZ7EBVhRdoeVVS$PlW%V33Dqlf{Updpbfcbm;r>-*dz^`&wC1evZWL`tU2zCtK ztE4*hiWo$h!Kb_1F^#G(Jg<-$AYWBZLP;#A-dZ_f0@?-ll`-Xo!|0un%S5~@sDa3XV#H@Fv9h`98CRkVg8HhaYPj5ad!$FH)S$qkC#6w_25a4#qg=Wc z1uwIMrlheAS+9^jY6>OKH@W~ zOL1wV)_rx0?-_O+59=3~7W?{9*aEiwYBE1)m>WEVZ`F0_B28E6yA)@n7T*%C^=xjJ zBwTbm?d8gGR?Bv$TfW;#YXtF$-4tGgJ!L9)8Rq#Skm%B;WZRC;1Lfa{dFa%d}wTtveS00000 + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/activities/LastOneLoses.activity/icons/player-win.svg b/activities/LastOneLoses.activity/icons/player-win.svg new file mode 100644 index 000000000..817b9af98 --- /dev/null +++ b/activities/LastOneLoses.activity/icons/player-win.svg @@ -0,0 +1,119 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/activities/LastOneLoses.activity/icons/switch-player.svg b/activities/LastOneLoses.activity/icons/switch-player.svg new file mode 100644 index 000000000..65964c7a6 --- /dev/null +++ b/activities/LastOneLoses.activity/icons/switch-player.svg @@ -0,0 +1,99 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/activities/LastOneLoses.activity/index.html b/activities/LastOneLoses.activity/index.html new file mode 100644 index 000000000..4891518cc --- /dev/null +++ b/activities/LastOneLoses.activity/index.html @@ -0,0 +1,42 @@ + + + + + + Last One Loses Activity + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + +
    + + +
    +
    + + + diff --git a/activities/LastOneLoses.activity/js/activity.js b/activities/LastOneLoses.activity/js/activity.js new file mode 100644 index 000000000..c46fc3216 --- /dev/null +++ b/activities/LastOneLoses.activity/js/activity.js @@ -0,0 +1,33 @@ + +define(function (require) { + var activity = require("sugar-web/activity/activity"); + var radioButtonsGroup = require("sugar-web/graphics/radiobuttonsgroup"); + var app; + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + // Initialize the Sugar activity. + activity.setup(); + + // Initialize buttons + document.getElementById("new-game-button").onclick = function() { + app.doRenew(); + }; + var levelRadio = new radioButtonsGroup.RadioButtonsGroup([ + document.getElementById("level-easy-button"), + document.getElementById("level-medium-button"), + document.getElementById("level-hard-button")] + ); + document.getElementById("switch-player-button").onclick = function() { + app.switchPlayer(); + }; + + // Initialize the game + app = new LOLGameApp({activity: activity}); + app.load(); + app.renderInto(document.getElementById("canvas")); + + }); + +}); diff --git a/activities/LastOneLoses.activity/js/app.js b/activities/LastOneLoses.activity/js/app.js new file mode 100644 index 000000000..bbff16f5f --- /dev/null +++ b/activities/LastOneLoses.activity/js/app.js @@ -0,0 +1,227 @@ + +// Main app class +enyo.kind({ + name: "LOLGameApp", + kind: enyo.Control, + published: {size: 13, level: 1, count: 13, activity: null}, + components: [ + {classes: "playboard", components:[ + {name: "player", classes: "player-image"}, + {name: "box", classes: "lol-box", components: [ + ]}, + {name: "computer", classes: "computer-image"} + ]}, + {name: "playbutton", kind: "Image", src: "icons/play.png", classes: "play", ontap: "doPlay", showing: false}, + {name: "endaudio", kind: "HTML5.Audio", preload: "auto", autobuffer: true, controlsbar: false}, + {name: "endmessage", content: "", showing: false, classes: "end-message"} + ], + + // Constructor + create: function() { + this.inherited(arguments); + this.init(); + }, + + // Init game + init: function() { + // Init game context + this.game = new LOLGame(this.count); + if (this.count > 0) this.player = this.game.getPlayer(); + this.count = this.size; + this.selectedCount = 0; + + // Init color + this.activity.getXOColor(function(error, colors) {console.log(colors);}); + + // Init level + document.getElementById("level-easy-button").classList.remove('active'); + document.getElementById("level-medium-button").classList.remove('active'); + document.getElementById("level-hard-button").classList.remove('active'); + if (this.level == 1) document.getElementById("level-easy-button").classList.add('active'); + else if (this.level == 2) document.getElementById("level-medium-button").classList.add('active'); + else if (this.level == 3) document.getElementById("level-hard-button").classList.add('active'); + + // Draw board + this.drawBoard(); + }, + + // Redraw the board + drawBoard: function() { + // Clean board + this.selectedCount = 0; + var items = []; + enyo.forEach(this.$.box.getControls(), function(item) { + items.push(item); + }); + for (var i = 0 ; i < items.length ; i++) { + items[i].destroy(); + } + + // Redraw board + for (var i = 0 ; i < this.game.getLength() ; i++) { + this.$.box.createComponent( + { kind: "LOLItem", ontap: "selectItem" }, + { owner: this } + ).render(); + } + document.getElementById("switch-player-button").disabled = (this.game.getLength() != this.size); + this.showCurrentPlayer(); + + // Test end condition + if (this.game.endOfGame()) { + this.$.endmessage.addClass(this.game.getPlayer() != this.player ? "end-message-win" : "end-message-lost"); + this.$.endmessage.removeClass(this.game.getPlayer() != this.player ? "end-message-lost" : "end-message-win"); + this.$.endaudio.setSrc(this.game.getPlayer() != this.player ? "audio/applause.ogg" : "audio/disappointed.ogg"); + this.$.endaudio.play(); + this.$.endmessage.show(); + } + this.$.endmessage.setShowing(this.game.endOfGame()); + + // Play for computer + if (this.game.getPlayer() != this.player && !this.game.endOfGame()) + this.computerPlay(); + }, + + // Get current level + getLevel: function() { + if (document.getElementById("level-easy-button").classList.contains('active')) + return 1; + if (document.getElementById("level-medium-button").classList.contains('active')) + return 2; + if (document.getElementById("level-hard-button").classList.contains('active')) + return 3; + return 0; + }, + + // Show the current player turn + showCurrentPlayer: function() { + if (this.player == this.game.getPlayer() && !this.game.endOfGame()) + this.$.player.removeClass("empty-image"); + else + this.$.player.addClass("empty-image"); + if (this.player != this.game.getPlayer() && !this.game.endOfGame()) + this.$.computer.removeClass("empty-image"); + else + this.$.computer.addClass("empty-image"); + }, + + // Select an item + selectItem: function(item) { + if (this.player != this.game.getPlayer()) + return; + var value = item.getSelected(); + if (this.selectedCount == 3 && !value) { + this.$.playbutton.show(); + return; + } + this.selectedCount = !value ? this.selectedCount + 1 : this.selectedCount - 1; + item.setSelected(!value); + if (this.selectedCount > 0) + this.$.playbutton.show(); + else + this.$.playbutton.hide(); + }, + + // Switch player + switchPlayer: function() { + this.game.reverse(); + this.player = this.player % 2; + this.drawBoard(); + }, + + // Play for the player + doPlay: function() { + if (this.player != this.game.getPlayer()) + return; + if (this.selectedCount == 0) + return; + this.save(this.game.play(this.selectedCount)); + this.drawBoard(); + this.$.playbutton.hide(); + }, + + // Let's computer play + computerPlay: function() { + if (this.player == this.game.getPlayer()) + return; + this.selectedCount = 0; + this.step = 0; + this.timer = window.setInterval(enyo.bind(this, "doComputer"), 400+50*this.getLevel()); + }, + + // Play for the computer + doComputer: function() { + // First, think to the shot and select item + if (this.step == 0) { + this.step++; + var shot = this.game.think(this.getLevel()); + var context = this; + enyo.forEach(this.$.box.getControls(), function(item) { + if (context.selectedCount < shot) { + item.setSelected(true); + context.selectedCount++; + } + }); + this.step++; + } + + // Then play + else if (this.step == 2) { + window.clearInterval(this.timer); + this.save(this.game.play(this.selectedCount)); + this.drawBoard(); + } + }, + + // Start a new game + doRenew: function() { + this.level = this.getLevel(); + this.init(); + }, + + // Load game from datastore + load: function() { + var datastoreObject = this.activity.getDatastoreObject(); + var currentthis = this; + datastoreObject.loadAsText(function (error, metadata, data) { + var data = JSON.parse(data); + currentthis.size = data.size; + currentthis.count = data.count; + currentthis.level = data.level; + currentthis.player = data.player; + currentthis.init(); + }); + }, + + // Save game in datastore + save: function(count) { + var datastoreObject = this.activity.getDatastoreObject(); + var jsonData = JSON.stringify({size: this.size, count: count, level: this.getLevel(), player: this.game.getPlayer()}); + datastoreObject.setDataAsText(jsonData); + datastoreObject.save(function() {}); + } +}); + + +// Class for an item +enyo.kind({ + name: "LOLItem", + kind: enyo.Control, + classes: "lol-item", + published: { selected: false }, + + // Constructor + create: function() { + this.inherited(arguments); + this.selectedChanged(); + }, + + // Selection changed + selectedChanged: function() { + var className = "lol-item-selected"; + if (this.selected) + this.addClass(className); + else + this.removeClass(className); + }, +}); \ No newline at end of file diff --git a/activities/LastOneLoses.activity/js/audio.js b/activities/LastOneLoses.activity/js/audio.js new file mode 100644 index 000000000..b6b0fb835 --- /dev/null +++ b/activities/LastOneLoses.activity/js/audio.js @@ -0,0 +1,112 @@ + + +// Basic HTML 5 Audio Element encapsulation +enyo.kind({ + name: "HTML5.Audio", + kind: enyo.Control, + tag: "audio", + published: { + src: "", crossorigin: "", preload: "auto", + mediagroup: "", loop: false, muted: "", controlsbar: false + }, + events: { + onSoundEnded: "", + onSoundTimeupdate: "" + }, + + // Constructor + create: function() { + this.inherited(arguments); + + this.srcChanged(); + this.crossoriginChanged(); + this.preloadChanged(); + this.loopChanged(); + this.mutedChanged(); + this.controlsbarChanged(); + }, + + // Render + rendered: function() { + this.inherited(arguments); + + // Handle init + if (this.hasNode() != null) { + // Handle sound ended event + var audio = this; + enyo.dispatcher.listen(audio.hasNode(), "ended", function() { + audio.doSoundEnded(); + }); + enyo.dispatcher.listen(audio.hasNode(), "timeupdate", function(s) { + audio.doSoundTimeupdate({timeStamp: s.timeStamp}); + }); + } + }, + + // Property changed + srcChanged: function() { + this.setAttribute("src", this.src); + }, + + crossoriginChanged: function() { + this.setAttribute("crossorigin", this.crossorigin); + }, + + preloadChanged: function() { + this.setAttribute("preload", this.preload); + }, + + loopChanged: function() { + this.setAttribute("loop", this.loop); + }, + + mutedChanged: function() { + if (this.muted.length != 0) + this.setAttribute("muted", this.muted); + }, + + controlsbarChanged: function() { + this.setAttribute("controls", this.controlsbar); + }, + + // Test if component could play a file type + canPlayType: function(typename) { + var node = this.hasNode(); + if (node == null) + return false; + return node.canPlayType(typename); + }, + + // Play audio + play: function() { + var node = this.hasNode(); + if (node == null) + return; + node.play(); + }, + + // Pause audio + pause: function() { + var node = this.hasNode(); + if (node == null) + return; + node.pause(); + }, + + // Test if audio is paused + paused: function() { + var node = this.hasNode(); + if (node == null) + return false; + return node.paused; + }, + + // Test if audio is ended + ended: function() { + var node = this.hasNode(); + if (node == null) + return false; + return node.ended; + } +}); + diff --git a/activities/LastOneLoses.activity/js/loader.js b/activities/LastOneLoses.activity/js/loader.js new file mode 100644 index 000000000..01021e981 --- /dev/null +++ b/activities/LastOneLoses.activity/js/loader.js @@ -0,0 +1,8 @@ +requirejs.config({ + baseUrl: "lib", + paths: { + activity: "../js" + } +}); + +requirejs(["activity/activity"]); diff --git a/activities/LastOneLoses.activity/js/lolgame.js b/activities/LastOneLoses.activity/js/lolgame.js new file mode 100644 index 000000000..6312833f7 --- /dev/null +++ b/activities/LastOneLoses.activity/js/lolgame.js @@ -0,0 +1,65 @@ + +function LOLGame(size) { + // Set initial + var length = Math.max(size,0); + var player = 0; + + // Accessor + this.getLength = function() { + return length; + }; + this.getPlayer = function() { + return player; + }; + + // Change player, only at start + this.reverse = function() { + if (length != size) + return false; + player = (player + 1) % 2; + return true; + }; + + // Play + this.play = function(number) { + if (number === undefined || number == null || number < 1 || number > 3 || number > length) + return length; + length = length - number; + if (length > 0) + player = (player + 1) % 2; + return length; + }; + + // Test end of game + this.endOfGame = function() { + return length == 0; + }; + + // Think to the better next shot + this.think = function(level) { + // Level 1: try to leave at least one at end of game + if (level >= 1) { + if (length >= 2 && length <= 4) + return length-1; + } + + // Level 2: try to leave five + if (level >= 2) { + if (length >= 6 && length <= 8) + return length-5; + } + + // Level 3: always try to left the nearest multiple of 4+1 + if (level >= 3) { + var attemptsup = length-(Math.floor(length/4)*4+1); + if (attemptsup >=1 && attemptsup <= 3) + return attemptsup; + var attemptinf = length-((Math.floor(length/4)-1)*4+1); + if (attemptinf >=1 && attemptinf <= 3) + return attemptinf; + } + + // Simple case: randomly choose a number + return Math.floor(Math.random()*Math.min(3,length-1))+1; + }; +} \ No newline at end of file diff --git a/activities/LastOneLoses.activity/lib/domReady.js b/activities/LastOneLoses.activity/lib/domReady.js new file mode 100644 index 000000000..2b5412209 --- /dev/null +++ b/activities/LastOneLoses.activity/lib/domReady.js @@ -0,0 +1,129 @@ +/** + * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/domReady for details + */ +/*jslint */ +/*global require: false, define: false, requirejs: false, + window: false, clearInterval: false, document: false, + self: false, setInterval: false */ + + +define(function () { + 'use strict'; + + var isTop, testDiv, scrollIntervalId, + isBrowser = typeof window !== "undefined" && window.document, + isPageLoaded = !isBrowser, + doc = isBrowser ? document : null, + readyCalls = []; + + function runCallbacks(callbacks) { + var i; + for (i = 0; i < callbacks.length; i += 1) { + callbacks[i](doc); + } + } + + function callReady() { + var callbacks = readyCalls; + + if (isPageLoaded) { + //Call the DOM ready callbacks + if (callbacks.length) { + readyCalls = []; + runCallbacks(callbacks); + } + } + } + + /** + * Sets the page as loaded. + */ + function pageLoaded() { + if (!isPageLoaded) { + isPageLoaded = true; + if (scrollIntervalId) { + clearInterval(scrollIntervalId); + } + + callReady(); + } + } + + if (isBrowser) { + if (document.addEventListener) { + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. + document.addEventListener("DOMContentLoaded", pageLoaded, false); + window.addEventListener("load", pageLoaded, false); + } else if (window.attachEvent) { + window.attachEvent("onload", pageLoaded); + + testDiv = document.createElement('div'); + try { + isTop = window.frameElement === null; + } catch (e) {} + + //DOMContentLoaded approximation that uses a doScroll, as found by + //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, + //but modified by other contributors, including jdalton + if (testDiv.doScroll && isTop && window.external) { + scrollIntervalId = setInterval(function () { + try { + testDiv.doScroll(); + pageLoaded(); + } catch (e) {} + }, 30); + } + } + + //Check if document already complete, and if so, just trigger page load + //listeners. Latest webkit browsers also use "interactive", and + //will fire the onDOMContentLoaded before "interactive" but not after + //entering "interactive" or "complete". More details: + //http://dev.w3.org/html5/spec/the-end.html#the-end + //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded + //Hmm, this is more complicated on further use, see "firing too early" + //bug: https://github.com/requirejs/domReady/issues/1 + //so removing the || document.readyState === "interactive" test. + //There is still a window.onload binding that should get fired if + //DOMContentLoaded is missed. + if (document.readyState === "complete") { + pageLoaded(); + } + } + + /** START OF PUBLIC API **/ + + /** + * Registers a callback for DOM ready. If DOM is already ready, the + * callback is called immediately. + * @param {Function} callback + */ + function domReady(callback) { + if (isPageLoaded) { + callback(doc); + } else { + readyCalls.push(callback); + } + return domReady; + } + + domReady.version = '2.0.1'; + + /** + * Loader Plugin API method + */ + domReady.load = function (name, req, onLoad, config) { + if (config.isBuild) { + onLoad(null); + } else { + domReady(onLoad); + } + }; + + /** END OF PUBLIC API **/ + + return domReady; +}); diff --git a/activities/LastOneLoses.activity/lib/enyo/enyo.css b/activities/LastOneLoses.activity/lib/enyo/enyo.css new file mode 100644 index 000000000..6d05291ff --- /dev/null +++ b/activities/LastOneLoses.activity/lib/enyo/enyo.css @@ -0,0 +1,181 @@ + +/* ../source/dom/dom.css */ + +/* things we always want */ +body { + font-family: 'Helvetica Neue', 'Nimbus Sans L', Arial, sans-serif; +} + +/* allow hw-accelerated scrolling on platforms that support it */ +body.webkitOverflowScrolling { + -webkit-overflow-scrolling: touch; +} + +/* for apps */ +.enyo-document-fit { + margin: 0; + height: 100%; + /* note: giving html overflow: auto is odd and was only ever done to avoid duplication + however, android 4.04 sometimes does not hide nodes when their display is set to none + if document is overflow auto. + */ + position: relative; +} + +.enyo-body-fit { + margin: 0; + height: 100%; + /* helps prevent ios page scroll */ + overflow: auto; + position: relative; +} + +.enyo-no-touch-action { + -ms-touch-action: none; +} + +/* reset */ + +button { + font-size: inherit; + font-family: inherit; +} +button::-moz-focus-inner { + border: 0; + padding: 0; +} + +/* user selection */ + +.enyo-unselectable { + cursor: default; + -ms-user-select: none; + -webkit-user-select: none; + -moz-user-select: -moz-none; + user-select: none; +} + +.enyo-unselectable::selection, .enyo-unselectable ::selection { + color: transparent; +} + +.enyo-selectable { + cursor: auto; + -ms-user-select: element; + -webkit-user-select: text; + -moz-user-select: text; + user-select: text; +} + +.enyo-selectable::selection, .enyo-selectable ::selection { + background: #3297FD; + color: #FFF; +} + +/* layout */ + +body .enyo-fit { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; +} + +.enyo-clip { + overflow: hidden; +} + +.enyo-border-box { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* compositing */ + +.enyo-composite { + -webkit-transform: translateZ(0); + -moz-transform: translateZ(0); + -ms-transform: translateZ(0); + -o-transform: translateZ(0); + transform: translateZ(0); +} + + +/* ../source/touch/Thumb.css */ + +.enyo-thumb { + position: absolute; + -moz-box-sizing: border-box; + box-sizing: border-box; + border-radius: 4px; + background: #333; + border: 1px solid #666; + opacity: 0.75; + z-index: 1; +} + +.enyo-vthumb { + top: 0; + right: 2px; + width: 4px; +} + +.enyo-hthumb { + left: 0; + bottom: 2px; + height: 4px; +} + + +/* ../source/touch/Scroller.css */ + +.enyo-scroller { + position: relative; +} + +.enyo-fit.enyo-scroller { + position: absolute; +} + +.enyo-touch-scroller { + overflow: hidden; +} + +.enyo-touch-strategy-container { + overflow: hidden; +} + +.enyo-scrollee-fit { + height: 100%; +} + +/* ../source/ui/ui.css */ + +.enyo-inline, .enyo-tool-decorator { + display: inline-block; +} + +.enyo-children-inline > *, .enyo-tool-decorator > * { + display: inline-block; +} + +.enyo-children-middle > *, .enyo-tool-decorator > * { + vertical-align: middle; +} + +.enyo-positioned { + position: relative; +} + +.enyo-fill { + position: relative; + width: 100%; + height: 100%; +} + +.enyo-popup { + position: absolute; + z-index: 10; +} diff --git a/activities/LastOneLoses.activity/lib/enyo/enyo.js b/activities/LastOneLoses.activity/lib/enyo/enyo.js new file mode 100644 index 000000000..d15030ffd --- /dev/null +++ b/activities/LastOneLoses.activity/lib/enyo/enyo.js @@ -0,0 +1,4306 @@ + +// enyo.js + +(function() { +var e = "enyo.js"; +enyo = window.enyo || {}, enyo.locateScript = function(e) { +var t = document.getElementsByTagName("script"); +for (var n = t.length - 1, r, i, s = e.length; n >= 0 && (r = t[n]); n--) if (!r.located) { +i = r.getAttribute("src") || ""; +if (i.slice(-s) == e) return r.located = !0, { +path: i.slice(0, Math.max(0, i.lastIndexOf("/"))), +node: r +}; +} +}, enyo.args = enyo.args || {}; +var t = enyo.locateScript(e); +if (t) { +enyo.args.root = (enyo.args.root || t.path).replace("/source", ""); +for (var n = 0, r = t.node.attributes.length, i; n < r && (i = t.node.attributes.item(n)); n++) enyo.args[i.nodeName] = i.value; +} +})(); + +// ../../loader.js + +(function() { +enyo = window.enyo || {}, enyo.pathResolverFactory = function() { +this.paths = {}; +}, enyo.pathResolverFactory.prototype = { +addPath: function(e, t) { +return this.paths[e] = t; +}, +addPaths: function(e) { +if (e) for (var t in e) this.addPath(t, e[t]); +}, +includeTrailingSlash: function(e) { +return e && e.slice(-1) !== "/" ? e + "/" : e; +}, +rewritePattern: /\$([^\/\\]*)(\/)?/g, +rewrite: function(e) { +var t, n = this.includeTrailingSlash, r = this.paths, i = function(e, i) { +return t = !0, n(r[i]) || ""; +}, s = e; +do t = !1, s = s.replace(this.rewritePattern, i); while (t); +return s; +} +}, enyo.path = new enyo.pathResolverFactory, enyo.loaderFactory = function(e, t) { +this.machine = e, this.packages = [], this.modules = [], this.sheets = [], this.stack = [], this.pathResolver = t || enyo.path, this.packageName = "", this.packageFolder = "", this.finishCallbacks = {}; +}, enyo.loaderFactory.prototype = { +verbose: !1, +loadScript: function(e, t, n) { +this.machine.script(e, t, n); +}, +loadSheet: function(e) { +this.machine.sheet(e); +}, +loadPackage: function(e) { +this.machine.script(e); +}, +report: function() {}, +load: function() { +this.more({ +index: 0, +depends: arguments || [] +}); +}, +more: function(e) { +if (e && this.continueBlock(e)) return; +var t = this.stack.pop(); +t ? (enyo.runtimeLoading && e.failed && (t.failed = t.failed || [], t.failed.push.apply(t.failed, e.failed)), this.verbose && console.groupEnd("* finish package (" + (t.packageName || "anon") + ")"), this.packageFolder = t.folder, this.packageName = "", this.more(t)) : this.finish(e); +}, +finish: function(e) { +this.packageFolder = "", this.verbose && console.log("-------------- fini"); +for (var t in this.finishCallbacks) this.finishCallbacks[t] && (this.finishCallbacks[t](e), this.finishCallbacks[t] = null); +}, +continueBlock: function(e) { +while (e.index < e.depends.length) { +var t = e.depends[e.index++]; +if (t) if (typeof t == "string") { +if (this.require(t, e)) return !0; +} else this.pathResolver.addPaths(t); +} +}, +require: function(e, t) { +var n = this.pathResolver.rewrite(e), r = this.getPathPrefix(e); +n = r + n; +if (n.slice(-4) != ".css" && n.slice(-5) != ".less") return n.slice(-3) == ".js" && n.slice(-10) != "package.js" ? (this.verbose && console.log("+ module: [" + r + "][" + e + "]"), this.requireScript(e, n, t)) : (this.requirePackage(n, t), !0); +this.verbose && console.log("+ stylesheet: [" + r + "][" + e + "]"), this.requireStylesheet(n); +}, +getPathPrefix: function(e) { +var t = e.slice(0, 1); +return t != "/" && t != "\\" && t != "$" && !/^https?:/i.test(e) ? this.packageFolder : ""; +}, +requireStylesheet: function(e) { +this.sheets.push(e), this.loadSheet(e); +}, +requireScript: function(e, t, n) { +this.modules.push({ +packageName: this.packageName, +rawPath: e, +path: t +}); +if (enyo.runtimeLoading) { +var r = this, i = function() { +r.more(n); +}, s = function() { +n.failed = n.failed || [], n.failed.push(t), r.more(n); +}; +this.loadScript(t, i, s); +} else this.loadScript(t); +return enyo.runtimeLoading; +}, +decodePackagePath: function(e) { +var t = "", n = "", r = "", i = "package.js", s = e.replace(/\\/g, "/").replace(/\/\//g, "/").replace(/:\//, "://").split("/"), o, u; +if (s.length) { +var a = s.pop() || s.pop() || ""; +a.slice(-i.length) !== i ? s.push(a) : i = a, r = s.join("/"), r = r ? r + "/" : "", i = r + i; +for (o = s.length - 1; o >= 0; o--) if (s[o] == "source") { +s.splice(o, 1); +break; +} +n = s.join("/"); +for (o = s.length - 1; u = s[o]; o--) if (u == "lib" || u == "enyo") { +s = s.slice(o + 1); +break; +} +for (o = s.length - 1; u = s[o]; o--) (u == ".." || u == ".") && s.splice(o, 1); +t = s.join("-"); +} +return { +alias: t, +target: n, +folder: r, +manifest: i +}; +}, +aliasPackage: function(e) { +var t = this.decodePackagePath(e); +this.manifest = t.manifest, t.alias && (this.pathResolver.addPath(t.alias, t.target), this.packageName = t.alias, this.packages.push({ +name: t.alias, +folder: t.folder +})), this.packageFolder = t.folder; +}, +requirePackage: function(e, t) { +t.folder = this.packageFolder, this.aliasPackage(e), t.packageName = this.packageName, this.stack.push(t), this.report("loading package", this.packageName), this.verbose && console.group("* start package [" + this.packageName + "]"), this.loadPackage(this.manifest); +} +}; +})(); + +// boot.js + +enyo.execUnsafeLocalFunction = function(e) { +typeof MSApp == "undefined" ? e() : MSApp.execUnsafeLocalFunction(e); +}, enyo.machine = { +sheet: function(e) { +var t = "text/css", n = "stylesheet", r = e.slice(-5) == ".less"; +r && (window.less ? (t = "text/less", n = "stylesheet/less") : e = e.slice(0, e.length - 4) + "css"); +var i; +enyo.runtimeLoading || r ? (i = document.createElement("link"), i.href = e, i.media = "screen", i.rel = n, i.type = t, document.getElementsByTagName("head")[0].appendChild(i)) : (i = function() { +document.write(''); +}, enyo.execUnsafeLocalFunction(i)), r && window.less && (less.sheets.push(i), enyo.loader.finishCallbacks.lessRefresh || (enyo.loader.finishCallbacks.lessRefresh = function() { +less.refresh(!0); +})); +}, +script: function(e, t, n) { +if (!enyo.runtimeLoading) document.write(' + + + + + +
    + + + + + +
    + +
    + +
    +
    + +
    +
    + + +
    + + + diff --git a/activities/Markdown.activity/js/activity.js b/activities/Markdown.activity/js/activity.js new file mode 100644 index 000000000..a4aae04db --- /dev/null +++ b/activities/Markdown.activity/js/activity.js @@ -0,0 +1,99 @@ +define(function (require) { + var activity = require("sugar-web/activity/activity"); + var datastore = require("sugar-web/datastore"); + + require("Markdown.Converter"); + //require("Markdown.Sanitizer"); + require("Markdown.Editor"); + + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + // Initialize the activity. + activity.setup(); + + inputTextContent = document.getElementById("wmd-input-second"); + inputTextContent.value = "#This is a sample input"; + //to save and resume the contents from datastore. + + var datastoreObject = activity.getDatastoreObject(); + + inputTextContent.onblur = function () { + + var jsonData = JSON.stringify((inputTextContent.value).toString()); + datastoreObject.setDataAsText(jsonData); + datastoreObject.save(function () {}); + }; + markdownParsing(); //to load for the first time + datastoreObject.loadAsText(function (error, metadata, data) { + markdowntext = JSON.parse(data); + inputTextContent.value = markdowntext; + markdownParsing(); //to load again when there is a datastore entry + }); + + var journal = document.getElementById("insertText"); + + journal.onclick = function () { + activity.showObjectChooser(function (error, result) { + //result1 = result.toString(); + var datastoreObject2 = new datastore.DatastoreObject(result); + datastoreObject2.loadAsText(function (error, metadata, data) { + + try { + textdata = JSON.parse(data); + } catch (e) { + textdata = data; + } + + var inputTextContent = document.getElementById("wmd-input-second"); + //inputTextContent.value += textdata; + insertAtCursor(inputTextContent, textdata); + }); + + }); + }; + + function insertAtCursor(myField, myValue) { + //IE support + if (document.selection) { + myField.focus(); + sel = document.selection.createRange(); + sel.text = myValue; + } + //MOZILLA and others + else if (myField.selectionStart || myField.selectionStart == '0') { + var startPos = myField.selectionStart; + var endPos = myField.selectionEnd; + myField.value = myField.value.substring(0, startPos) + + myValue + + myField.value.substring(endPos, myField.value.length); + } else { + myField.value += myValue; + } + //markdownParsing(); + } + + function markdownParsing() { + var converter2 = new Markdown.Converter(); + + var help = function () { + alert("Do you need help?"); + } + var options = { + helpButton: { + handler: help + }, + strings: { + quoteexample: "whatever you're quoting, put it right here" + } + }; + + var editor2 = new Markdown.Editor(converter2, "-second", options); + + editor2.run(); + } + + }); + +}); \ No newline at end of file diff --git a/activities/Markdown.activity/js/loader.js b/activities/Markdown.activity/js/loader.js new file mode 100644 index 000000000..bdcb48bd8 --- /dev/null +++ b/activities/Markdown.activity/js/loader.js @@ -0,0 +1,11 @@ +requirejs.config({ + baseUrl: "lib", + paths: { + activity: "../js", + markdownconverter: "../lib/Markdown.Converter", + markdowneditor: "../lib/Markdown.Editor", + markdownsanitizer: "../lib/Markdown.Sanitizer", + } +}); + +requirejs(["activity/activity"]); diff --git a/activities/Markdown.activity/lib/Markdown.Converter.js b/activities/Markdown.activity/lib/Markdown.Converter.js new file mode 100644 index 000000000..4ef99075a --- /dev/null +++ b/activities/Markdown.activity/lib/Markdown.Converter.js @@ -0,0 +1,1412 @@ +var Markdown; + +if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module + Markdown = exports; +else + Markdown = {}; + +// The following text is included for historical reasons, but should +// be taken with a pinch of salt; it's not all true anymore. + +// +// Wherever possible, Showdown is a straight, line-by-line port +// of the Perl version of Markdown. +// +// This is not a normal parser design; it's basically just a +// series of string substitutions. It's hard to read and +// maintain this way, but keeping Showdown close to the original +// design makes it easier to port new features. +// +// More importantly, Showdown behaves like markdown.pl in most +// edge cases. So web applications can do client-side preview +// in Javascript, and then build identical HTML on the server. +// +// This port needs the new RegExp functionality of ECMA 262, +// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers +// should do fine. Even with the new regular expression features, +// We do a lot of work to emulate Perl's regex functionality. +// The tricky changes in this file mostly have the "attacklab:" +// label. Major or self-explanatory changes don't. +// +// Smart diff tools like Araxis Merge will be able to match up +// this file with markdown.pl in a useful way. A little tweaking +// helps: in a copy of markdown.pl, replace "#" with "//" and +// replace "$text" with "text". Be sure to ignore whitespace +// and line endings. +// + + +// +// Usage: +// +// var text = "Markdown *rocks*."; +// +// var converter = new Markdown.Converter(); +// var html = converter.makeHtml(text); +// +// alert(html); +// +// Note: move the sample code to the bottom of this +// file before uncommenting it. +// + +(function () { + + function identity(x) { return x; } + function returnFalse(x) { return false; } + + function HookCollection() { } + + HookCollection.prototype = { + + chain: function (hookname, func) { + var original = this[hookname]; + if (!original) + throw new Error("unknown hook " + hookname); + + if (original === identity) + this[hookname] = func; + else + this[hookname] = function (text) { + var args = Array.prototype.slice.call(arguments, 0); + args[0] = original.apply(null, args); + return func.apply(null, args); + }; + }, + set: function (hookname, func) { + if (!this[hookname]) + throw new Error("unknown hook " + hookname); + this[hookname] = func; + }, + addNoop: function (hookname) { + this[hookname] = identity; + }, + addFalse: function (hookname) { + this[hookname] = returnFalse; + } + }; + + Markdown.HookCollection = HookCollection; + + // g_urls and g_titles allow arbitrary user-entered strings as keys. This + // caused an exception (and hence stopped the rendering) when the user entered + // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this + // (since no builtin property starts with "s_"). See + // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug + // (granted, switching from Array() to Object() alone would have left only __proto__ + // to be a problem) + function SaveHash() { } + SaveHash.prototype = { + set: function (key, value) { + this["s_" + key] = value; + }, + get: function (key) { + return this["s_" + key]; + } + }; + + Markdown.Converter = function () { + var pluginHooks = this.hooks = new HookCollection(); + + // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link + pluginHooks.addNoop("plainLinkText"); + + // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked + pluginHooks.addNoop("preConversion"); + + // called with the text once all normalizations have been completed (tabs to spaces, line endings, etc.), but before any conversions have + pluginHooks.addNoop("postNormalization"); + + // Called with the text before / after creating block elements like code blocks and lists. Note that this is called recursively + // with inner content, e.g. it's called with the full text, and then only with the content of a blockquote. The inner + // call will receive outdented text. + pluginHooks.addNoop("preBlockGamut"); + pluginHooks.addNoop("postBlockGamut"); + + // called with the text of a single block element before / after the span-level conversions (bold, code spans, etc.) have been made + pluginHooks.addNoop("preSpanGamut"); + pluginHooks.addNoop("postSpanGamut"); + + // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml + pluginHooks.addNoop("postConversion"); + + // + // Private state of the converter instance: + // + + // Global hashes, used by various utility routines + var g_urls; + var g_titles; + var g_html_blocks; + + // Used to track when we're inside an ordered or unordered list + // (see _ProcessListItems() for details): + var g_list_level; + + this.makeHtml = function (text) { + + // + // Main function. The order in which other subs are called here is + // essential. Link and image substitutions need to happen before + // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the + // and tags get encoded. + // + + // This will only happen if makeHtml on the same converter instance is called from a plugin hook. + // Don't do that. + if (g_urls) + throw new Error("Recursive call to converter.makeHtml"); + + // Create the private state objects. + g_urls = new SaveHash(); + g_titles = new SaveHash(); + g_html_blocks = []; + g_list_level = 0; + + text = pluginHooks.preConversion(text); + + // attacklab: Replace ~ with ~T + // This lets us use tilde as an escape char to avoid md5 hashes + // The choice of character is arbitray; anything that isn't + // magic in Markdown will work. + text = text.replace(/~/g, "~T"); + + // attacklab: Replace $ with ~D + // RegExp interprets $ as a special character + // when it's in a replacement string + text = text.replace(/\$/g, "~D"); + + // Standardize line endings + text = text.replace(/\r\n/g, "\n"); // DOS to Unix + text = text.replace(/\r/g, "\n"); // Mac to Unix + + // Make sure text begins and ends with a couple of newlines: + text = "\n\n" + text + "\n\n"; + + // Convert all tabs to spaces. + text = _Detab(text); + + // Strip any lines consisting only of spaces and tabs. + // This makes subsequent regexen easier to write, because we can + // match consecutive blank lines with /\n+/ instead of something + // contorted like /[ \t]*\n+/ . + text = text.replace(/^[ \t]+$/mg, ""); + + text = pluginHooks.postNormalization(text); + + // Turn block-level HTML blocks into hash entries + text = _HashHTMLBlocks(text); + + // Strip link definitions, store in hashes. + text = _StripLinkDefinitions(text); + + text = _RunBlockGamut(text); + + text = _UnescapeSpecialChars(text); + + // attacklab: Restore dollar signs + text = text.replace(/~D/g, "$$"); + + // attacklab: Restore tildes + text = text.replace(/~T/g, "~"); + + text = pluginHooks.postConversion(text); + + g_html_blocks = g_titles = g_urls = null; + + return text; + }; + + function _StripLinkDefinitions(text) { + // + // Strips link definitions from text, stores the URLs and titles in + // hash references. + // + + // Link defs are in the form: ^[id]: url "optional title" + + /* + text = text.replace(/ + ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 + [ \t]* + \n? // maybe *one* newline + [ \t]* + ? // url = $2 + (?=\s|$) // lookahead for whitespace instead of the lookbehind removed below + [ \t]* + \n? // maybe one newline + [ \t]* + ( // (potential) title = $3 + (\n*) // any lines skipped = $4 attacklab: lookbehind removed + [ \t]+ + ["(] + (.+?) // title = $5 + [")] + [ \t]* + )? // title is optional + (?:\n+|$) + /gm, function(){...}); + */ + + text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, + function (wholeMatch, m1, m2, m3, m4, m5) { + m1 = m1.toLowerCase(); + g_urls.set(m1, _EncodeAmpsAndAngles(m2)); // Link IDs are case-insensitive + if (m4) { + // Oops, found blank lines, so it's not a title. + // Put back the parenthetical statement we stole. + return m3; + } else if (m5) { + g_titles.set(m1, m5.replace(/"/g, """)); + } + + // Completely remove the definition from the text + return ""; + } + ); + + return text; + } + + function _HashHTMLBlocks(text) { + + // Hashify HTML blocks: + // We only want to do this for block-level HTML tags, such as headers, + // lists, and tables. That's because we still want to wrap

    s around + // "paragraphs" that are wrapped in non-block-level tags, such as anchors, + // phrase emphasis, and spans. The list of tags we're looking for is + // hard-coded: + var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" + var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" + + // First, look for nested blocks, e.g.: + //

    + //
    + // tags for inner block must be indented. + //
    + //
    + // + // The outermost tags must start at the left margin for this to match, and + // the inner nested divs must be indented. + // We need to do this before the next, more liberal match, because the next + // match will start at the first `
    ` and stop at the first `
    `. + + // attacklab: This regex can be expensive when it fails. + + /* + text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_a) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*?\n // any number of lines, minimally matching + // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement); + + // + // Now match more liberally, simply from `\n` to `\n` + // + + /* + text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_b) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*? // any number of lines, minimally matching + .* // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement); + + // Special case just for
    . It was easier to make a special case than + // to make the other regex more complicated. + + /* + text = text.replace(/ + \n // Starting after a blank line + [ ]{0,3} + ( // save in $1 + (<(hr) // start tag = $2 + \b // word break + ([^<>])*? + \/?>) // the matching end tag + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement); + + // Special case for standalone HTML comments: + + /* + text = text.replace(/ + \n\n // Starting after a blank line + [ ]{0,3} // attacklab: g_tab_width - 1 + ( // save in $1 + -]|-[^>])(?:[^-]|-[^-])*)--) // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256 + > + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/\n\n[ ]{0,3}(-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement); + + // PHP and ASP-style processor instructions ( and <%...%>) + + /* + text = text.replace(/ + (?: + \n\n // Starting after a blank line + ) + ( // save in $1 + [ ]{0,3} // attacklab: g_tab_width - 1 + (?: + <([?%]) // $2 + [^\r]*? + \2> + ) + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement); + + return text; + } + + function hashElement(wholeMatch, m1) { + var blockText = m1; + + // Undo double lines + blockText = blockText.replace(/^\n+/, ""); + + // strip trailing blank lines + blockText = blockText.replace(/\n+$/g, ""); + + // Replace the element text with a marker ("~KxK" where x is its key) + blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n"; + + return blockText; + } + + var blockGamutHookCallback = function (t) { return _RunBlockGamut(t); } + + function _RunBlockGamut(text, doNotUnhash) { + // + // These are all the transformations that form block-level + // tags like paragraphs, headers, and list items. + // + + text = pluginHooks.preBlockGamut(text, blockGamutHookCallback); + + text = _DoHeaders(text); + + // Do Horizontal Rules: + var replacement = "
    \n"; + text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement); + text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement); + text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement); + + text = _DoLists(text); + text = _DoCodeBlocks(text); + text = _DoBlockQuotes(text); + + text = pluginHooks.postBlockGamut(text, blockGamutHookCallback); + + // We already ran _HashHTMLBlocks() before, in Markdown(), but that + // was to escape raw HTML in the original Markdown source. This time, + // we're escaping the markup we've just created, so that we don't wrap + //

    tags around block-level tags. + text = _HashHTMLBlocks(text); + text = _FormParagraphs(text, doNotUnhash); + + return text; + } + + function _RunSpanGamut(text) { + // + // These are all the transformations that occur *within* block-level + // tags like paragraphs, headers, and list items. + // + + text = pluginHooks.preSpanGamut(text); + + text = _DoCodeSpans(text); + text = _EscapeSpecialCharsWithinTagAttributes(text); + text = _EncodeBackslashEscapes(text); + + // Process anchor and image tags. Images must come first, + // because ![foo][f] looks like an anchor. + text = _DoImages(text); + text = _DoAnchors(text); + + // Make links out of things like `` + // Must come after _DoAnchors(), because you can use < and > + // delimiters in inline links like [this](). + text = _DoAutoLinks(text); + + text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now + + text = _EncodeAmpsAndAngles(text); + text = _DoItalicsAndBold(text); + + // Do hard breaks: + text = text.replace(/ +\n/g, "
    \n"); + + text = pluginHooks.postSpanGamut(text); + + return text; + } + + function _EscapeSpecialCharsWithinTagAttributes(text) { + // + // Within tags -- meaning between < and > -- encode [\ ` * _] so they + // don't conflict with their use in Markdown for code, italics and strong. + // + + // Build a regex to find HTML tags and comments. See Friedl's + // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. + + // SE: changed the comment part of the regex + + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi; + + text = text.replace(regex, function (wholeMatch) { + var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`"); + tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987 + return tag; + }); + + return text; + } + + function _DoAnchors(text) { + // + // Turn Markdown link shortcuts into XHTML
    tags. + // + // + // First, handle reference-style links: [link text] [id] + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[] // or anything else + )* + ) + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + ) + ()()()() // pad remaining backreferences + /g, writeAnchorTag); + */ + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag); + + // + // Next, inline-style links: [link text](url "optional title") + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[\]] // or anything else + )* + ) + \] + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + ? + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // Title = $7 + \6 // matching quote + [ \t]* // ignore any spaces/tabs between closing quote and ) + )? // title is optional + \) + ) + /g, writeAnchorTag); + */ + + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag); + + // + // Last, handle reference-style shortcuts: [link text] + // These must come last in case you've also got [link test][1] + // or [link test](/foo) + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ([^\[\]]+) // link text = $2; can't contain '[' or ']' + \] + ) + ()()()()() // pad rest of backreferences + /g, writeAnchorTag); + */ + text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); + + return text; + } + + function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) { + if (m7 == undefined) m7 = ""; + var whole_match = m1; + var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs + var link_id = m3.toLowerCase(); + var url = m4; + var title = m7; + + if (url == "") { + if (link_id == "") { + // lower-case and turn embedded newlines into spaces + link_id = link_text.toLowerCase().replace(/ ?\n/g, " "); + } + url = "#" + link_id; + + if (g_urls.get(link_id) != undefined) { + url = g_urls.get(link_id); + if (g_titles.get(link_id) != undefined) { + title = g_titles.get(link_id); + } + } + else { + if (whole_match.search(/\(\s*\)$/m) > -1) { + // Special case for explicit empty url + url = ""; + } else { + return whole_match; + } + } + } + url = encodeProblemUrlChars(url); + url = escapeCharacters(url, "*_"); + var result = ""; + + return result; + } + + function _DoImages(text) { + // + // Turn Markdown image shortcuts into tags. + // + + // + // First, handle reference-style labeled images: ![alt text][id] + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + ) + ()()()() // pad rest of backreferences + /g, writeImageTag); + */ + text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag); + + // + // Next, handle inline images: ![alt text](url "optional title") + // Don't forget: encode * and _ + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + \s? // One optional whitespace character + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + ? // src url = $4 + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // title = $7 + \6 // matching quote + [ \t]* + )? // title is optional + \) + ) + /g, writeImageTag); + */ + text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag); + + return text; + } + + function attributeEncode(text) { + // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title) + // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it) + return text.replace(/>/g, ">").replace(/" + _RunSpanGamut(m1) + "\n\n"; } + ); + + text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, + function (matchFound, m1) { return "

    " + _RunSpanGamut(m1) + "

    \n\n"; } + ); + + // atx-style headers: + // # Header 1 + // ## Header 2 + // ## Header 2 with closing hashes ## + // ... + // ###### Header 6 + // + + /* + text = text.replace(/ + ^(\#{1,6}) // $1 = string of #'s + [ \t]* + (.+?) // $2 = Header text + [ \t]* + \#* // optional closing #'s (not counted) + \n+ + /gm, function() {...}); + */ + + text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, + function (wholeMatch, m1, m2) { + var h_level = m1.length; + return "" + _RunSpanGamut(m2) + "\n\n"; + } + ); + + return text; + } + + function _DoLists(text, isInsideParagraphlessListItem) { + // + // Form HTML ordered (numbered) and unordered (bulleted) lists. + // + + // attacklab: add sentinel to hack around khtml/safari bug: + // http://bugs.webkit.org/show_bug.cgi?id=11231 + text += "~0"; + + // Re-usable pattern to match any entirel ul or ol list: + + /* + var whole_list = / + ( // $1 = whole list + ( // $2 + [ ]{0,3} // attacklab: g_tab_width - 1 + ([*+-]|\d+[.]) // $3 = first list item marker + [ \t]+ + ) + [^\r]+? + ( // $4 + ~0 // sentinel for workaround; should be $ + | + \n{2,} + (?=\S) + (?! // Negative lookahead for another list item marker + [ \t]* + (?:[*+-]|\d+[.])[ \t]+ + ) + ) + ) + /g + */ + var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; + + if (g_list_level) { + text = text.replace(whole_list, function (wholeMatch, m1, m2) { + var list = m1; + var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol"; + + var result = _ProcessListItems(list, list_type, isInsideParagraphlessListItem); + + // Trim any trailing whitespace, to put the closing `` + // up on the preceding line, to get it past the current stupid + // HTML block parser. This is a hack to work around the terrible + // hack that is the HTML block parser. + result = result.replace(/\s+$/, ""); + result = "<" + list_type + ">" + result + "\n"; + return result; + }); + } else { + whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; + text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) { + var runup = m1; + var list = m2; + + var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol"; + var result = _ProcessListItems(list, list_type); + result = runup + "<" + list_type + ">\n" + result + "\n"; + return result; + }); + } + + // attacklab: strip sentinel + text = text.replace(/~0/, ""); + + return text; + } + + var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" }; + + function _ProcessListItems(list_str, list_type, isInsideParagraphlessListItem) { + // + // Process the contents of a single ordered or unordered list, splitting it + // into individual list items. + // + // list_type is either "ul" or "ol". + + // The $g_list_level global keeps track of when we're inside a list. + // Each time we enter a list, we increment it; when we leave a list, + // we decrement. If it's zero, we're not in a list anymore. + // + // We do this because when we're not inside a list, we want to treat + // something like this: + // + // I recommend upgrading to version + // 8. Oops, now this line is treated + // as a sub-list. + // + // As a single paragraph, despite the fact that the second line starts + // with a digit-period-space sequence. + // + // Whereas when we're inside a list (or sub-list), that line will be + // treated as the start of a sub-list. What a kludge, huh? This is + // an aspect of Markdown's syntax that's hard to parse perfectly + // without resorting to mind-reading. Perhaps the solution is to + // change the syntax rules such that sub-lists must start with a + // starting cardinal number; e.g. "1." or "a.". + + g_list_level++; + + // trim trailing blank lines: + list_str = list_str.replace(/\n{2,}$/, "\n"); + + // attacklab: add sentinel to emulate \z + list_str += "~0"; + + // In the original attacklab showdown, list_type was not given to this function, and anything + // that matched /[*+-]|\d+[.]/ would just create the next
  • , causing this mismatch: + // + // Markdown rendered by WMD rendered by MarkdownSharp + // ------------------------------------------------------------------ + // 1. first 1. first 1. first + // 2. second 2. second 2. second + // - third 3. third * third + // + // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx, + // with {MARKER} being one of \d+[.] or [*+-], depending on list_type: + + /* + list_str = list_str.replace(/ + (^[ \t]*) // leading whitespace = $1 + ({MARKER}) [ \t]+ // list marker = $2 + ([^\r]+? // list item text = $3 + (\n+) + ) + (?= + (~0 | \2 ({MARKER}) [ \t]+) + ) + /gm, function(){...}); + */ + + var marker = _listItemMarkers[list_type]; + var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm"); + var last_item_had_a_double_newline = false; + list_str = list_str.replace(re, + function (wholeMatch, m1, m2, m3) { + var item = m3; + var leading_space = m1; + var ends_with_double_newline = /\n\n$/.test(item); + var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1; + + if (contains_double_newline || last_item_had_a_double_newline) { + item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true); + } + else { + // Recursion for sub-lists: + item = _DoLists(_Outdent(item), /* isInsideParagraphlessListItem= */ true); + item = item.replace(/\n$/, ""); // chomp(item) + if (!isInsideParagraphlessListItem) // only the outer-most item should run this, otherwise it's run multiple times for the inner ones + item = _RunSpanGamut(item); + } + last_item_had_a_double_newline = ends_with_double_newline; + return "
  • " + item + "
  • \n"; + } + ); + + // attacklab: strip sentinel + list_str = list_str.replace(/~0/g, ""); + + g_list_level--; + return list_str; + } + + function _DoCodeBlocks(text) { + // + // Process Markdown `
    ` blocks.
    +            //  
    +
    +            /*
    +            text = text.replace(/
    +                (?:\n\n|^)
    +                (                               // $1 = the code block -- one or more lines, starting with a space/tab
    +                    (?:
    +                        (?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
    +                        .*\n+
    +                    )+
    +                )
    +                (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
    +            /g ,function(){...});
    +            */
    +
    +            // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
    +            text += "~0";
    +
    +            text = text.replace(/(?:\n\n|^\n?)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
    +                function (wholeMatch, m1, m2) {
    +                    var codeblock = m1;
    +                    var nextChar = m2;
    +
    +                    codeblock = _EncodeCode(_Outdent(codeblock));
    +                    codeblock = _Detab(codeblock);
    +                    codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
    +                    codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
    +
    +                    codeblock = "
    " + codeblock + "\n
    "; + + return "\n\n" + codeblock + "\n\n" + nextChar; + } + ); + + // attacklab: strip sentinel + text = text.replace(/~0/, ""); + + return text; + } + + function hashBlock(text) { + text = text.replace(/(^\n+|\n+$)/g, ""); + return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n"; + } + + function _DoCodeSpans(text) { + // + // * Backtick quotes are used for spans. + // + // * You can use multiple backticks as the delimiters if you want to + // include literal backticks in the code span. So, this input: + // + // Just type ``foo `bar` baz`` at the prompt. + // + // Will translate to: + // + //

    Just type foo `bar` baz at the prompt.

    + // + // There's no arbitrary limit to the number of backticks you + // can use as delimters. If you need three consecutive backticks + // in your code, use four for delimiters, etc. + // + // * You can use spaces to get literal backticks at the edges: + // + // ... type `` `bar` `` ... + // + // Turns to: + // + // ... type `bar` ... + // + + /* + text = text.replace(/ + (^|[^\\]) // Character before opening ` can't be a backslash + (`+) // $2 = Opening run of ` + ( // $3 = The code block + [^\r]*? + [^`] // attacklab: work around lack of lookbehind + ) + \2 // Matching closer + (?!`) + /gm, function(){...}); + */ + + text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, + function (wholeMatch, m1, m2, m3, m4) { + var c = m3; + c = c.replace(/^([ \t]*)/g, ""); // leading whitespace + c = c.replace(/[ \t]*$/g, ""); // trailing whitespace + c = _EncodeCode(c); + c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs. + return m1 + "" + c + ""; + } + ); + + return text; + } + + function _EncodeCode(text) { + // + // Encode/escape certain characters inside Markdown code runs. + // The point is that in code, these characters are literals, + // and lose their special Markdown meanings. + // + // Encode all ampersands; HTML entities are not + // entities within a Markdown code span. + text = text.replace(/&/g, "&"); + + // Do the angle bracket song and dance: + text = text.replace(//g, ">"); + + // Now, escape characters that are magic in Markdown: + text = escapeCharacters(text, "\*_{}[]\\", false); + + // jj the line above breaks this: + //--- + + //* Item + + // 1. Subitem + + // special char: * + //--- + + return text; + } + + function _DoItalicsAndBold(text) { + + // must go first: + text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g, + "$1$3$4"); + + text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g, + "$1$3$4"); + + return text; + } + + function _DoBlockQuotes(text) { + + /* + text = text.replace(/ + ( // Wrap whole match in $1 + ( + ^[ \t]*>[ \t]? // '>' at the start of a line + .+\n // rest of the first line + (.+\n)* // subsequent consecutive lines + \n* // blanks + )+ + ) + /gm, function(){...}); + */ + + text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, + function (wholeMatch, m1) { + var bq = m1; + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting + + // attacklab: clean up hack + bq = bq.replace(/~0/g, ""); + + bq = bq.replace(/^[ \t]+$/gm, ""); // trim whitespace-only lines + bq = _RunBlockGamut(bq); // recurse + + bq = bq.replace(/(^|\n)/g, "$1 "); + // These leading spaces screw with
     content, so we need to fix that:
    +                    bq = bq.replace(
    +                            /(\s*
    [^\r]+?<\/pre>)/gm,
    +                        function (wholeMatch, m1) {
    +                            var pre = m1;
    +                            // attacklab: hack around Konqueror 3.5.4 bug:
    +                            pre = pre.replace(/^  /mg, "~0");
    +                            pre = pre.replace(/~0/g, "");
    +                            return pre;
    +                        });
    +
    +                    return hashBlock("
    \n" + bq + "\n
    "); + } + ); + return text; + } + + function _FormParagraphs(text, doNotUnhash) { + // + // Params: + // $text - string to process with html

    tags + // + + // Strip leading and trailing lines: + text = text.replace(/^\n+/g, ""); + text = text.replace(/\n+$/g, ""); + + var grafs = text.split(/\n{2,}/g); + var grafsOut = []; + + var markerRe = /~K(\d+)K/; + + // + // Wrap

    tags. + // + var end = grafs.length; + for (var i = 0; i < end; i++) { + var str = grafs[i]; + + // if this is an HTML marker, copy it + if (markerRe.test(str)) { + grafsOut.push(str); + } + else if (/\S/.test(str)) { + str = _RunSpanGamut(str); + str = str.replace(/^([ \t]*)/g, "

    "); + str += "

    " + grafsOut.push(str); + } + + } + // + // Unhashify HTML blocks + // + if (!doNotUnhash) { + end = grafsOut.length; + for (var i = 0; i < end; i++) { + var foundAny = true; + while (foundAny) { // we may need several runs, since the data may be nested + foundAny = false; + grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) { + foundAny = true; + return g_html_blocks[id]; + }); + } + } + } + return grafsOut.join("\n\n"); + } + + function _EncodeAmpsAndAngles(text) { + // Smart processing for ampersands and angle brackets that need to be encoded. + + // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: + // http://bumppo.net/projects/amputator/ + text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&"); + + // Encode naked <'s + text = text.replace(/<(?![a-z\/?!]|~D)/gi, "<"); + + return text; + } + + function _EncodeBackslashEscapes(text) { + // + // Parameter: String. + // Returns: The string, with after processing the following backslash + // escape sequences. + // + + // attacklab: The polite way to do this is with the new + // escapeCharacters() function: + // + // text = escapeCharacters(text,"\\",true); + // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); + // + // ...but we're sidestepping its use of the (slow) RegExp constructor + // as an optimization for Firefox. This function gets called a LOT. + + text = text.replace(/\\(\\)/g, escapeCharacters_callback); + text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback); + return text; + } + + var charInsideUrl = "[-A-Z0-9+&@#/%?=~_|[\\]()!:,.;]", + charEndingUrl = "[-A-Z0-9+&@#/%=~_|[\\])]", + autoLinkRegex = new RegExp("(=\"|<)?\\b(https?|ftp)(://" + charInsideUrl + "*" + charEndingUrl + ")(?=$|\\W)", "gi"), + endCharRegex = new RegExp(charEndingUrl, "i"); + + function handleTrailingParens(wholeMatch, lookbehind, protocol, link) { + if (lookbehind) + return wholeMatch; + if (link.charAt(link.length - 1) !== ")") + return "<" + protocol + link + ">"; + var parens = link.match(/[()]/g); + var level = 0; + for (var i = 0; i < parens.length; i++) { + if (parens[i] === "(") { + if (level <= 0) + level = 1; + else + level++; + } + else { + level--; + } + } + var tail = ""; + if (level < 0) { + var re = new RegExp("\\){1," + (-level) + "}$"); + link = link.replace(re, function (trailingParens) { + tail = trailingParens; + return ""; + }); + } + if (tail) { + var lastChar = link.charAt(link.length - 1); + if (!endCharRegex.test(lastChar)) { + tail = lastChar + tail; + link = link.substr(0, link.length - 1); + } + } + return "<" + protocol + link + ">" + tail; + } + + function _DoAutoLinks(text) { + + // note that at this point, all other URL in the text are already hyperlinked as
    + // *except* for the case + + // automatically add < and > around unadorned raw hyperlinks + // must be preceded by a non-word character (and not by =" or <) and followed by non-word/EOF character + // simulating the lookbehind in a consuming way is okay here, since a URL can neither and with a " nor + // with a <, so there is no risk of overlapping matches. + text = text.replace(autoLinkRegex, handleTrailingParens); + + // autolink anything like + + var replacer = function (wholematch, m1) { return "" + pluginHooks.plainLinkText(m1) + ""; } + text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer); + + // Email addresses: + /* + text = text.replace(/ + < + (?:mailto:)? + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + /gi, _DoAutoLinks_callback()); + */ + + /* disabling email autolinking, since we don't do that on the server, either + text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, + function(wholeMatch,m1) { + return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); + } + ); + */ + return text; + } + + function _UnescapeSpecialChars(text) { + // + // Swap back in all the special characters we've hidden. + // + text = text.replace(/~E(\d+)E/g, + function (wholeMatch, m1) { + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + } + ); + return text; + } + + function _Outdent(text) { + // + // Remove one level of line-leading tabs or spaces + // + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width + + // attacklab: clean up hack + text = text.replace(/~0/g, "") + + return text; + } + + function _Detab(text) { + if (!/\t/.test(text)) + return text; + + var spaces = [" ", " ", " ", " "], + skew = 0, + v; + + return text.replace(/[\n\t]/g, function (match, offset) { + if (match === "\n") { + skew = offset + 1; + return match; + } + v = (offset - skew) % 4; + skew = offset + 1; + return spaces[v]; + }); + } + + // + // attacklab: Utility functions + // + + var _problemUrlChars = /(?:["'*()[\]:]|~D)/g; + + // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems + function encodeProblemUrlChars(url) { + if (!url) + return ""; + + var len = url.length; + + return url.replace(_problemUrlChars, function (match, offset) { + if (match == "~D") // escape for dollar + return "%24"; + if (match == ":") { + if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1))) + return ":" + } + return "%" + match.charCodeAt(0).toString(16); + }); + } + + + function escapeCharacters(text, charsToEscape, afterBackslash) { + // First we have to escape the escape characters so that + // we can build a character class out of them + var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])"; + + if (afterBackslash) { + regexString = "\\\\" + regexString; + } + + var regex = new RegExp(regexString, "g"); + text = text.replace(regex, escapeCharacters_callback); + + return text; + } + + + function escapeCharacters_callback(wholeMatch, m1) { + var charCodeToEscape = m1.charCodeAt(0); + return "~E" + charCodeToEscape + "E"; + } + + }; // end of the Markdown.Converter constructor + +})(); diff --git a/activities/Markdown.activity/lib/Markdown.Editor.js b/activities/Markdown.activity/lib/Markdown.Editor.js new file mode 100644 index 000000000..5089251c3 --- /dev/null +++ b/activities/Markdown.activity/lib/Markdown.Editor.js @@ -0,0 +1,2258 @@ +// needs Markdown.Converter.js at the moment + +(function () { + + var util = {}, + position = {}, + ui = {}, + doc = window.document, + re = window.RegExp, + nav = window.navigator, + SETTINGS = { lineLength: 72 }, + + // Used to work around some browser bugs where we can't use feature testing. + uaSniffed = { + isIE: /msie/.test(nav.userAgent.toLowerCase()), + isIE_5or6: /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase()), + isOpera: /opera/.test(nav.userAgent.toLowerCase()) + }; + + var defaultsStrings = { + bold: "Strong Ctrl+B", + boldexample: "strong text", + + italic: "Emphasis Ctrl+I", + italicexample: "emphasized text", + + link: "Hyperlink Ctrl+L", + linkdescription: "enter link description here", + linkdialog: "

    Insert Hyperlink

    http://example.com/ \"optional title\"

    ", + + quote: "Blockquote
    Ctrl+Q", + quoteexample: "Blockquote", + + code: "Code Sample
     Ctrl+K",
    +        codeexample: "enter code here",
    +
    +        image: "Image  Ctrl+G",
    +        imagedescription: "enter image description here",
    +        imagedialog: "

    Insert Image

    http://example.com/images/diagram.jpg \"optional title\"

    Need
    free image hosting?

    ", + + olist: "Numbered List
      Ctrl+O", + ulist: "Bulleted List
        Ctrl+U", + litem: "List item", + + heading: "Heading

        /

        Ctrl+H", + headingexample: "Heading", + + hr: "Horizontal Rule
        Ctrl+R", + + undo: "Undo - Ctrl+Z", + redo: "Redo - Ctrl+Y", + redomac: "Redo - Ctrl+Shift+Z", + + help: "Markdown Editing Help" + }; + + + // ------------------------------------------------------------------- + // YOUR CHANGES GO HERE + // + // I've tried to localize the things you are likely to change to + // this area. + // ------------------------------------------------------------------- + + // The default text that appears in the dialog input box when entering + // links. + var imageDefaultText = "http://"; + var linkDefaultText = "http://"; + + // ------------------------------------------------------------------- + // END OF YOUR CHANGES + // ------------------------------------------------------------------- + + // options, if given, can have the following properties: + // options.helpButton = { handler: yourEventHandler } + // options.strings = { italicexample: "slanted text" } + // `yourEventHandler` is the click handler for the help button. + // If `options.helpButton` isn't given, not help button is created. + // `options.strings` can have any or all of the same properties as + // `defaultStrings` above, so you can just override some string displayed + // to the user on a case-by-case basis, or translate all strings to + // a different language. + // + // For backwards compatibility reasons, the `options` argument can also + // be just the `helpButton` object, and `strings.help` can also be set via + // `helpButton.title`. This should be considered legacy. + // + // The constructed editor object has the methods: + // - getConverter() returns the markdown converter object that was passed to the constructor + // - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op. + // - refreshPreview() forces the preview to be updated. This method is only available after run() was called. + Markdown.Editor = function (markdownConverter, idPostfix, options) { + + options = options || {}; + + if (typeof options.handler === "function") { //backwards compatible behavior + options = { helpButton: options }; + } + options.strings = options.strings || {}; + if (options.helpButton) { + options.strings.help = options.strings.help || options.helpButton.title; + } + var getString = function (identifier) { return options.strings[identifier] || defaultsStrings[identifier]; } + + idPostfix = idPostfix || ""; + + var hooks = this.hooks = new Markdown.HookCollection(); + hooks.addNoop("onPreviewRefresh"); // called with no arguments after the preview has been refreshed + hooks.addNoop("postBlockquoteCreation"); // called with the user's selection *after* the blockquote was created; should return the actual to-be-inserted text + hooks.addFalse("insertImageDialog"); /* called with one parameter: a callback to be called with the URL of the image. If the application creates + * its own image insertion dialog, this hook should return true, and the callback should be called with the chosen + * image url (or null if the user cancelled). If this hook returns false, the default dialog will be used. + */ + + this.getConverter = function () { return markdownConverter; } + + var that = this, + panels; + + this.run = function () { + if (panels) + return; // already initialized + + panels = new PanelCollection(idPostfix); + var commandManager = new CommandManager(hooks, getString); + var previewManager = new PreviewManager(markdownConverter, panels, function () { hooks.onPreviewRefresh(); }); + var undoManager, uiManager; + + if (!/\?noundo/.test(doc.location.href)) { + undoManager = new UndoManager(function () { + previewManager.refresh(); + if (uiManager) // not available on the first call + uiManager.setUndoRedoButtonStates(); + }, panels); + this.textOperation = function (f) { + undoManager.setCommandMode(); + f(); + that.refreshPreview(); + } + } + + uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, options.helpButton, getString); + uiManager.setUndoRedoButtonStates(); + + var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); }; + + forceRefresh(); + }; + + } + + // before: contains all the text in the input box BEFORE the selection. + // after: contains all the text in the input box AFTER the selection. + function Chunks() { } + + // startRegex: a regular expression to find the start tag + // endRegex: a regular expresssion to find the end tag + Chunks.prototype.findTags = function (startRegex, endRegex) { + + var chunkObj = this; + var regex; + + if (startRegex) { + + regex = util.extendRegExp(startRegex, "", "$"); + + this.before = this.before.replace(regex, + function (match) { + chunkObj.startTag = chunkObj.startTag + match; + return ""; + }); + + regex = util.extendRegExp(startRegex, "^", ""); + + this.selection = this.selection.replace(regex, + function (match) { + chunkObj.startTag = chunkObj.startTag + match; + return ""; + }); + } + + if (endRegex) { + + regex = util.extendRegExp(endRegex, "", "$"); + + this.selection = this.selection.replace(regex, + function (match) { + chunkObj.endTag = match + chunkObj.endTag; + return ""; + }); + + regex = util.extendRegExp(endRegex, "^", ""); + + this.after = this.after.replace(regex, + function (match) { + chunkObj.endTag = match + chunkObj.endTag; + return ""; + }); + } + }; + + // If remove is false, the whitespace is transferred + // to the before/after regions. + // + // If remove is true, the whitespace disappears. + Chunks.prototype.trimWhitespace = function (remove) { + var beforeReplacer, afterReplacer, that = this; + if (remove) { + beforeReplacer = afterReplacer = ""; + } else { + beforeReplacer = function (s) { that.before += s; return ""; } + afterReplacer = function (s) { that.after = s + that.after; return ""; } + } + + this.selection = this.selection.replace(/^(\s*)/, beforeReplacer).replace(/(\s*)$/, afterReplacer); + }; + + + Chunks.prototype.skipLines = function (nLinesBefore, nLinesAfter, findExtraNewlines) { + + if (nLinesBefore === undefined) { + nLinesBefore = 1; + } + + if (nLinesAfter === undefined) { + nLinesAfter = 1; + } + + nLinesBefore++; + nLinesAfter++; + + var regexText; + var replacementText; + + // chrome bug ... documented at: http://meta.stackoverflow.com/questions/63307/blockquote-glitch-in-editor-in-chrome-6-and-7/65985#65985 + if (navigator.userAgent.match(/Chrome/)) { + "X".match(/()./); + } + + this.selection = this.selection.replace(/(^\n*)/, ""); + + this.startTag = this.startTag + re.$1; + + this.selection = this.selection.replace(/(\n*$)/, ""); + this.endTag = this.endTag + re.$1; + this.startTag = this.startTag.replace(/(^\n*)/, ""); + this.before = this.before + re.$1; + this.endTag = this.endTag.replace(/(\n*$)/, ""); + this.after = this.after + re.$1; + + if (this.before) { + + regexText = replacementText = ""; + + while (nLinesBefore--) { + regexText += "\\n?"; + replacementText += "\n"; + } + + if (findExtraNewlines) { + regexText = "\\n*"; + } + this.before = this.before.replace(new re(regexText + "$", ""), replacementText); + } + + if (this.after) { + + regexText = replacementText = ""; + + while (nLinesAfter--) { + regexText += "\\n?"; + replacementText += "\n"; + } + if (findExtraNewlines) { + regexText = "\\n*"; + } + + this.after = this.after.replace(new re(regexText, ""), replacementText); + } + }; + + // end of Chunks + + // A collection of the important regions on the page. + // Cached so we don't have to keep traversing the DOM. + // Also holds ieCachedRange and ieCachedScrollTop, where necessary; working around + // this issue: + // Internet explorer has problems with CSS sprite buttons that use HTML + // lists. When you click on the background image "button", IE will + // select the non-existent link text and discard the selection in the + // textarea. The solution to this is to cache the textarea selection + // on the button's mousedown event and set a flag. In the part of the + // code where we need to grab the selection, we check for the flag + // and, if it's set, use the cached area instead of querying the + // textarea. + // + // This ONLY affects Internet Explorer (tested on versions 6, 7 + // and 8) and ONLY on button clicks. Keyboard shortcuts work + // normally since the focus never leaves the textarea. + function PanelCollection(postfix) { + this.buttonBar = doc.getElementById("wmd-button-bar" + postfix); + this.preview = doc.getElementById("wmd-preview" + postfix); + this.input = doc.getElementById("wmd-input" + postfix); + }; + + // Returns true if the DOM element is visible, false if it's hidden. + // Checks if display is anything other than none. + util.isVisible = function (elem) { + + if (window.getComputedStyle) { + // Most browsers + return window.getComputedStyle(elem, null).getPropertyValue("display") !== "none"; + } + else if (elem.currentStyle) { + // IE + return elem.currentStyle["display"] !== "none"; + } + }; + + + // Adds a listener callback to a DOM element which is fired on a specified + // event. + util.addEvent = function (elem, event, listener) { + if (elem.attachEvent) { + // IE only. The "on" is mandatory. + elem.attachEvent("on" + event, listener); + } + else { + // Other browsers. + elem.addEventListener(event, listener, false); + } + }; + + + // Removes a listener callback from a DOM element which is fired on a specified + // event. + util.removeEvent = function (elem, event, listener) { + if (elem.detachEvent) { + // IE only. The "on" is mandatory. + elem.detachEvent("on" + event, listener); + } + else { + // Other browsers. + elem.removeEventListener(event, listener, false); + } + }; + + // Converts \r\n and \r to \n. + util.fixEolChars = function (text) { + text = text.replace(/\r\n/g, "\n"); + text = text.replace(/\r/g, "\n"); + return text; + }; + + // Extends a regular expression. Returns a new RegExp + // using pre + regex + post as the expression. + // Used in a few functions where we have a base + // expression and we want to pre- or append some + // conditions to it (e.g. adding "$" to the end). + // The flags are unchanged. + // + // regex is a RegExp, pre and post are strings. + util.extendRegExp = function (regex, pre, post) { + + if (pre === null || pre === undefined) { + pre = ""; + } + if (post === null || post === undefined) { + post = ""; + } + + var pattern = regex.toString(); + var flags; + + // Replace the flags with empty space and store them. + pattern = pattern.replace(/\/([gim]*)$/, function (wholeMatch, flagsPart) { + flags = flagsPart; + return ""; + }); + + // Remove the slash delimiters on the regular expression. + pattern = pattern.replace(/(^\/|\/$)/g, ""); + pattern = pre + pattern + post; + + return new re(pattern, flags); + } + + // UNFINISHED + // The assignment in the while loop makes jslint cranky. + // I'll change it to a better loop later. + position.getTop = function (elem, isInner) { + var result = elem.offsetTop; + if (!isInner) { + while (elem = elem.offsetParent) { + result += elem.offsetTop; + } + } + return result; + }; + + position.getHeight = function (elem) { + return elem.offsetHeight || elem.scrollHeight; + }; + + position.getWidth = function (elem) { + return elem.offsetWidth || elem.scrollWidth; + }; + + position.getPageSize = function () { + + var scrollWidth, scrollHeight; + var innerWidth, innerHeight; + + // It's not very clear which blocks work with which browsers. + if (self.innerHeight && self.scrollMaxY) { + scrollWidth = doc.body.scrollWidth; + scrollHeight = self.innerHeight + self.scrollMaxY; + } + else if (doc.body.scrollHeight > doc.body.offsetHeight) { + scrollWidth = doc.body.scrollWidth; + scrollHeight = doc.body.scrollHeight; + } + else { + scrollWidth = doc.body.offsetWidth; + scrollHeight = doc.body.offsetHeight; + } + + if (self.innerHeight) { + // Non-IE browser + innerWidth = self.innerWidth; + innerHeight = self.innerHeight; + } + else if (doc.documentElement && doc.documentElement.clientHeight) { + // Some versions of IE (IE 6 w/ a DOCTYPE declaration) + innerWidth = doc.documentElement.clientWidth; + innerHeight = doc.documentElement.clientHeight; + } + else if (doc.body) { + // Other versions of IE + innerWidth = doc.body.clientWidth; + innerHeight = doc.body.clientHeight; + } + + var maxWidth = Math.max(scrollWidth, innerWidth); + var maxHeight = Math.max(scrollHeight, innerHeight); + return [maxWidth, maxHeight, innerWidth, innerHeight]; + }; + + // Handles pushing and popping TextareaStates for undo/redo commands. + // I should rename the stack variables to list. + function UndoManager(callback, panels) { + + var undoObj = this; + var undoStack = []; // A stack of undo states + var stackPtr = 0; // The index of the current state + var mode = "none"; + var lastState; // The last state + var timer; // The setTimeout handle for cancelling the timer + var inputStateObj; + + // Set the mode for later logic steps. + var setMode = function (newMode, noSave) { + if (mode != newMode) { + mode = newMode; + if (!noSave) { + saveState(); + } + } + + if (!uaSniffed.isIE || mode != "moving") { + timer = setTimeout(refreshState, 1); + } + else { + inputStateObj = null; + } + }; + + var refreshState = function (isInitialState) { + inputStateObj = new TextareaState(panels, isInitialState); + timer = undefined; + }; + + this.setCommandMode = function () { + mode = "command"; + saveState(); + timer = setTimeout(refreshState, 0); + }; + + this.canUndo = function () { + return stackPtr > 1; + }; + + this.canRedo = function () { + if (undoStack[stackPtr + 1]) { + return true; + } + return false; + }; + + // Removes the last state and restores it. + this.undo = function () { + + if (undoObj.canUndo()) { + if (lastState) { + // What about setting state -1 to null or checking for undefined? + lastState.restore(); + lastState = null; + } + else { + undoStack[stackPtr] = new TextareaState(panels); + undoStack[--stackPtr].restore(); + + if (callback) { + callback(); + } + } + } + + mode = "none"; + panels.input.focus(); + refreshState(); + }; + + // Redo an action. + this.redo = function () { + + if (undoObj.canRedo()) { + + undoStack[++stackPtr].restore(); + + if (callback) { + callback(); + } + } + + mode = "none"; + panels.input.focus(); + refreshState(); + }; + + // Push the input area state to the stack. + var saveState = function () { + var currState = inputStateObj || new TextareaState(panels); + + if (!currState) { + return false; + } + if (mode == "moving") { + if (!lastState) { + lastState = currState; + } + return; + } + if (lastState) { + if (undoStack[stackPtr - 1].text != lastState.text) { + undoStack[stackPtr++] = lastState; + } + lastState = null; + } + undoStack[stackPtr++] = currState; + undoStack[stackPtr + 1] = null; + if (callback) { + callback(); + } + }; + + var handleCtrlYZ = function (event) { + + var handled = false; + + if ((event.ctrlKey || event.metaKey) && !event.altKey) { + + // IE and Opera do not support charCode. + var keyCode = event.charCode || event.keyCode; + var keyCodeChar = String.fromCharCode(keyCode); + + switch (keyCodeChar.toLowerCase()) { + + case "y": + undoObj.redo(); + handled = true; + break; + + case "z": + if (!event.shiftKey) { + undoObj.undo(); + } + else { + undoObj.redo(); + } + handled = true; + break; + } + } + + if (handled) { + if (event.preventDefault) { + event.preventDefault(); + } + if (window.event) { + window.event.returnValue = false; + } + return; + } + }; + + // Set the mode depending on what is going on in the input area. + var handleModeChange = function (event) { + + if (!event.ctrlKey && !event.metaKey) { + + var keyCode = event.keyCode; + + if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) { + // 33 - 40: page up/dn and arrow keys + // 63232 - 63235: page up/dn and arrow keys on safari + setMode("moving"); + } + else if (keyCode == 8 || keyCode == 46 || keyCode == 127) { + // 8: backspace + // 46: delete + // 127: delete + setMode("deleting"); + } + else if (keyCode == 13) { + // 13: Enter + setMode("newlines"); + } + else if (keyCode == 27) { + // 27: escape + setMode("escape"); + } + else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) { + // 16-20 are shift, etc. + // 91: left window key + // I think this might be a little messed up since there are + // a lot of nonprinting keys above 20. + setMode("typing"); + } + } + }; + + var setEventHandlers = function () { + util.addEvent(panels.input, "keypress", function (event) { + // keyCode 89: y + // keyCode 90: z + if ((event.ctrlKey || event.metaKey) && !event.altKey && (event.keyCode == 89 || event.keyCode == 90)) { + event.preventDefault(); + } + }); + + var handlePaste = function () { + if (uaSniffed.isIE || (inputStateObj && inputStateObj.text != panels.input.value)) { + if (timer == undefined) { + mode = "paste"; + saveState(); + refreshState(); + } + } + }; + + util.addEvent(panels.input, "keydown", handleCtrlYZ); + util.addEvent(panels.input, "keydown", handleModeChange); + util.addEvent(panels.input, "mousedown", function () { + setMode("moving"); + }); + + panels.input.onpaste = handlePaste; + panels.input.ondrop = handlePaste; + }; + + var init = function () { + setEventHandlers(); + refreshState(true); + saveState(); + }; + + init(); + } + + // end of UndoManager + + // The input textarea state/contents. + // This is used to implement undo/redo by the undo manager. + function TextareaState(panels, isInitialState) { + + // Aliases + var stateObj = this; + var inputArea = panels.input; + this.init = function () { + if (!util.isVisible(inputArea)) { + return; + } + if (!isInitialState && doc.activeElement && doc.activeElement !== inputArea) { // this happens when tabbing out of the input box + return; + } + + this.setInputAreaSelectionStartEnd(); + this.scrollTop = inputArea.scrollTop; + if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) { + this.text = inputArea.value; + } + + } + + // Sets the selected text in the input box after we've performed an + // operation. + this.setInputAreaSelection = function () { + + if (!util.isVisible(inputArea)) { + return; + } + + if (inputArea.selectionStart !== undefined && !uaSniffed.isOpera) { + + inputArea.focus(); + inputArea.selectionStart = stateObj.start; + inputArea.selectionEnd = stateObj.end; + inputArea.scrollTop = stateObj.scrollTop; + } + else if (doc.selection) { + + if (doc.activeElement && doc.activeElement !== inputArea) { + return; + } + + inputArea.focus(); + var range = inputArea.createTextRange(); + range.moveStart("character", -inputArea.value.length); + range.moveEnd("character", -inputArea.value.length); + range.moveEnd("character", stateObj.end); + range.moveStart("character", stateObj.start); + range.select(); + } + }; + + this.setInputAreaSelectionStartEnd = function () { + + if (!panels.ieCachedRange && (inputArea.selectionStart || inputArea.selectionStart === 0)) { + + stateObj.start = inputArea.selectionStart; + stateObj.end = inputArea.selectionEnd; + } + else if (doc.selection) { + + stateObj.text = util.fixEolChars(inputArea.value); + + // IE loses the selection in the textarea when buttons are + // clicked. On IE we cache the selection. Here, if something is cached, + // we take it. + var range = panels.ieCachedRange || doc.selection.createRange(); + + var fixedRange = util.fixEolChars(range.text); + var marker = "\x07"; + var markedRange = marker + fixedRange + marker; + range.text = markedRange; + var inputText = util.fixEolChars(inputArea.value); + + range.moveStart("character", -markedRange.length); + range.text = fixedRange; + + stateObj.start = inputText.indexOf(marker); + stateObj.end = inputText.lastIndexOf(marker) - marker.length; + + var len = stateObj.text.length - util.fixEolChars(inputArea.value).length; + + if (len) { + range.moveStart("character", -fixedRange.length); + while (len--) { + fixedRange += "\n"; + stateObj.end += 1; + } + range.text = fixedRange; + } + + if (panels.ieCachedRange) + stateObj.scrollTop = panels.ieCachedScrollTop; // this is set alongside with ieCachedRange + + panels.ieCachedRange = null; + + this.setInputAreaSelection(); + } + }; + + // Restore this state into the input area. + this.restore = function () { + + if (stateObj.text != undefined && stateObj.text != inputArea.value) { + inputArea.value = stateObj.text; + } + this.setInputAreaSelection(); + inputArea.scrollTop = stateObj.scrollTop; + }; + + // Gets a collection of HTML chunks from the inptut textarea. + this.getChunks = function () { + + var chunk = new Chunks(); + chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start)); + chunk.startTag = ""; + chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end)); + chunk.endTag = ""; + chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end)); + chunk.scrollTop = stateObj.scrollTop; + + return chunk; + }; + + // Sets the TextareaState properties given a chunk of markdown. + this.setChunks = function (chunk) { + + chunk.before = chunk.before + chunk.startTag; + chunk.after = chunk.endTag + chunk.after; + + this.start = chunk.before.length; + this.end = chunk.before.length + chunk.selection.length; + this.text = chunk.before + chunk.selection + chunk.after; + this.scrollTop = chunk.scrollTop; + }; + this.init(); + }; + + function PreviewManager(converter, panels, previewRefreshCallback) { + + var managerObj = this; + var timeout; + var elapsedTime; + var oldInputText; + var maxDelay = 3000; + var startType = "delayed"; // The other legal value is "manual" + + // Adds event listeners to elements + var setupEvents = function (inputElem, listener) { + + util.addEvent(inputElem, "input", listener); + inputElem.onpaste = listener; + inputElem.ondrop = listener; + + util.addEvent(inputElem, "keypress", listener); + util.addEvent(inputElem, "keydown", listener); + }; + + var getDocScrollTop = function () { + + var result = 0; + + if (window.innerHeight) { + result = window.pageYOffset; + } + else + if (doc.documentElement && doc.documentElement.scrollTop) { + result = doc.documentElement.scrollTop; + } + else + if (doc.body) { + result = doc.body.scrollTop; + } + + return result; + }; + + var makePreviewHtml = function () { + + // If there is no registered preview panel + // there is nothing to do. + if (!panels.preview) + return; + + + var text = panels.input.value; + if (text && text == oldInputText) { + return; // Input text hasn't changed. + } + else { + oldInputText = text; + } + + var prevTime = new Date().getTime(); + + text = converter.makeHtml(text); + + // Calculate the processing time of the HTML creation. + // It's used as the delay time in the event listener. + var currTime = new Date().getTime(); + elapsedTime = currTime - prevTime; + + pushPreviewHtml(text); + }; + + // setTimeout is already used. Used as an event listener. + var applyTimeout = function () { + + if (timeout) { + clearTimeout(timeout); + timeout = undefined; + } + + if (startType !== "manual") { + + var delay = 0; + + if (startType === "delayed") { + delay = elapsedTime; + } + + if (delay > maxDelay) { + delay = maxDelay; + } + timeout = setTimeout(makePreviewHtml, delay); + } + }; + + var getScaleFactor = function (panel) { + if (panel.scrollHeight <= panel.clientHeight) { + return 1; + } + return panel.scrollTop / (panel.scrollHeight - panel.clientHeight); + }; + + var setPanelScrollTops = function () { + if (panels.preview) { + panels.preview.scrollTop = (panels.preview.scrollHeight - panels.preview.clientHeight) * getScaleFactor(panels.preview); + } + }; + + this.refresh = function (requiresRefresh) { + + if (requiresRefresh) { + oldInputText = ""; + makePreviewHtml(); + } + else { + applyTimeout(); + } + }; + + this.processingTime = function () { + return elapsedTime; + }; + + var isFirstTimeFilled = true; + + // IE doesn't let you use innerHTML if the element is contained somewhere in a table + // (which is the case for inline editing) -- in that case, detach the element, set the + // value, and reattach. Yes, that *is* ridiculous. + var ieSafePreviewSet = function (text) { + var preview = panels.preview; + var parent = preview.parentNode; + var sibling = preview.nextSibling; + parent.removeChild(preview); + preview.innerHTML = text; + if (!sibling) + parent.appendChild(preview); + else + parent.insertBefore(preview, sibling); + } + + var nonSuckyBrowserPreviewSet = function (text) { + panels.preview.innerHTML = text; + } + + var previewSetter; + + var previewSet = function (text) { + if (previewSetter) + return previewSetter(text); + + try { + nonSuckyBrowserPreviewSet(text); + previewSetter = nonSuckyBrowserPreviewSet; + } catch (e) { + previewSetter = ieSafePreviewSet; + previewSetter(text); + } + }; + + var pushPreviewHtml = function (text) { + + var emptyTop = position.getTop(panels.input) - getDocScrollTop(); + + if (panels.preview) { + previewSet(text); + previewRefreshCallback(); + } + + setPanelScrollTops(); + + if (isFirstTimeFilled) { + isFirstTimeFilled = false; + return; + } + + var fullTop = position.getTop(panels.input) - getDocScrollTop(); + + if (uaSniffed.isIE) { + setTimeout(function () { + window.scrollBy(0, fullTop - emptyTop); + }, 0); + } + else { + window.scrollBy(0, fullTop - emptyTop); + } + }; + + var init = function () { + + setupEvents(panels.input, applyTimeout); + makePreviewHtml(); + + if (panels.preview) { + panels.preview.scrollTop = 0; + } + }; + + init(); + }; + + // Creates the background behind the hyperlink text entry box. + // And download dialog + // Most of this has been moved to CSS but the div creation and + // browser-specific hacks remain here. + ui.createBackground = function () { + + var background = doc.createElement("div"), + style = background.style; + + background.className = "wmd-prompt-background"; + + style.position = "absolute"; + style.top = "0"; + + style.zIndex = "1000"; + + if (uaSniffed.isIE) { + style.filter = "alpha(opacity=50)"; + } + else { + style.opacity = "0.5"; + } + + var pageSize = position.getPageSize(); + style.height = pageSize[1] + "px"; + + if (uaSniffed.isIE) { + style.left = doc.documentElement.scrollLeft; + style.width = doc.documentElement.clientWidth; + } + else { + style.left = "0"; + style.width = "100%"; + } + + doc.body.appendChild(background); + return background; + }; + + // This simulates a modal dialog box and asks for the URL when you + // click the hyperlink or image buttons. + // + // text: The html for the input box. + // defaultInputText: The default value that appears in the input box. + // callback: The function which is executed when the prompt is dismissed, either via OK or Cancel. + // It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel + // was chosen). + ui.prompt = function (text, defaultInputText, callback) { + + // These variables need to be declared at this level since they are used + // in multiple functions. + var dialog; // The dialog box. + var input; // The text box where you enter the hyperlink. + + + if (defaultInputText === undefined) { + defaultInputText = ""; + } + + // Used as a keydown event handler. Esc dismisses the prompt. + // Key code 27 is ESC. + var checkEscape = function (key) { + var code = (key.charCode || key.keyCode); + if (code === 27) { + close(true); + } + }; + + // Dismisses the hyperlink input box. + // isCancel is true if we don't care about the input text. + // isCancel is false if we are going to keep the text. + var close = function (isCancel) { + util.removeEvent(doc.body, "keydown", checkEscape); + var text = input.value; + + if (isCancel) { + text = null; + } + else { + // Fixes common pasting errors. + text = text.replace(/^http:\/\/(https?|ftp):\/\//, '$1://'); + if (!/^(?:https?|ftp):\/\//.test(text)) + text = 'http://' + text; + } + + dialog.parentNode.removeChild(dialog); + + callback(text); + return false; + }; + + + + // Create the text input box form/window. + var createDialog = function () { + + // The main dialog box. + dialog = doc.createElement("div"); + dialog.className = "wmd-prompt-dialog"; + dialog.style.padding = "10px;"; + dialog.style.position = "fixed"; + dialog.style.width = "400px"; + dialog.style.zIndex = "1001"; + + // The dialog text. + var question = doc.createElement("div"); + question.innerHTML = text; + question.style.padding = "5px"; + dialog.appendChild(question); + + // The web form container for the text box and buttons. + var form = doc.createElement("form"), + style = form.style; + form.onsubmit = function () { return close(false); }; + style.padding = "0"; + style.margin = "0"; + style.cssFloat = "left"; + style.width = "100%"; + style.textAlign = "center"; + style.position = "relative"; + dialog.appendChild(form); + + // The input text box + input = doc.createElement("input"); + input.type = "text"; + input.value = defaultInputText; + style = input.style; + style.display = "block"; + style.width = "80%"; + style.marginLeft = style.marginRight = "auto"; + form.appendChild(input); + + // The ok button + var okButton = doc.createElement("input"); + okButton.type = "button"; + okButton.onclick = function () { return close(false); }; + okButton.value = "OK"; + style = okButton.style; + style.margin = "10px"; + style.display = "inline"; + style.width = "7em"; + + + // The cancel button + var cancelButton = doc.createElement("input"); + cancelButton.type = "button"; + cancelButton.onclick = function () { return close(true); }; + cancelButton.value = "Cancel"; + style = cancelButton.style; + style.margin = "10px"; + style.display = "inline"; + style.width = "7em"; + + form.appendChild(okButton); + form.appendChild(cancelButton); + + util.addEvent(doc.body, "keydown", checkEscape); + dialog.style.top = "50%"; + dialog.style.left = "50%"; + dialog.style.display = "block"; + if (uaSniffed.isIE_5or6) { + dialog.style.position = "absolute"; + dialog.style.top = doc.documentElement.scrollTop + 200 + "px"; + dialog.style.left = "50%"; + } + doc.body.appendChild(dialog); + + // This has to be done AFTER adding the dialog to the form if you + // want it to be centered. + dialog.style.marginTop = -(position.getHeight(dialog) / 2) + "px"; + dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + "px"; + + }; + + // Why is this in a zero-length timeout? + // Is it working around a browser bug? + setTimeout(function () { + + createDialog(); + + var defTextLen = defaultInputText.length; + if (input.selectionStart !== undefined) { + input.selectionStart = 0; + input.selectionEnd = defTextLen; + } + else if (input.createTextRange) { + var range = input.createTextRange(); + range.collapse(false); + range.moveStart("character", -defTextLen); + range.moveEnd("character", defTextLen); + range.select(); + } + + input.focus(); + }, 0); + }; + + function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions, getString) { + + var inputBox = panels.input, + buttons = {}; // buttons.undo, buttons.link, etc. The actual DOM elements. + + makeSpritedButtonRow(); + + var keyEvent = "keydown"; + if (uaSniffed.isOpera) { + keyEvent = "keypress"; + } + + util.addEvent(inputBox, keyEvent, function (key) { + + // Check to see if we have a button key and, if so execute the callback. + if ((key.ctrlKey || key.metaKey) && !key.altKey && !key.shiftKey) { + + var keyCode = key.charCode || key.keyCode; + var keyCodeStr = String.fromCharCode(keyCode).toLowerCase(); + + switch (keyCodeStr) { + case "b": + doClick(buttons.bold); + break; + case "i": + doClick(buttons.italic); + break; + case "l": + doClick(buttons.link); + break; + case "q": + doClick(buttons.quote); + break; + case "k": + doClick(buttons.code); + break; + case "g": + doClick(buttons.image); + break; + case "o": + doClick(buttons.olist); + break; + case "u": + doClick(buttons.ulist); + break; + case "h": + doClick(buttons.heading); + break; + case "r": + doClick(buttons.hr); + break; + case "y": + doClick(buttons.redo); + break; + case "z": + if (key.shiftKey) { + doClick(buttons.redo); + } + else { + doClick(buttons.undo); + } + break; + default: + return; + } + + + if (key.preventDefault) { + key.preventDefault(); + } + + if (window.event) { + window.event.returnValue = false; + } + } + }); + + // Auto-indent on shift-enter + util.addEvent(inputBox, "keyup", function (key) { + if (key.shiftKey && !key.ctrlKey && !key.metaKey) { + var keyCode = key.charCode || key.keyCode; + // Character 13 is Enter + if (keyCode === 13) { + var fakeButton = {}; + fakeButton.textOp = bindCommand("doAutoindent"); + doClick(fakeButton); + } + } + }); + + // special handler because IE clears the context of the textbox on ESC + if (uaSniffed.isIE) { + util.addEvent(inputBox, "keydown", function (key) { + var code = key.keyCode; + if (code === 27) { + return false; + } + }); + } + + + // Perform the button's action. + function doClick(button) { + + inputBox.focus(); + + if (button.textOp) { + if (undoManager) { + undoManager.setCommandMode(); + } + + var state = new TextareaState(panels); + + if (!state) { + return; + } + + var chunks = state.getChunks(); + + // Some commands launch a "modal" prompt dialog. Javascript + // can't really make a modal dialog box and the WMD code + // will continue to execute while the dialog is displayed. + // This prevents the dialog pattern I'm used to and means + // I can't do something like this: + // + // var link = CreateLinkDialog(); + // makeMarkdownLink(link); + // + // Instead of this straightforward method of handling a + // dialog I have to pass any code which would execute + // after the dialog is dismissed (e.g. link creation) + // in a function parameter. + // + // Yes this is awkward and I think it sucks, but there's + // no real workaround. Only the image and link code + // create dialogs and require the function pointers. + var fixupInputArea = function () { + + inputBox.focus(); + + if (chunks) { + state.setChunks(chunks); + } + + state.restore(); + previewManager.refresh(); + }; + + var noCleanup = button.textOp(chunks, fixupInputArea); + + if (!noCleanup) { + fixupInputArea(); + } + + } + + if (button.execute) { + button.execute(undoManager); + } + }; + + function setupButton(button, isEnabled) { + + var normalYShift = "0px"; + var disabledYShift = "-20px"; + var highlightYShift = "-40px"; + var image = button.getElementsByTagName("span")[0]; + if (isEnabled) { + image.style.backgroundPosition = button.XShift + " " + normalYShift; + button.onmouseover = function () { + image.style.backgroundPosition = this.XShift + " " + highlightYShift; + }; + + button.onmouseout = function () { + image.style.backgroundPosition = this.XShift + " " + normalYShift; + }; + + // IE tries to select the background image "button" text (it's + // implemented in a list item) so we have to cache the selection + // on mousedown. + if (uaSniffed.isIE) { + button.onmousedown = function () { + if (doc.activeElement && doc.activeElement !== panels.input) { // we're not even in the input box, so there's no selection + return; + } + panels.ieCachedRange = document.selection.createRange(); + panels.ieCachedScrollTop = panels.input.scrollTop; + }; + } + + if (!button.isHelp) { + button.onclick = function () { + if (this.onmouseout) { + this.onmouseout(); + } + doClick(this); + return false; + } + } + } + else { + image.style.backgroundPosition = button.XShift + " " + disabledYShift; + button.onmouseover = button.onmouseout = button.onclick = function () { }; + } + } + + function bindCommand(method) { + if (typeof method === "string") + {method = commandManager[method]; + } + + return function () { method.apply(commandManager, arguments); } + } + + function makeSpritedButtonRow() { + + //var buttonBar = panels.buttonBar; + + /*var normalYShift = "0px"; + var disabledYShift = "-20px"; + var highlightYShift = "-40px"; + +
        + + + + */ + + var maintoolbar = document.getElementById("main-toolbar"); + //console.log(maintoolbar); + //buttonRow.id = "wmd-button-row" + postfix; + //buttonRow.className = 'wmd-button-row'; + //buttonRow = buttonBar.appendChild(buttonRow); + //var xPosition = 0; + + var makeButton = function (id, title, XShift, textOp) { + var button = document.createElement("button"); + button.className = "toolbutton"; + //button.style.left = xPosition + "px"; + //xPosition += 25; + //var buttonImage = document.createElement("span"); + button.id = id; + + button.title = title; + //button.XShift = XShift; + if (textOp) + button.textOp = textOp; + //setupButton(button, true); + //buttonRow.appendChild(button); + + maintoolbar.appendChild(button); + return button; + }; + /* + var makeSpacer = function (num) { + var spacer = document.createElement("li"); + spacer.className = "wmd-spacer wmd-spacer" + num; + spacer.id = "wmd-spacer" + num + postfix; + buttonRow.appendChild(spacer); + xPosition += 25; + } + */ + if(doc.getElementById("wmd-bold-button-second")==null){ + buttons.bold = makeButton("wmd-bold-button-second", getString("bold"), "0px", bindCommand("doBold")); + buttons.bold.onclick = function (){doClick(buttons.bold);}; + + buttons.italic = makeButton("wmd-italic-button-second", getString("italic"), "-20px", bindCommand("doItalic")); + buttons.italic.onclick = function (){ doClick(buttons.italic);}; + + buttons.heading = makeButton("wmd-heading-button", getString("heading"), "-160px", bindCommand("doHeading")); + buttons.heading.onclick = function (){ doClick(buttons.heading);}; + + buttons.hr = makeButton("wmd-hr-button", getString("hr"), "-180px", bindCommand("doHorizontalRule")); + buttons.hr.onclick = function (){ doClick(buttons.hr);}; + + buttons.olist = makeButton("wmd-olist-button", getString("olist"), "-120px", bindCommand(function (chunk, postProcessing) { + this.doList(chunk, postProcessing, true); + })); + buttons.olist.onclick = function () {doClick(buttons.olist);}; + + buttons.ulist = makeButton("wmd-ulist-button", getString("ulist"), "-140px", bindCommand(function (chunk, postProcessing) { + this.doList(chunk, postProcessing, false); + })); + buttons.ulist.onclick = function () {doClick(buttons.ulist);}; + + buttons.code = makeButton("wmd-code-button", getString("code"), "-80px", bindCommand("doCode")); + buttons.code.onclick = function () {doClick(buttons.code);}; + + buttons.quote = makeButton("wmd-quote-button", getString("quote"), "-60px", bindCommand("doBlockquote")); + buttons.quote.onclick = function () {doClick(buttons.quote);}; + + buttons.link = makeButton("wmd-link-button", getString("link"), "-40px", bindCommand(function (chunk, postProcessing) { + return this.doLinkOrImage(chunk, postProcessing, false); + })); + buttons.link.onclick = function () {doClick(buttons.link);}; + + var redoTitle = /win/.test(nav.platform.toLowerCase()) ? + getString("redo") : + getString("redomac"); // mac and other non-Windows platforms + + buttons.undo = makeButton("wmd-undo-button", getString("undo"), "-200px", null); + buttons.undo.execute = function (manager) { if (manager) manager.undo(); }; + buttons.undo.onclick = function () {doClick(buttons.undo);} + + buttons.redo = makeButton("wmd-redo-button", redoTitle, "-220px", null); + buttons.redo.execute = function (manager) { if (manager) manager.redo(); }; + buttons.redo.onclick = function () {doClick(buttons.redo);} + + } + + + //makeSpacer(1); + /* + + + buttons.code = makeButton("wmd-code-button", getString("code"), "-80px", bindCommand("doCode")); + buttons.image = makeButton("wmd-image-button", getString("image"), "-100px", bindCommand(function (chunk, postProcessing) { + return this.doLinkOrImage(chunk, postProcessing, true); + })); + //makeSpacer(2); + + + //makeSpacer(3); + + */ + + + if (helpOptions) { + var helpButton = document.createElement("li"); + var helpButtonImage = document.createElement("span"); + helpButton.appendChild(helpButtonImage); + helpButton.className = "wmd-button wmd-help-button"; + helpButton.id = "wmd-help-button" + postfix; + helpButton.XShift = "-240px"; + helpButton.isHelp = true; + helpButton.style.right = "0px"; + helpButton.title = getString("help"); + helpButton.onclick = helpOptions.handler; + + //setupButton(helpButton, true); + //buttonRow.appendChild(helpButton); + buttons.help = helpButton; + } + + setUndoRedoButtonStates(); + } + + function setUndoRedoButtonStates() { + if (undoManager) { + // setupButton(buttons.undo, undoManager.canUndo()); + // setupButton(buttons.redo, undoManager.canRedo()); + } + }; + + this.setUndoRedoButtonStates = setUndoRedoButtonStates; + + } + + function CommandManager(pluginHooks, getString) { + this.hooks = pluginHooks; + this.getString = getString; + } + + var commandProto = CommandManager.prototype; + + // The markdown symbols - 4 spaces = code, > = blockquote, etc. + commandProto.prefixes = "(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)"; + + // Remove markdown symbols from the chunk selection. + commandProto.unwrap = function (chunk) { + var txt = new re("([^\\n])\\n(?!(\\n|" + this.prefixes + "))", "g"); + chunk.selection = chunk.selection.replace(txt, "$1 $2"); + }; + + commandProto.wrap = function (chunk, len) { + this.unwrap(chunk); + var regex = new re("(.{1," + len + "})( +|$\\n?)", "gm"), + that = this; + + chunk.selection = chunk.selection.replace(regex, function (line, marked) { + if (new re("^" + that.prefixes, "").test(line)) { + return line; + } + return marked + "\n"; + }); + + chunk.selection = chunk.selection.replace(/\s+$/, ""); + }; + + commandProto.doBold = function (chunk, postProcessing) { + return this.doBorI(chunk, postProcessing, 2, this.getString("boldexample")); + }; + + commandProto.doItalic = function (chunk, postProcessing) { + return this.doBorI(chunk, postProcessing, 1, this.getString("italicexample")); + }; + + // chunk: The selected region that will be enclosed with */** + // nStars: 1 for italics, 2 for bold + // insertText: If you just click the button without highlighting text, this gets inserted + commandProto.doBorI = function (chunk, postProcessing, nStars, insertText) { + + // Get rid of whitespace and fixup newlines. + chunk.trimWhitespace(); + chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n"); + + // Look for stars before and after. Is the chunk already marked up? + // note that these regex matches cannot fail + var starsBefore = /(\**$)/.exec(chunk.before)[0]; + var starsAfter = /(^\**)/.exec(chunk.after)[0]; + + var prevStars = Math.min(starsBefore.length, starsAfter.length); + + // Remove stars if we have to since the button acts as a toggle. + if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) { + chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), ""); + chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), ""); + } + else if (!chunk.selection && starsAfter) { + // It's not really clear why this code is necessary. It just moves + // some arbitrary stuff around. + chunk.after = chunk.after.replace(/^([*_]*)/, ""); + chunk.before = chunk.before.replace(/(\s?)$/, ""); + var whitespace = re.$1; + chunk.before = chunk.before + starsAfter + whitespace; + } + else { + + // In most cases, if you don't have any selected text and click the button + // you'll get a selected, marked up region with the default text inserted. + if (!chunk.selection && !starsAfter) { + chunk.selection = insertText; + } + + // Add the true markup. + var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ? + chunk.before = chunk.before + markup; + chunk.after = markup + chunk.after; + } + + return; + }; + + commandProto.stripLinkDefs = function (text, defsToAdd) { + + text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm, + function (totalMatch, id, link, newlines, title) { + defsToAdd[id] = totalMatch.replace(/\s*$/, ""); + if (newlines) { + // Strip the title and return that separately. + defsToAdd[id] = totalMatch.replace(/["(](.+?)[")]$/, ""); + return newlines + title; + } + return ""; + }); + + return text; + }; + + commandProto.addLinkDef = function (chunk, linkDef) { + + var refNumber = 0; // The current reference number + var defsToAdd = {}; // + // Start with a clean slate by removing all previous link definitions. + chunk.before = this.stripLinkDefs(chunk.before, defsToAdd); + chunk.selection = this.stripLinkDefs(chunk.selection, defsToAdd); + chunk.after = this.stripLinkDefs(chunk.after, defsToAdd); + + var defs = ""; + var regex = /(\[)((?:\[[^\]]*\]|[^\[\]])*)(\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g; + + var addDefNumber = function (def) { + refNumber++; + def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, " [" + refNumber + "]:"); + defs += "\n" + def; + }; + + // note that + // a) the recursive call to getLink cannot go infinite, because by definition + // of regex, inner is always a proper substring of wholeMatch, and + // b) more than one level of nesting is neither supported by the regex + // nor making a lot of sense (the only use case for nesting is a linked image) + var getLink = function (wholeMatch, before, inner, afterInner, id, end) { + inner = inner.replace(regex, getLink); + if (defsToAdd[id]) { + addDefNumber(defsToAdd[id]); + return before + inner + afterInner + refNumber + end; + } + return wholeMatch; + }; + + chunk.before = chunk.before.replace(regex, getLink); + + if (linkDef) { + addDefNumber(linkDef); + } + else { + chunk.selection = chunk.selection.replace(regex, getLink); + } + + var refOut = refNumber; + + chunk.after = chunk.after.replace(regex, getLink); + + if (chunk.after) { + chunk.after = chunk.after.replace(/\n*$/, ""); + } + if (!chunk.after) { + chunk.selection = chunk.selection.replace(/\n*$/, ""); + } + + chunk.after += "\n\n" + defs; + + return refOut; + }; + + // takes the line as entered into the add link/as image dialog and makes + // sure the URL and the optinal title are "nice". + function properlyEncoded(linkdef) { + return linkdef.replace(/^\s*(.*?)(?:\s+"(.+)")?\s*$/, function (wholematch, link, title) { + link = link.replace(/\?.*$/, function (querypart) { + return querypart.replace(/\+/g, " "); // in the query string, a plus and a space are identical + }); + link = decodeURIComponent(link); // unencode first, to prevent double encoding + link = encodeURI(link).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29'); + link = link.replace(/\?.*$/, function (querypart) { + return querypart.replace(/\+/g, "%2b"); // since we replaced plus with spaces in the query part, all pluses that now appear where originally encoded + }); + if (title) { + title = title.trim ? title.trim() : title.replace(/^\s*/, "").replace(/\s*$/, ""); + title = title.replace(/"/g, "quot;").replace(/\(/g, "(").replace(/\)/g, ")").replace(//g, ">"); + } + return title ? link + ' "' + title + '"' : link; + }); + } + + commandProto.doLinkOrImage = function (chunk, postProcessing, isImage) { + + chunk.trimWhitespace(); + chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/); + var background; + + if (chunk.endTag.length > 1 && chunk.startTag.length > 0) { + + chunk.startTag = chunk.startTag.replace(/!?\[/, ""); + chunk.endTag = ""; + this.addLinkDef(chunk, null); + + } + else { + + // We're moving start and end tag back into the selection, since (as we're in the else block) we're not + // *removing* a link, but *adding* one, so whatever findTags() found is now back to being part of the + // link text. linkEnteredCallback takes care of escaping any brackets. + chunk.selection = chunk.startTag + chunk.selection + chunk.endTag; + chunk.startTag = chunk.endTag = ""; + + if (/\n\n/.test(chunk.selection)) { + this.addLinkDef(chunk, null); + return; + } + var that = this; + // The function to be executed when you enter a link and press OK or Cancel. + // Marks up the link and adds the ref. + var linkEnteredCallback = function (link) { + + background.parentNode.removeChild(background); + + if (link !== null) { + // ( $1 + // [^\\] anything that's not a backslash + // (?:\\\\)* an even number (this includes zero) of backslashes + // ) + // (?= followed by + // [[\]] an opening or closing bracket + // ) + // + // In other words, a non-escaped bracket. These have to be escaped now to make sure they + // don't count as the end of the link or similar. + // Note that the actual bracket has to be a lookahead, because (in case of to subsequent brackets), + // the bracket in one match may be the "not a backslash" character in the next match, so it + // should not be consumed by the first match. + // The "prepend a space and finally remove it" steps makes sure there is a "not a backslash" at the + // start of the string, so this also works if the selection begins with a bracket. We cannot solve + // this by anchoring with ^, because in the case that the selection starts with two brackets, this + // would mean a zero-width match at the start. Since zero-width matches advance the string position, + // the first bracket could then not act as the "not a backslash" for the second. + chunk.selection = (" " + chunk.selection).replace(/([^\\](?:\\\\)*)(?=[[\]])/g, "$1\\").substr(1); + + var linkDef = " [999]: " + properlyEncoded(link); + + var num = that.addLinkDef(chunk, linkDef); + chunk.startTag = isImage ? "![" : "["; + chunk.endTag = "][" + num + "]"; + + if (!chunk.selection) { + if (isImage) { + chunk.selection = that.getString("imagedescription"); + } + else { + chunk.selection = that.getString("linkdescription"); + } + } + } + postProcessing(); + }; + + background = ui.createBackground(); + + if (isImage) { + if (!this.hooks.insertImageDialog(linkEnteredCallback)) + ui.prompt(this.getString("imagedialog"), imageDefaultText, linkEnteredCallback); + } + else { + ui.prompt(this.getString("linkdialog"), linkDefaultText, linkEnteredCallback); + } + return true; + } + }; + + // When making a list, hitting shift-enter will put your cursor on the next line + // at the current indent level. + commandProto.doAutoindent = function (chunk, postProcessing) { + + var commandMgr = this, + fakeSelection = false; + + chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n"); + chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n"); + chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n"); + + // There's no selection, end the cursor wasn't at the end of the line: + // The user wants to split the current list item / code line / blockquote line + // (for the latter it doesn't really matter) in two. Temporarily select the + // (rest of the) line to achieve this. + if (!chunk.selection && !/^[ \t]*(?:\n|$)/.test(chunk.after)) { + chunk.after = chunk.after.replace(/^[^\n]*/, function (wholeMatch) { + chunk.selection = wholeMatch; + return ""; + }); + fakeSelection = true; + } + + if (/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(chunk.before)) { + if (commandMgr.doList) { + commandMgr.doList(chunk); + } + } + if (/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)) { + if (commandMgr.doBlockquote) { + commandMgr.doBlockquote(chunk); + } + } + if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) { + if (commandMgr.doCode) { + commandMgr.doCode(chunk); + } + } + + if (fakeSelection) { + chunk.after = chunk.selection + chunk.after; + chunk.selection = ""; + } + }; + + commandProto.doBlockquote = function (chunk, postProcessing) { + + chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/, + function (totalMatch, newlinesBefore, text, newlinesAfter) { + chunk.before += newlinesBefore; + chunk.after = newlinesAfter + chunk.after; + return text; + }); + + chunk.before = chunk.before.replace(/(>[ \t]*)$/, + function (totalMatch, blankLine) { + chunk.selection = blankLine + chunk.selection; + return ""; + }); + + chunk.selection = chunk.selection.replace(/^(\s|>)+$/, ""); + chunk.selection = chunk.selection || this.getString("quoteexample"); + + // The original code uses a regular expression to find out how much of the + // text *directly before* the selection already was a blockquote: + + /* + if (chunk.before) { + chunk.before = chunk.before.replace(/\n?$/, "\n"); + } + chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/, + function (totalMatch) { + chunk.startTag = totalMatch; + return ""; + }); + */ + + // This comes down to: + // Go backwards as many lines a possible, such that each line + // a) starts with ">", or + // b) is almost empty, except for whitespace, or + // c) is preceeded by an unbroken chain of non-empty lines + // leading up to a line that starts with ">" and at least one more character + // and in addition + // d) at least one line fulfills a) + // + // Since this is essentially a backwards-moving regex, it's susceptible to + // catstrophic backtracking and can cause the browser to hang; + // see e.g. http://meta.stackoverflow.com/questions/9807. + // + // Hence we replaced this by a simple state machine that just goes through the + // lines and checks for a), b), and c). + + var match = "", + leftOver = "", + line; + if (chunk.before) { + var lines = chunk.before.replace(/\n$/, "").split("\n"); + var inChain = false; + for (var i = 0; i < lines.length; i++) { + var good = false; + line = lines[i]; + inChain = inChain && line.length > 0; // c) any non-empty line continues the chain + if (/^>/.test(line)) { // a) + good = true; + if (!inChain && line.length > 1) // c) any line that starts with ">" and has at least one more character starts the chain + inChain = true; + } else if (/^[ \t]*$/.test(line)) { // b) + good = true; + } else { + good = inChain; // c) the line is not empty and does not start with ">", so it matches if and only if we're in the chain + } + if (good) { + match += line + "\n"; + } else { + leftOver += match + line; + match = "\n"; + } + } + if (!/(^|\n)>/.test(match)) { // d) + leftOver += match; + match = ""; + } + } + + chunk.startTag = match; + chunk.before = leftOver; + + // end of change + + if (chunk.after) { + chunk.after = chunk.after.replace(/^\n?/, "\n"); + } + + chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/, + function (totalMatch) { + chunk.endTag = totalMatch; + return ""; + } + ); + + var replaceBlanksInTags = function (useBracket) { + + var replacement = useBracket ? "> " : ""; + + if (chunk.startTag) { + chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/, + function (totalMatch, markdown) { + return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n"; + }); + } + if (chunk.endTag) { + chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/, + function (totalMatch, markdown) { + return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n"; + }); + } + }; + + if (/^(?![ ]{0,3}>)/m.test(chunk.selection)) { + this.wrap(chunk, SETTINGS.lineLength - 2); + chunk.selection = chunk.selection.replace(/^/gm, "> "); + replaceBlanksInTags(true); + chunk.skipLines(); + } else { + chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, ""); + this.unwrap(chunk); + replaceBlanksInTags(false); + + if (!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag) { + chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n"); + } + + if (!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag) { + chunk.endTag = chunk.endTag.replace(/^\n{0,2}/, "\n\n"); + } + } + + chunk.selection = this.hooks.postBlockquoteCreation(chunk.selection); + + if (!/\n/.test(chunk.selection)) { + chunk.selection = chunk.selection.replace(/^(> *)/, + function (wholeMatch, blanks) { + chunk.startTag += blanks; + return ""; + }); + } + }; + + commandProto.doCode = function (chunk, postProcessing) { + + var hasTextBefore = /\S[ ]*$/.test(chunk.before); + var hasTextAfter = /^[ ]*\S/.test(chunk.after); + + // Use 'four space' markdown if the selection is on its own + // line or is multiline. + if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) { + + chunk.before = chunk.before.replace(/[ ]{4}$/, + function (totalMatch) { + chunk.selection = totalMatch + chunk.selection; + return ""; + }); + + var nLinesBack = 1; + var nLinesForward = 1; + + if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) { + nLinesBack = 0; + } + if (/^\n(\t|[ ]{4,})/.test(chunk.after)) { + nLinesForward = 0; + } + + chunk.skipLines(nLinesBack, nLinesForward); + + if (!chunk.selection) { + chunk.startTag = " "; + chunk.selection = this.getString("codeexample"); + } + else { + if (/^[ ]{0,3}\S/m.test(chunk.selection)) { + if (/\n/.test(chunk.selection)) + chunk.selection = chunk.selection.replace(/^/gm, " "); + else // if it's not multiline, do not select the four added spaces; this is more consistent with the doList behavior + chunk.before += " "; + } + else { + chunk.selection = chunk.selection.replace(/^(?:[ ]{4}|[ ]{0,3}\t)/gm, ""); + } + } + } + else { + // Use backticks (`) to delimit the code block. + + chunk.trimWhitespace(); + chunk.findTags(/`/, /`/); + + if (!chunk.startTag && !chunk.endTag) { + chunk.startTag = chunk.endTag = "`"; + if (!chunk.selection) { + chunk.selection = this.getString("codeexample"); + } + } + else if (chunk.endTag && !chunk.startTag) { + chunk.before += chunk.endTag; + chunk.endTag = ""; + } + else { + chunk.startTag = chunk.endTag = ""; + } + } + }; + + commandProto.doList = function (chunk, postProcessing, isNumberedList) { + + // These are identical except at the very beginning and end. + // Should probably use the regex extension function to make this clearer. + var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/; + var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/; + + // The default bullet is a dash but others are possible. + // This has nothing to do with the particular HTML bullet, + // it's just a markdown bullet. + var bullet = "-"; + + // The number in a numbered list. + var num = 1; + + // Get the item prefix - e.g. " 1. " for a numbered list, " - " for a bulleted list. + var getItemPrefix = function () { + var prefix; + if (isNumberedList) { + prefix = " " + num + ". "; + num++; + } + else { + prefix = " " + bullet + " "; + } + return prefix; + }; + + // Fixes the prefixes of the other list items. + var getPrefixedItem = function (itemText) { + + // The numbering flag is unset when called by autoindent. + if (isNumberedList === undefined) { + isNumberedList = /^\s*\d/.test(itemText); + } + + // Renumber/bullet the list element. + itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm, + function (_) { + return getItemPrefix(); + }); + + return itemText; + }; + + chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null); + + if (chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)) { + chunk.before += chunk.startTag; + chunk.startTag = ""; + } + + if (chunk.startTag) { + + var hasDigits = /\d+[.]/.test(chunk.startTag); + chunk.startTag = ""; + chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n"); + this.unwrap(chunk); + chunk.skipLines(); + + if (hasDigits) { + // Have to renumber the bullet points if this is a numbered list. + chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem); + } + if (isNumberedList == hasDigits) { + return; + } + } + + var nLinesUp = 1; + + chunk.before = chunk.before.replace(previousItemsRegex, + function (itemText) { + if (/^\s*([*+-])/.test(itemText)) { + bullet = re.$1; + } + nLinesUp = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0; + return getPrefixedItem(itemText); + }); + + if (!chunk.selection) { + chunk.selection = this.getString("litem"); + } + + var prefix = getItemPrefix(); + + var nLinesDown = 1; + + chunk.after = chunk.after.replace(nextItemsRegex, + function (itemText) { + nLinesDown = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0; + return getPrefixedItem(itemText); + }); + + chunk.trimWhitespace(true); + chunk.skipLines(nLinesUp, nLinesDown, true); + chunk.startTag = prefix; + var spaces = prefix.replace(/./g, " "); + this.wrap(chunk, SETTINGS.lineLength - spaces.length); + chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces); + + }; + + commandProto.doHeading = function (chunk, postProcessing) { + + // Remove leading/trailing whitespace and reduce internal spaces to single spaces. + chunk.selection = chunk.selection.replace(/\s+/g, " "); + chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, ""); + + // If we clicked the button with no selected text, we just + // make a level 2 hash header around some default text. + if (!chunk.selection) { + chunk.startTag = "## "; + chunk.selection = this.getString("headingexample"); + chunk.endTag = " ##"; + return; + } + + var headerLevel = 0; // The existing header level of the selected text. + + // Remove any existing hash heading markdown and save the header level. + chunk.findTags(/#+[ ]*/, /[ ]*#+/); + if (/#+/.test(chunk.startTag)) { + headerLevel = re.lastMatch.length; + } + chunk.startTag = chunk.endTag = ""; + + // Try to get the current header level by looking for - and = in the line + // below the selection. + chunk.findTags(null, /\s?(-+|=+)/); + if (/=+/.test(chunk.endTag)) { + headerLevel = 1; + } + if (/-+/.test(chunk.endTag)) { + headerLevel = 2; + } + + // Skip to the next line so we can create the header markdown. + chunk.startTag = chunk.endTag = ""; + chunk.skipLines(1, 1); + + // We make a level 2 header if there is no current header. + // If there is a header level, we substract one from the header level. + // If it's already a level 1 header, it's removed. + var headerLevelToCreate = headerLevel == 0 ? 2 : headerLevel - 1; + + if (headerLevelToCreate > 0) { + + // The button only creates level 1 and 2 underline headers. + // Why not have it iterate over hash header levels? Wouldn't that be easier and cleaner? + var headerChar = headerLevelToCreate >= 2 ? "-" : "="; + var len = chunk.selection.length; + if (len > SETTINGS.lineLength) { + len = SETTINGS.lineLength; + } + chunk.endTag = "\n"; + while (len--) { + chunk.endTag += headerChar; + } + } + }; + + commandProto.doHorizontalRule = function (chunk, postProcessing) { + chunk.startTag = "----------\n"; + chunk.selection = ""; + chunk.skipLines(2, 1, true); + } + + +})(); \ No newline at end of file diff --git a/activities/Markdown.activity/lib/Markdown.Sanitizer.js b/activities/Markdown.activity/lib/Markdown.Sanitizer.js new file mode 100644 index 000000000..277d702a7 --- /dev/null +++ b/activities/Markdown.activity/lib/Markdown.Sanitizer.js @@ -0,0 +1,108 @@ +(function () { + var output, Converter; + if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module + output = exports; + Converter = require("./Markdown.Converter").Converter; + } else { + output = window.Markdown; + Converter = Markdown.Converter; + } + + output.getSanitizingConverter = function () { + var converter = new Converter(); + converter.hooks.chain("postConversion", sanitizeHtml); + converter.hooks.chain("postConversion", balanceTags); + return converter; + } + + function sanitizeHtml(html) { + return html.replace(/<[^>]*>?/gi, sanitizeTag); + } + + // (tags that can be opened/closed) | (tags that stand alone) + var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i; + // | + var a_white = /^(]+")?\s?>|<\/a>)$/i; + + // ]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i; + + function sanitizeTag(tag) { + if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white)) + return tag; + else + return ""; + } + + /// + /// attempt to balance HTML tags in the html string + /// by removing any unmatched opening or closing tags + /// IMPORTANT: we *assume* HTML has *already* been + /// sanitized and is safe/sane before balancing! + /// + /// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593 + /// + function balanceTags(html) { + + if (html == "") + return ""; + + var re = /<\/?\w+[^>]*(\s|$|>)/g; + // convert everything to lower case; this makes + // our case insensitive comparisons easier + var tags = html.toLowerCase().match(re); + + // no HTML tags present? nothing to do; exit now + var tagcount = (tags || []).length; + if (tagcount == 0) + return html; + + var tagname, tag; + var ignoredtags = "



      • "; + var match; + var tagpaired = []; + var tagremove = []; + var needsRemoval = false; + + // loop through matched tags in forward order + for (var ctag = 0; ctag < tagcount; ctag++) { + tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1"); + // skip any already paired tags + // and skip tags in our ignore list; assume they're self-closed + if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1) + continue; + + tag = tags[ctag]; + match = -1; + + if (!/^<\//.test(tag)) { + // this is an opening tag + // search forwards (next tags), look for closing tags + for (var ntag = ctag + 1; ntag < tagcount; ntag++) { + if (!tagpaired[ntag] && tags[ntag] == "") { + match = ntag; + break; + } + } + } + + if (match == -1) + needsRemoval = tagremove[ctag] = true; // mark for removal + else + tagpaired[match] = true; // mark paired + } + + if (!needsRemoval) + return html; + + // delete all orphaned tags from the string + + var ctag = 0; + html = html.replace(re, function (match) { + var res = tagremove[ctag] ? "" : match; + ctag++; + return res; + }); + return html; + } +})(); diff --git a/activities/Markdown.activity/lib/domReady.js b/activities/Markdown.activity/lib/domReady.js new file mode 100644 index 000000000..2b5412209 --- /dev/null +++ b/activities/Markdown.activity/lib/domReady.js @@ -0,0 +1,129 @@ +/** + * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/domReady for details + */ +/*jslint */ +/*global require: false, define: false, requirejs: false, + window: false, clearInterval: false, document: false, + self: false, setInterval: false */ + + +define(function () { + 'use strict'; + + var isTop, testDiv, scrollIntervalId, + isBrowser = typeof window !== "undefined" && window.document, + isPageLoaded = !isBrowser, + doc = isBrowser ? document : null, + readyCalls = []; + + function runCallbacks(callbacks) { + var i; + for (i = 0; i < callbacks.length; i += 1) { + callbacks[i](doc); + } + } + + function callReady() { + var callbacks = readyCalls; + + if (isPageLoaded) { + //Call the DOM ready callbacks + if (callbacks.length) { + readyCalls = []; + runCallbacks(callbacks); + } + } + } + + /** + * Sets the page as loaded. + */ + function pageLoaded() { + if (!isPageLoaded) { + isPageLoaded = true; + if (scrollIntervalId) { + clearInterval(scrollIntervalId); + } + + callReady(); + } + } + + if (isBrowser) { + if (document.addEventListener) { + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. + document.addEventListener("DOMContentLoaded", pageLoaded, false); + window.addEventListener("load", pageLoaded, false); + } else if (window.attachEvent) { + window.attachEvent("onload", pageLoaded); + + testDiv = document.createElement('div'); + try { + isTop = window.frameElement === null; + } catch (e) {} + + //DOMContentLoaded approximation that uses a doScroll, as found by + //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, + //but modified by other contributors, including jdalton + if (testDiv.doScroll && isTop && window.external) { + scrollIntervalId = setInterval(function () { + try { + testDiv.doScroll(); + pageLoaded(); + } catch (e) {} + }, 30); + } + } + + //Check if document already complete, and if so, just trigger page load + //listeners. Latest webkit browsers also use "interactive", and + //will fire the onDOMContentLoaded before "interactive" but not after + //entering "interactive" or "complete". More details: + //http://dev.w3.org/html5/spec/the-end.html#the-end + //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded + //Hmm, this is more complicated on further use, see "firing too early" + //bug: https://github.com/requirejs/domReady/issues/1 + //so removing the || document.readyState === "interactive" test. + //There is still a window.onload binding that should get fired if + //DOMContentLoaded is missed. + if (document.readyState === "complete") { + pageLoaded(); + } + } + + /** START OF PUBLIC API **/ + + /** + * Registers a callback for DOM ready. If DOM is already ready, the + * callback is called immediately. + * @param {Function} callback + */ + function domReady(callback) { + if (isPageLoaded) { + callback(doc); + } else { + readyCalls.push(callback); + } + return domReady; + } + + domReady.version = '2.0.1'; + + /** + * Loader Plugin API method + */ + domReady.load = function (name, req, onLoad, config) { + if (config.isBuild) { + onLoad(null); + } else { + domReady(onLoad); + } + }; + + /** END OF PUBLIC API **/ + + return domReady; +}); diff --git a/activities/Markdown.activity/lib/mustache.js b/activities/Markdown.activity/lib/mustache.js new file mode 100644 index 000000000..932052b4a --- /dev/null +++ b/activities/Markdown.activity/lib/mustache.js @@ -0,0 +1,610 @@ +/*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + */ + +/*global define: false*/ + +(function (root, factory) { + if (typeof exports === "object" && exports) { + module.exports = factory; // CommonJS + } else if (typeof define === "function" && define.amd) { + define(factory); // AMD + } else { + root.Mustache = factory; // + + +
        + + + + +
        +
        + +
        + +
        + + diff --git a/activities/Memorize.activity/js/activity.js b/activities/Memorize.activity/js/activity.js new file mode 100644 index 000000000..20a5f96b0 --- /dev/null +++ b/activities/Memorize.activity/js/activity.js @@ -0,0 +1,85 @@ +define(function (require) { + var activity = require("sugar-web/activity/activity"); + var menupalette = require("sugar-web/graphics/menupalette"); + var mustache = require("mustache"); + + var model = require("activity/model"); + var view = require("activity/view"); + var controller = require("activity/controller"); + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + // Initialize the activity. + activity.setup(); + + // Example game. + var cardsSet = [ + {question: '1+1', answer: '2'}, + {question: '3+0', answer: '3'}, + {question: '2+2', answer: '4'}, + {question: '3+2', answer: '5'}, + {question: '2+4', answer: '6'}, + {question: '5+2', answer: '7'}, + {question: '7+1', answer: '8'}, + {question: '5+4', answer: '9'}, + {question: '2+8', answer: '10'}, + {question: '10+1', answer: '11'}, + {question: '7+5', answer: '12'}, + {question: '4+9', answer: '13'}, + {question: '11+3', answer: '14'}, + {question: '6+9', answer: '15'}, + {question: '10+6', answer: '16'}, + {question: '5+12', answer: '17'}, + {question: '9+9', answer: '18'}, + {question: '6+13', answer: '19'} + ]; + + function Memorize() { + this.model = new model.Model(); + this.view = new view.View(); + this.controller = new controller.Controller(this.model, this.view); + } + + memorize = new Memorize(); + memorize.model.loadGame(cardsSet); + memorize.controller.newGame(4); + + var menuData = [ + {label: "4 X 4", id: "four-button", icon: true}, + {label: "5 X 5", id: "five-button", icon: true}, + {label: "6 X 6", id: "six-button", icon: true} + ]; + + var changePaletteIcon = function (button) { + var span = button.querySelector('span'); + var style = span.currentStyle || window.getComputedStyle(span, ''); + sizeButton.style.backgroundImage = style.backgroundImage; + var invoker = sizePalette.getPalette().querySelector('.palette-invoker'); + invoker.style.backgroundImage = style.backgroundImage; + }; + + function onSizeSelected(event) { + if (event.detail.target.id == "four-button") { + memorize.controller.newGame(4); + } + if (event.detail.target.id == "five-button") { + memorize.controller.newGame(5); + } + if (event.detail.target.id == "six-button") { + memorize.controller.newGame(6); + } + changePaletteIcon(event.detail.target); + } + + var sizeButton = document.getElementById("size-button"); + var sizePalette = new menupalette.MenuPalette(sizeButton, "Change size", + menuData); + sizePalette.addEventListener('selectItem', onSizeSelected); + + var restartButton = document.getElementById("restart-button"); + restartButton.onclick = function () { + memorize.controller.newGame(); + }; + }); +}); diff --git a/activities/Memorize.activity/js/controller.js b/activities/Memorize.activity/js/controller.js new file mode 100644 index 000000000..494c97ab0 --- /dev/null +++ b/activities/Memorize.activity/js/controller.js @@ -0,0 +1,61 @@ +define(function (require) { + + var controller = {}; + + controller.Controller = function (model, view) { + this.model = model; + this.view = view; + }; + + controller.Controller.prototype.newGame = function (size) { + var size = size || this.model.size; + this.model.createGame(size); + this.view.createView(size); + this.update(); + }; + + // Add callback to click events of table buttons. + controller.Controller.prototype.update = function () { + var that = this; + + var buttonPressed = function (e) { + var cardPosition = this.getAttribute('id'); + + if (that.model.prohibitedMove(cardPosition)) { + return; + } + + var result = that.model.selectCard(cardPosition); + + that.view.unfoldCard(this, result.cardContent); + + if (result.end) { + var match = that.model.checkMatches(); + if (match) { + that.view.highlightCards(that.model.currentUnfoldedCards); + that.model.unlockMove(); + + if (that.model.gameDone()) { + that.view.highlightDone(that.model.unfoldedCards); + } + } + else { + // Wait a second, fold them again. + window.setTimeout(function () { + that.view.foldCards(that.model.currentUnfoldedCards); + that.model.unlockMove(); + }, 1000); + } + } + }; + + var buttons = document.querySelectorAll("#buttons-table button"); + for (var i = 0; i < buttons.length; i++) { + var button = buttons[i]; + button.addEventListener('click', buttonPressed); + } + }; + + return controller; + +}); diff --git a/activities/Memorize.activity/js/loader.js b/activities/Memorize.activity/js/loader.js new file mode 100644 index 000000000..01021e981 --- /dev/null +++ b/activities/Memorize.activity/js/loader.js @@ -0,0 +1,8 @@ +requirejs.config({ + baseUrl: "lib", + paths: { + activity: "../js" + } +}); + +requirejs(["activity/activity"]); diff --git a/activities/Memorize.activity/js/model.js b/activities/Memorize.activity/js/model.js new file mode 100644 index 000000000..f51a78a6d --- /dev/null +++ b/activities/Memorize.activity/js/model.js @@ -0,0 +1,147 @@ +define(function (require) { + + var model = {}; + + // Utility to shuffle array elements. + function shuffle(array) { + var counter = array.length; + var temp; + var index; + + // While there are elements in the array + while (counter > 0) { + // Pick a random index + index = (Math.random() * counter--) | 0; + + // And swap the last element with it + temp = array[counter]; + array[counter] = array[index]; + array[index] = temp; + } + + return array; + } + + model.Model = function () { + this.cardsSet = undefined; + this.inGameCards = undefined; + this.size = undefined; + this.status = undefined; + this.selectedQuestion = undefined; + this.selectedAnswer = undefined; + this.unfoldedCards = undefined; + this.currentUnfoldedCards = undefined; + }; + + model.Model.prototype.loadGame = function (cardsSet) { + this.cardsSet = cardsSet; + }; + + // Initialize gameCards, the set of cards shuffled and grouped in + // two suits: questions and answers. + model.Model.prototype.createGame = function (size) { + this.size = size || this.size || 4; + this.status = "selecting question"; + this.selectedQuestion = undefined; + this.selectedAnswer = undefined; + this.unfoldedCards = []; + this.currentUnfoldedCards = []; + + var pairs = Math.floor(this.size * this.size / 2); + var questions = []; + var answers = []; + + // Clone the set of cards, shuffle them, and select the needed + // pairs. + var subset = shuffle(this.cardsSet.slice(0)).slice(0, pairs); + + for (var i = 0; i < subset.length; i ++) { + questions.push(subset[i].question); + answers.push(subset[i].answer); + } + + this.inGameCards = shuffle(questions).concat(shuffle(answers)); + }; + + model.Model.prototype.unlockMove = function (cardPosition) { + this.status = "selecting question"; + }; + + // Return true if the move is prohibited in the current game + // status. + model.Model.prototype.prohibitedMove = function (cardPosition) { + // The user is not allowed to move in the current status. + if (this.status == "selecting none") { + return true; + } + + // The user should select a question but is trying to select + // an answer. + if (this.status == "selecting question") { + if (cardPosition > (this.inGameCards.length / 2) - 1) { + return true; + } + } + + // The user should select an answer but is trying to select + // a question. + if (this.status == "selecting answer") { + if (cardPosition < (this.inGameCards.length / 2)) { + return true; + } + } + + // The user is trying to select a card that was already + // unfolded. + for (var i = 0; i < this.unfoldedCards.length; i++) { + if (cardPosition == this.unfoldedCards[i]) { + return true; + } + } + + return false; + }; + + // Return the card content and update the game state. + model.Model.prototype.selectCard = function (cardPosition) { + var cardContent = this.inGameCards[cardPosition]; + + if (this.status == "selecting question") { + this.selectedQuestion = this.inGameCards[cardPosition]; + this.currentUnfoldedCards = [cardPosition]; + this.status = "selecting answer"; + return {'cardContent': cardContent, 'end': false}; + } + else { + if (this.status == "selecting answer") { + this.selectedAnswer = this.inGameCards[cardPosition]; + this.currentUnfoldedCards.push(cardPosition); + this.status = "selecting none"; + return {'cardContent': cardContent, 'end': true}; + } + } + }; + + model.Model.prototype.gameDone = function () { + return this.unfoldedCards.length == this.inGameCards.length; + } + + // Return true if the unfolded cards match. + model.Model.prototype.checkMatches = function () { + for (var i = 0; i < this.cardsSet.length; i++) { + if (this.cardsSet[i].question == this.selectedQuestion) { + var match = (this.cardsSet[i].answer == this.selectedAnswer); + if (match) { + // Update the list of unfolded cards. + for (var j = 0; j < this.currentUnfoldedCards.length; j++) { + this.unfoldedCards.push(this.currentUnfoldedCards[j]); + } + } + return match; + } + } + }; + + return model; + +}); diff --git a/activities/Memorize.activity/js/view.js b/activities/Memorize.activity/js/view.js new file mode 100644 index 000000000..20afa465b --- /dev/null +++ b/activities/Memorize.activity/js/view.js @@ -0,0 +1,141 @@ +define(function (require) { + var mustache = require("mustache"); + + var view = {}; + + // Utility to make the text fit the button. + function scaleTextToFit(button) { + + // Remove 'px' from CSS strings. + px = function (styleString) { + return styleString.slice(0, -2); + } + + var buttonStyle = button.currentStyle || + window.getComputedStyle(button, ''); + + // Get the allowed space inside the button. + var targetWidth = px(buttonStyle.width) - + (2 * px(buttonStyle.borderBottomWidth)) - + (2 * px(buttonStyle.paddingLeft)); + + // Temporarily add a new div to the document. Copy the + // content of the button to the div. + var helperDiv = document.createElement('div'); + helperDiv.style.visibility = "hidden"; + helperDiv.style.display = "inline-block"; + helperDiv.style.fontSize = buttonStyle.fontSize; + helperDiv.innerHTML = button.innerHTML; + document.body.appendChild(helperDiv); + + // Try smaller font sizes in the div until its width is not + // bigger than the button. + + var divStyle = helperDiv.currentStyle || + window.getComputedStyle(helperDiv, ''); + + var width = px(divStyle.width); + if (width > targetWidth) { + while (width > targetWidth) { + // Remove 'px' from the strings. + var oldSize = px(helperDiv.style.fontSize); + var newSize = oldSize - 1 + "px"; + helperDiv.style.fontSize = newSize; + + divStyle = helperDiv.currentStyle || + window.getComputedStyle(helperDiv, ''); + + width = px(divStyle.width); + } + button.style.fontSize = helperDiv.style.fontSize; + } + + document.body.removeChild(helperDiv); + } + + view.View = function () { + this.template = + '' + + '{{#rows}}' + + '' + + '{{#.}}' + + '' + + '' + + '' + + '{{/.}}' + + '' + + '{{/rows}}' + + ''; + }; + + view.View.prototype.createView = function (size) { + // Calculate the number of cards per row, based in the number + // of cards. + var cardsLength = size * size; + + // The number of cards must be even. + if (cardsLength % 2 != 0) { + cardsLength -= 1; + } + var cardsPerRow = Math.ceil(Math.sqrt(cardsLength)); + + var tableData = {"rows": []}; + var currentRow = []; + for (var i = 0; i < cardsLength; i++) { + var suit; + if (i < cardsLength / 2) { + suit = 1; + } + else { + suit = 2; + } + currentRow.push({"id": i, "suit": suit}); + if (currentRow.length == cardsPerRow) { + tableData.rows.push(currentRow); + currentRow = []; + } + // The row is not complete but it is the last card. + else { + if (i == cardsLength -1) { + tableData.rows.push(currentRow); + currentRow = []; + } + } + } + + // Arrange the cards in a table. + var tableElem = document.getElementById("buttons-table"); + tableElem.innerHTML = mustache.render(this.template, tableData); + }; + + view.View.prototype.unfoldCard = function (card, cardContent) { + card.innerHTML = cardContent; + scaleTextToFit(card); + card.classList.remove('folded'); + }; + + view.View.prototype.highlightCards = function (unfoldedCards) { + for (var j = 0; j < unfoldedCards.length; j++) { + var elem = document.getElementById(unfoldedCards[j]); + elem.classList.add('match'); + } + }; + + view.View.prototype.highlightDone = function (unfoldedCards) { + for (var j = 0; j < unfoldedCards.length; j++) { + var elem = document.getElementById(unfoldedCards[j]); + elem.classList.add('done'); + } + }; + + view.View.prototype.foldCards = function (unfoldedCards) { + for (var j = 0; j < unfoldedCards.length; j++) { + var elem = document.getElementById(unfoldedCards[j]); + elem.innerHTML = ''; + elem.classList.add('folded'); + } + }; + + return view; + +}); diff --git a/activities/Memorize.activity/lib/domReady.js b/activities/Memorize.activity/lib/domReady.js new file mode 100644 index 000000000..2b5412209 --- /dev/null +++ b/activities/Memorize.activity/lib/domReady.js @@ -0,0 +1,129 @@ +/** + * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/domReady for details + */ +/*jslint */ +/*global require: false, define: false, requirejs: false, + window: false, clearInterval: false, document: false, + self: false, setInterval: false */ + + +define(function () { + 'use strict'; + + var isTop, testDiv, scrollIntervalId, + isBrowser = typeof window !== "undefined" && window.document, + isPageLoaded = !isBrowser, + doc = isBrowser ? document : null, + readyCalls = []; + + function runCallbacks(callbacks) { + var i; + for (i = 0; i < callbacks.length; i += 1) { + callbacks[i](doc); + } + } + + function callReady() { + var callbacks = readyCalls; + + if (isPageLoaded) { + //Call the DOM ready callbacks + if (callbacks.length) { + readyCalls = []; + runCallbacks(callbacks); + } + } + } + + /** + * Sets the page as loaded. + */ + function pageLoaded() { + if (!isPageLoaded) { + isPageLoaded = true; + if (scrollIntervalId) { + clearInterval(scrollIntervalId); + } + + callReady(); + } + } + + if (isBrowser) { + if (document.addEventListener) { + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. + document.addEventListener("DOMContentLoaded", pageLoaded, false); + window.addEventListener("load", pageLoaded, false); + } else if (window.attachEvent) { + window.attachEvent("onload", pageLoaded); + + testDiv = document.createElement('div'); + try { + isTop = window.frameElement === null; + } catch (e) {} + + //DOMContentLoaded approximation that uses a doScroll, as found by + //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, + //but modified by other contributors, including jdalton + if (testDiv.doScroll && isTop && window.external) { + scrollIntervalId = setInterval(function () { + try { + testDiv.doScroll(); + pageLoaded(); + } catch (e) {} + }, 30); + } + } + + //Check if document already complete, and if so, just trigger page load + //listeners. Latest webkit browsers also use "interactive", and + //will fire the onDOMContentLoaded before "interactive" but not after + //entering "interactive" or "complete". More details: + //http://dev.w3.org/html5/spec/the-end.html#the-end + //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded + //Hmm, this is more complicated on further use, see "firing too early" + //bug: https://github.com/requirejs/domReady/issues/1 + //so removing the || document.readyState === "interactive" test. + //There is still a window.onload binding that should get fired if + //DOMContentLoaded is missed. + if (document.readyState === "complete") { + pageLoaded(); + } + } + + /** START OF PUBLIC API **/ + + /** + * Registers a callback for DOM ready. If DOM is already ready, the + * callback is called immediately. + * @param {Function} callback + */ + function domReady(callback) { + if (isPageLoaded) { + callback(doc); + } else { + readyCalls.push(callback); + } + return domReady; + } + + domReady.version = '2.0.1'; + + /** + * Loader Plugin API method + */ + domReady.load = function (name, req, onLoad, config) { + if (config.isBuild) { + onLoad(null); + } else { + domReady(onLoad); + } + }; + + /** END OF PUBLIC API **/ + + return domReady; +}); diff --git a/activities/Memorize.activity/lib/mustache.js b/activities/Memorize.activity/lib/mustache.js new file mode 100644 index 000000000..932052b4a --- /dev/null +++ b/activities/Memorize.activity/lib/mustache.js @@ -0,0 +1,610 @@ +/*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + */ + +/*global define: false*/ + +(function (root, factory) { + if (typeof exports === "object" && exports) { + module.exports = factory; // CommonJS + } else if (typeof define === "function" && define.amd) { + define(factory); // AMD + } else { + root.Mustache = factory; // + + + +
        + + + + +
        +
        + +
        + + + diff --git a/activities/Paint.activity/js/activity.js b/activities/Paint.activity/js/activity.js new file mode 100644 index 000000000..ba003bf28 --- /dev/null +++ b/activities/Paint.activity/js/activity.js @@ -0,0 +1,200 @@ +define(function (require) { + var activity = require("sugar-web/activity/activity"); + var colorpalette = require("activity/colorpalette"); + require("easel"); + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + // Initialize the activity. + activity.setup(); + + var paintCanvas = document.getElementById("paint-canvas"); + + function serializeCanvas() { + return paintCanvas.toDataURL(); + } + + function deserializeCanvas(data) { + var image = new Image(); + image.onload = function() { + paintCanvas.width = image.width; + paintCanvas.height = image.height; + paintCanvas.getContext("2d").drawImage(image, 0, 0); + }; + + image.src = data; + } + + activity.write = function (callback) { + + var data = { + image: serializeCanvas() + }; + + console.log("writing..."); + + var jsonData = JSON.stringify(data); + this.getDatastoreObject().setDataAsText(jsonData); + + this.getDatastoreObject().save(function (error) { + if (error === null) { + console.log("write done."); + } + else { + console.log("write failed."); + } + callback(error); + }); + }; + + var datastoreObject = activity.getDatastoreObject(); + function onLoaded(error, metadata, jsonData) { + var data = JSON.parse(jsonData); + deserializeCanvas(data.image); + } + datastoreObject.loadAsText(onLoaded); + + var updateSizes = function () { + var toolbarElem = document.getElementById("main-toolbar"); + var height = window.innerHeight - toolbarElem.offsetHeight - 5; + paintCanvas.width = window.innerWidth - 5; + paintCanvas.height = height; + }; + updateSizes(); + + window.onresize = function (event) { + updateSizes(); + }; + + var stage = new createjs.Stage(paintCanvas); + + //check to see if we are running in a browser with touch support + stage.autoClear = false; + stage.enableDOMEvents(true); + var touchEnabled = createjs.Touch.enable(stage); + if (touchEnabled) { + console.log("Touch enabled"); + stage.addEventListener("stagemousedown", handlePressDown); + stage.addEventListener("stagemouseup", handlePressUp); + } else { + console.log("Touch not enabled"); + stage.addEventListener("stagemousedown", handleMouseDown); + stage.addEventListener("stagemouseup", handleMouseUp); + } + createjs.Ticker.setFPS(24); + + // set up our defaults: + var strokeColor = "#309090"; + var strokeSize = 40; + var shape; + + // For touch interaction + var pointers = []; + + // For mouse only interaction + var oldPoint; + var oldMidPoint; + + shape = new createjs.Shape(); + stage.addChild(shape); + + // add handler for stage mouse events: + function handleMouseDown(event) { + oldPoint = new createjs.Point(stage.mouseX, stage.mouseY); + oldMidPoint = oldPoint; + stage.addEventListener("stagemousemove" , handleMouseMove); + } + + function handleMouseUp(event) { + stage.removeEventListener("stagemousemove" , handleMouseMove); + } + + function handleMouseMove(event) { + var midPoint = new createjs.Point(oldPoint.x + stage.mouseX>>1, + oldPoint.y + stage.mouseY>>1); + + shape.graphics.clear().setStrokeStyle(strokeSize, 'round', 'round'). + beginStroke(strokeColor).moveTo(midPoint.x, midPoint.y). + curveTo(oldPoint.x, oldPoint.y, oldMidPoint.x, oldMidPoint.y); + + oldPoint.x = stage.mouseX; + oldPoint.y = stage.mouseY; + + oldMidPoint.x = midPoint.x; + oldMidPoint.y = midPoint.y; + + stage.update(); + } + + function handlePressDown(event) { + var pointerData = {}; + pointerData.oldPoint = new createjs.Point(event.stageX, + event.stageY); + pointerData.oldMidPoint = pointerData.oldPoint; + pointers[event.pointerID] = pointerData; + if (pointers.length == 1) { + stage.addEventListener("stagemousemove" , handlePressMove); + } + } + + function handlePressUp(event) { + pointers.splice(pointers[event.pointerID], 1); + if (pointers.length === 0) { + stage.removeEventListener("stagemousemove" , handlePressMove); + } + } + + function handlePressMove(event) { + pointerData = pointers[event.pointerID]; + var midPoint = new createjs.Point( + pointerData.oldPoint.x + event.stageX>>1, + pointerData.oldPoint.y + event.stageY>>1); + + shape.graphics.clear().setStrokeStyle(strokeSize, 'round', 'round'). + beginStroke(strokeColor).moveTo(midPoint.x, midPoint.y). + curveTo(pointerData.oldPoint.x, pointerData.oldPoint.y, + pointerData.oldMidPoint.x, pointerData.oldMidPoint.y); + + pointerData.oldPoint.x = event.stageX; + pointerData.oldPoint.y = event.stageY; + + pointerData.oldMidPoint.x = midPoint.x; + pointerData.oldMidPoint.y = midPoint.y; + + stage.update(); + } + + // Color palette. + + function onColorChange(event) { + strokeColor = event.detail.color; + colorsButton.style.backgroundColor = event.detail.color; + colorInvoker.style.backgroundColor = event.detail.color; + } + + var colorsButton = document.getElementById("colors-button"); + colorPalette = new colorpalette.ColorPalette(colorsButton, undefined); + colorPalette.addEventListener('colorChange', onColorChange); + + var colorInvoker = colorPalette.getPalette(). + querySelector('.palette-invoker'); + + // Select the first color of the palette. + colorPalette.setColor(0); + + // Clear button. + + var clearButton = document.getElementById("clear-button"); + clearButton.addEventListener('click', function (event) { + stage.clear(); + stage.removeChild(shape); + shape = new createjs.Shape(); + stage.addChild(shape); + stage.update(); + }); + + stage.update(); + }); + +}); diff --git a/activities/Paint.activity/js/colorpalette.js b/activities/Paint.activity/js/colorpalette.js new file mode 100644 index 000000000..97f211376 --- /dev/null +++ b/activities/Paint.activity/js/colorpalette.js @@ -0,0 +1,99 @@ +define(["sugar-web/graphics/palette", "mustache"], + function (palette, mustache) { + + var colorpalette = {}; + + colorpalette.ColorPalette = function (invoker, primaryText) { + palette.Palette.call(this, invoker, primaryText); + + this.colorChangeEvent = new CustomEvent( + "colorChange", + { + detail: { + color: "#ed2529" + }, + bubbles: true, + cancelable: true + } + ); + + this.template = + '' + + '{{#rows}}' + + '' + + '{{#.}}' + + '' + + '' + + '' + + '{{/.}}' + + '' + + '{{/rows}}' + + ''; + + var colorsElem = document.createElement('table'); + colorsElem.className = "colors"; + var colorsData = {rows: [[{color: "#ed2529"}, + {color: "#69bc47"}, + {color: "#3c54a3"}], + [{color: "#f57f25"}, + {color: "#0b6b3a"}, + {color: "#00a0c6"}], + [{color: "#f6eb1a"}, + {color: "#b93f94"}, + {color: "#5b4a9c"}], + [{color: "#000000"}, + {color: "#919496"}, + {color: "#ffffff"}]]}; + + colorsElem.innerHTML = mustache.render(this.template, colorsData); + this.setContent([colorsElem]); + + // Pop-down the palette when a item in the menu is clicked. + + this.buttons = colorsElem.querySelectorAll('button'); + + var that = this; + + function popDownOnButtonClick(event) { + that.colorChangeEvent.detail.color = + event.target.style.backgroundColor; + that.getPalette().dispatchEvent(that.colorChangeEvent); + that.popDown(); + } + + for (var i = 0; i < this.buttons.length; i++) { + this.buttons[i].addEventListener('click', popDownOnButtonClick); + } + + }; + + var setColor = function (index) { + // Click the nth button + var event = document.createEvent("MouseEvents"); + event.initEvent("click", true, true); + this.buttons[index].dispatchEvent(event); + } + + var addEventListener = function (type, listener, useCapture) { + return this.getPalette().addEventListener(type, listener, useCapture); + } + + colorpalette.ColorPalette.prototype = + Object.create(palette.Palette.prototype, { + setColor: { + value: setColor, + enumerable: true, + configurable: true, + writable: true + }, + addEventListener: { + value: addEventListener, + enumerable: true, + configurable: true, + writable: true + } + }); + + return colorpalette; + +}); diff --git a/activities/Paint.activity/js/loader.js b/activities/Paint.activity/js/loader.js new file mode 100644 index 000000000..9681ac022 --- /dev/null +++ b/activities/Paint.activity/js/loader.js @@ -0,0 +1,14 @@ +requirejs.config({ + baseUrl: "lib", + shim: { + easel: { + exports: 'createjs' + } + }, + paths: { + activity: "../js", + easel: '../lib/easel' + } +}); + +requirejs(["activity/activity"]); diff --git a/activities/Paint.activity/lib/domReady.js b/activities/Paint.activity/lib/domReady.js new file mode 100644 index 000000000..2b5412209 --- /dev/null +++ b/activities/Paint.activity/lib/domReady.js @@ -0,0 +1,129 @@ +/** + * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/domReady for details + */ +/*jslint */ +/*global require: false, define: false, requirejs: false, + window: false, clearInterval: false, document: false, + self: false, setInterval: false */ + + +define(function () { + 'use strict'; + + var isTop, testDiv, scrollIntervalId, + isBrowser = typeof window !== "undefined" && window.document, + isPageLoaded = !isBrowser, + doc = isBrowser ? document : null, + readyCalls = []; + + function runCallbacks(callbacks) { + var i; + for (i = 0; i < callbacks.length; i += 1) { + callbacks[i](doc); + } + } + + function callReady() { + var callbacks = readyCalls; + + if (isPageLoaded) { + //Call the DOM ready callbacks + if (callbacks.length) { + readyCalls = []; + runCallbacks(callbacks); + } + } + } + + /** + * Sets the page as loaded. + */ + function pageLoaded() { + if (!isPageLoaded) { + isPageLoaded = true; + if (scrollIntervalId) { + clearInterval(scrollIntervalId); + } + + callReady(); + } + } + + if (isBrowser) { + if (document.addEventListener) { + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. + document.addEventListener("DOMContentLoaded", pageLoaded, false); + window.addEventListener("load", pageLoaded, false); + } else if (window.attachEvent) { + window.attachEvent("onload", pageLoaded); + + testDiv = document.createElement('div'); + try { + isTop = window.frameElement === null; + } catch (e) {} + + //DOMContentLoaded approximation that uses a doScroll, as found by + //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, + //but modified by other contributors, including jdalton + if (testDiv.doScroll && isTop && window.external) { + scrollIntervalId = setInterval(function () { + try { + testDiv.doScroll(); + pageLoaded(); + } catch (e) {} + }, 30); + } + } + + //Check if document already complete, and if so, just trigger page load + //listeners. Latest webkit browsers also use "interactive", and + //will fire the onDOMContentLoaded before "interactive" but not after + //entering "interactive" or "complete". More details: + //http://dev.w3.org/html5/spec/the-end.html#the-end + //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded + //Hmm, this is more complicated on further use, see "firing too early" + //bug: https://github.com/requirejs/domReady/issues/1 + //so removing the || document.readyState === "interactive" test. + //There is still a window.onload binding that should get fired if + //DOMContentLoaded is missed. + if (document.readyState === "complete") { + pageLoaded(); + } + } + + /** START OF PUBLIC API **/ + + /** + * Registers a callback for DOM ready. If DOM is already ready, the + * callback is called immediately. + * @param {Function} callback + */ + function domReady(callback) { + if (isPageLoaded) { + callback(doc); + } else { + readyCalls.push(callback); + } + return domReady; + } + + domReady.version = '2.0.1'; + + /** + * Loader Plugin API method + */ + domReady.load = function (name, req, onLoad, config) { + if (config.isBuild) { + onLoad(null); + } else { + domReady(onLoad); + } + }; + + /** END OF PUBLIC API **/ + + return domReady; +}); diff --git a/activities/Paint.activity/lib/easel.js b/activities/Paint.activity/lib/easel.js new file mode 100644 index 000000000..9c20189e9 --- /dev/null +++ b/activities/Paint.activity/lib/easel.js @@ -0,0 +1,135 @@ +/* +* EaselJS +* Visit http://createjs.com/ for documentation, updates and examples. +* +* Copyright (c) 2011 gskinner.com, inc. +* +* Distributed under the terms of the MIT license. +* http://www.opensource.org/licenses/mit-license.html +* +* This notice shall be included in all copies or substantial portions of the Software. +*/ +this.createjs=this.createjs||{};(function(){var c=function(){throw"UID cannot be instantiated";};c._nextID=0;c.get=function(){return c._nextID++};createjs.UID=c})();this.createjs=this.createjs||{}; +(function(){var c=function(){this.initialize()},b=c.prototype;c.initialize=function(a){a.addEventListener=b.addEventListener;a.removeEventListener=b.removeEventListener;a.removeAllEventListeners=b.removeAllEventListeners;a.hasEventListener=b.hasEventListener;a.dispatchEvent=b.dispatchEvent};b._listeners=null;b.initialize=function(){};b.addEventListener=function(a,m){var b=this._listeners;b?this.removeEventListener(a,m):b=this._listeners={};var d=b[a];d||(d=b[a]=[]);d.push(m);return m};b.removeEventListener= +function(a,m){var b=this._listeners;if(b){var d=b[a];if(d)for(var e=0,c=d.length;ec._times.length)return-1;null==a&&(a=c.getFPS()|0);a=Math.min(c._times.length-1,a);return 1E3/((c._times[0]-c._times[a])/a)};c.setPaused=function(a){c._paused=a};c.getPaused=function(){return c._paused};c.getTime=function(a){return c._getTime()-c._startTime-(a?c._pausedTime:0)};c.getTicks=function(a){return c._ticks-(a? +c._pausedTicks:0)};c._handleAF=function(){c._rafActive=!1;c._setupTick();c._getTime()-c._lastTime>=0.97*(c._interval-1)&&c._tick()};c._handleTimeout=function(){c.timeoutID=null;c._setupTick();c._tick()};c._setupTick=function(){if(!(c._rafActive||null!=c.timeoutID)){if(c.useRAF){var a=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame;if(a){a(c._handleAF);c._rafActive=!0;return}}c.timeoutID= +setTimeout(c._handleTimeout,c._interval)}};c._tick=function(){var a=c._getTime();c._ticks++;var m=a-c._lastTime,b=c._paused;b&&(c._pausedTicks++,c._pausedTime+=m);c._lastTime=a;for(var d=c._pauseable,e=c._listeners.slice(),f=e?e.length:0,h=0;hthis.a&&0<=this.d&&(a.rotation+=0>=a.rotation?180:-180),a.skewX=a.skewY= +0):(a.skewX=b/c.DEG_TO_RAD,a.skewY=g/c.DEG_TO_RAD);return a};b.reinitialize=function(a,b,g,d,c,f,h,k,j){this.initialize(a,b,g,d,c,f);this.alpha=h||1;this.shadow=k;this.compositeOperation=j;return this};b.appendProperties=function(a,b,g){this.alpha*=a;this.shadow=b||this.shadow;this.compositeOperation=g||this.compositeOperation;return this};b.prependProperties=function(a,b,g){this.alpha*=a;this.shadow=this.shadow||b;this.compositeOperation=this.compositeOperation||g;return this};b.clone=function(){var a= +new c(this.a,this.b,this.c,this.d,this.tx,this.ty);a.shadow=this.shadow;a.alpha=this.alpha;a.compositeOperation=this.compositeOperation;return a};b.toString=function(){return"[Matrix2D (a="+this.a+" b="+this.b+" c="+this.c+" d="+this.d+" tx="+this.tx+" ty="+this.ty+")]"};c.identity=new c(1,0,0,1,0,0);createjs.Matrix2D=c})();this.createjs=this.createjs||{};(function(){var c=function(a,b){this.initialize(a,b)},b=c.prototype;b.x=0;b.y=0;b.initialize=function(a,b){this.x=null==a?0:a;this.y=null==b?0:b};b.clone=function(){return new c(this.x,this.y)};b.toString=function(){return"[Point (x="+this.x+" y="+this.y+")]"};createjs.Point=c})();this.createjs=this.createjs||{};(function(){var c=function(a,b,g,d){this.initialize(a,b,g,d)},b=c.prototype;b.x=0;b.y=0;b.width=0;b.height=0;b.initialize=function(a,b,g,d){this.x=null==a?0:a;this.y=null==b?0:b;this.width=null==g?0:g;this.height=null==d?0:d};b.clone=function(){return new c(this.x,this.y,this.width,this.height)};b.toString=function(){return"[Rectangle (x="+this.x+" y="+this.y+" width="+this.width+" height="+this.height+")]"};createjs.Rectangle=c})();this.createjs=this.createjs||{}; +(function(){var c=function(a,b,g,d,c,f,h){this.initialize(a,b,g,d,c,f,h)},b=c.prototype;b.target=null;b.overLabel=null;b.outLabel=null;b.downLabel=null;b.play=!1;b._isPressed=!1;b._isOver=!1;b.initialize=function(a,b,g,d,c,f,h){a.addEventListener&&(this.target=a,a.cursor="pointer",this.overLabel=null==g?"over":g,this.outLabel=null==b?"out":b,this.downLabel=null==d?"down":d,this.play=c,this.setEnabled(!0),this.handleEvent({}),f&&(h&&(f.actionsEnabled=!1,f.gotoAndStop&&f.gotoAndStop(h)),a.hitArea=f))}; +b.setEnabled=function(a){var b=this.target;a?(b.addEventListener("mouseover",this),b.addEventListener("mouseout",this),b.addEventListener("mousedown",this)):(b.removeEventListener("mouseover",this),b.removeEventListener("mouseout",this),b.removeEventListener("mousedown",this))};b.toString=function(){return"[ButtonHelper]"};b.handleEvent=function(a){var b=this.target,g=a.type;"mousedown"==g?(a.addEventListener("mouseup",this),this._isPressed=!0,a=this.downLabel):"mouseup"==g?(this._isPressed=!1,a= +this._isOver?this.overLabel:this.outLabel):"mouseover"==g?(this._isOver=!0,a=this._isPressed?this.downLabel:this.overLabel):(this._isOver=!1,a=this._isPressed?this.overLabel:this.outLabel);this.play?b.gotoAndPlay&&b.gotoAndPlay(a):b.gotoAndStop&&b.gotoAndStop(a)};createjs.ButtonHelper=c})();this.createjs=this.createjs||{};(function(){var c=function(a,b,g,d){this.initialize(a,b,g,d)},b=c.prototype;c.identity=null;b.color=null;b.offsetX=0;b.offsetY=0;b.blur=0;b.initialize=function(a,b,g,d){this.color=a;this.offsetX=b;this.offsetY=g;this.blur=d};b.toString=function(){return"[Shadow]"};b.clone=function(){return new c(this.color,this.offsetX,this.offsetY,this.blur)};c.identity=new c("transparent",0,0,0);createjs.Shadow=c})();this.createjs=this.createjs||{}; +(function(){var c=function(a){this.initialize(a)},b=c.prototype;b.complete=!0;b.onComplete=null;b.addEventListener=null;b.removeEventListener=null;b.removeAllEventListeners=null;b.dispatchEvent=null;b.hasEventListener=null;b._listeners=null;createjs.EventDispatcher.initialize(b);b._animations=null;b._frames=null;b._images=null;b._data=null;b._loadCount=0;b._frameHeight=0;b._frameWidth=0;b._numFrames=0;b._regX=0;b._regY=0;b.initialize=function(a){var b,g,d;if(null!=a){if(a.images&&0<(g=a.images.length)){d= +this._images=[];for(b=0;bd.length||!1==a.next?null:null==a.next||!0==a.next?h:a.next;a.frequency||(a.frequency=1);this._animations.push(h);this._data[h]=a}}}};b.getNumFrames=function(a){if(null==a)return this._frames?this._frames.length:this._numFrames;a=this._data[a];return null==a?0:a.frames.length};b.getAnimations=function(){return this._animations.slice(0)};b.getAnimation=function(a){return this._data[a]};b.getFrame=function(a){var b;return this.complete&&this._frames&&(b=this._frames[a])?b:null}; +b.getFrameBounds=function(a){return(a=this.getFrame(a))?new createjs.Rectangle(-a.regX,-a.regY,a.rect.width,a.rect.height):null};b.toString=function(){return"[SpriteSheet]"};b.clone=function(){var a=new c;a.complete=this.complete;a._animations=this._animations;a._frames=this._frames;a._images=this._images;a._data=this._data;a._frameHeight=this._frameHeight;a._frameWidth=this._frameWidth;a._numFrames=this._numFrames;a._loadCount=this._loadCount;return a};b._handleImageLoad=function(){0==--this._loadCount&& +(this._calculateFrames(),this.complete=!0,this.onComplete&&this.onComplete(),this.dispatchEvent("complete"))};b._calculateFrames=function(){if(!(this._frames||0==this._frameWidth)){this._frames=[];for(var a=0,b=this._frameWidth,g=this._frameHeight,d=0,c=this._images;d>8&255,a=a>>16&255);return null==c?"rgb("+a+","+b+","+d+")":"rgba("+a+","+b+","+d+","+c+")"};b.getHSL=function(a,b,d,c){return null==c?"hsl("+a%360+","+b+"%,"+d+"%)":"hsla("+a%360+","+b+"%,"+d+"%,"+c+")"};b.BASE_64={A:0,B:1,C:2,D:3,E:4,F:5,G:6,H:7,I:8,J:9, +K:10,L:11,M:12,N:13,O:14,P:15,Q:16,R:17,S:18,T:19,U:20,V:21,W:22,X:23,Y:24,Z:25,a:26,b:27,c:28,d:29,e:30,f:31,g:32,h:33,i:34,j:35,k:36,l:37,m:38,n:39,o:40,p:41,q:42,r:43,s:44,t:45,u:46,v:47,w:48,x:49,y:50,z:51,"0":52,1:53,2:54,3:55,4:56,5:57,6:58,7:59,8:60,9:61,"+":62,"/":63};b.STROKE_CAPS_MAP=["butt","round","square"];b.STROKE_JOINTS_MAP=["miter","round","bevel"];b._ctx=(createjs.createCanvas?createjs.createCanvas():document.createElement("canvas")).getContext("2d");b.beginCmd=new c(b._ctx.beginPath, +[],!1);b.fillCmd=new c(b._ctx.fill,[],!1);b.strokeCmd=new c(b._ctx.stroke,[],!1);a._strokeInstructions=null;a._strokeStyleInstructions=null;a._ignoreScaleStroke=!1;a._fillInstructions=null;a._instructions=null;a._oldInstructions=null;a._activeInstructions=null;a._active=!1;a._dirty=!1;a.initialize=function(){this.clear();this._ctx=b._ctx};a.isEmpty=function(){return!(this._instructions.length||this._oldInstructions.length||this._activeInstructions.length)};a.draw=function(a){this._dirty&&this._updateInstructions(); +for(var b=this._instructions,d=0,c=b.length;df&&(f*=n=-1);f>l&&(f=l);0>h&&(h*=q=-1);h>l&&(h=l);0>k&&(k*=p=-1);k>l&&(k=l);0>j&&(j*=s=-1);j>l&&(j=l);this._dirty=this._active=!0;var l=this._ctx.arcTo,r=this._ctx.lineTo;this._activeInstructions.push(new c(this._ctx.moveTo,[a+d-h,b]),new c(l,[a+d+h*q,b-h*q,a+d,b+h,h]),new c(r,[a+d,b+e-k]),new c(l,[a+d+k*p,b+e+k*p,a+d-k,b+e,k]),new c(r,[a+j,b+e]),new c(l,[a-j*s,b+e+j*s,a,b+e-j,j]),new c(r,[a,b+f]),new c(l,[a-f*n,b-f*n,a+f,b,f]),new c(this._ctx.closePath));return this};a.drawCircle= +function(a,b,d){this.arc(a,b,d,0,2*Math.PI);return this};a.drawEllipse=function(a,b,d,e){this._dirty=this._active=!0;var f=0.5522848*(d/2),h=0.5522848*(e/2),k=a+d,j=b+e;d=a+d/2;e=b+e/2;this._activeInstructions.push(new c(this._ctx.moveTo,[a,e]),new c(this._ctx.bezierCurveTo,[a,e-h,d-f,b,d,b]),new c(this._ctx.bezierCurveTo,[d+f,b,k,e-h,k,e]),new c(this._ctx.bezierCurveTo,[k,e+h,d+f,j,d,j]),new c(this._ctx.bezierCurveTo,[d-f,j,a,e+h,a,e]));return this};a.drawPolyStar=function(a,b,d,e,f,h){this._dirty= +this._active=!0;null==f&&(f=0);f=1-f;h=null==h?0:h/(180/Math.PI);var k=Math.PI/e;this._activeInstructions.push(new c(this._ctx.moveTo,[a+Math.cos(h)*d,b+Math.sin(h)*d]));for(var j=0;j>3,s=g[p];if(!s||q&3)throw"bad path data (@"+c+"): "+n;n=d[p];p||(k=j=0);h.length=0;c++;q=(q>>2&1)+2;for(p=0;p>5?-1:1,r=(r&31)<<6|l[a.charAt(c+1)];3==q&&(r=r<<6|l[a.charAt(c+2)]);r=u*r/10;p%2?k=r+=k:j=r+=j;h[p]=r;c+=q}s.apply(this,h)}return this};a.clone=function(){var a=new b;a._instructions=this._instructions.slice();a._activeInstructions=this._activeInstructions.slice(); +a._oldInstructions=this._oldInstructions.slice();this._fillInstructions&&(a._fillInstructions=this._fillInstructions.slice());this._strokeInstructions&&(a._strokeInstructions=this._strokeInstructions.slice());this._strokeStyleInstructions&&(a._strokeStyleInstructions=this._strokeStyleInstructions.slice());a._active=this._active;a._dirty=this._dirty;return a};a.toString=function(){return"[Graphics]"};a.mt=a.moveTo;a.lt=a.lineTo;a.at=a.arcTo;a.bt=a.bezierCurveTo;a.qt=a.quadraticCurveTo;a.a=a.arc;a.r= +a.rect;a.cp=a.closePath;a.c=a.clear;a.f=a.beginFill;a.lf=a.beginLinearGradientFill;a.rf=a.beginRadialGradientFill;a.bf=a.beginBitmapFill;a.ef=a.endFill;a.ss=a.setStrokeStyle;a.s=a.beginStroke;a.ls=a.beginLinearGradientStroke;a.rs=a.beginRadialGradientStroke;a.bs=a.beginBitmapStroke;a.es=a.endStroke;a.dr=a.drawRect;a.rr=a.drawRoundRect;a.rc=a.drawRoundRectComplex;a.dc=a.drawCircle;a.de=a.drawEllipse;a.dp=a.drawPolyStar;a.p=a.decodePath;a._updateInstructions=function(){this._instructions=this._oldInstructions.slice(); +this._instructions.push(b.beginCmd);this._instructions.push.apply(this._instructions,this._activeInstructions);this._fillInstructions&&this._instructions.push.apply(this._instructions,this._fillInstructions);this._strokeInstructions&&(this._strokeStyleInstructions&&this._instructions.push.apply(this._instructions,this._strokeStyleInstructions),this._instructions.push.apply(this._instructions,this._strokeInstructions),this._ignoreScaleStroke?this._instructions.push(new c(this._ctx.save,[],!1),new c(this._ctx.setTransform, +[1,0,0,1,0,0],!1),b.strokeCmd,new c(this._ctx.restore,[],!1)):this._instructions.push(b.strokeCmd))};a._newPath=function(){this._dirty&&this._updateInstructions();this._oldInstructions=this._instructions;this._activeInstructions=[];this._active=this._dirty=!1};a._setProp=function(a,b){this[a]=b};createjs.Graphics=b})();this.createjs=this.createjs||{}; +(function(){var c=function(){this.initialize()},b=c.prototype;c.suppressCrossDomainErrors=!1;c._hitTestCanvas=createjs.createCanvas?createjs.createCanvas():document.createElement("canvas");c._hitTestCanvas.width=c._hitTestCanvas.height=1;c._hitTestContext=c._hitTestCanvas.getContext("2d");c._nextCacheID=1;b.alpha=1;b.cacheCanvas=null;b.id=-1;b.mouseEnabled=!0;b.name=null;b.parent=null;b.regX=0;b.regY=0;b.rotation=0;b.scaleX=1;b.scaleY=1;b.skewX=0;b.skewY=0;b.shadow=null;b.visible=!0;b.x=0;b.y=0;b.compositeOperation= +null;b.snapToPixel=!1;b.onPress=null;b.onClick=null;b.onDoubleClick=null;b.onMouseOver=null;b.onMouseOut=null;b.onTick=null;b.filters=null;b.cacheID=0;b.mask=null;b.hitArea=null;b.cursor=null;b.addEventListener=null;b.removeEventListener=null;b.removeAllEventListeners=null;b.dispatchEvent=null;b.hasEventListener=null;b._listeners=null;createjs.EventDispatcher.initialize(b);b._cacheOffsetX=0;b._cacheOffsetY=0;b._cacheScale=1;b._cacheDataURLID=0;b._cacheDataURL=null;b._matrix=null;b.initialize=function(){this.id= +createjs.UID.get();this._matrix=new createjs.Matrix2D};b.isVisible=function(){return!(!this.visible||!(0d||d>this.children.length)return arguments[c-2];if(2a||a>this.children.length- +1)return!1;if(b=this.children[a])b.parent=null;this.children.splice(a,1);return!0};b.removeAllChildren=function(){for(var a=this.children;a.length;)a.pop().parent=null};b.getChildAt=function(a){return this.children[a]};b.getChildByName=function(a){for(var b=this.children,c=0,d=b.length;cb||b>=d)){for(var e=0;e=a)return;var b=this;this._mouseOverIntervalID=setInterval(function(){b._testMouseOver()},1E3/Math.min(50,a))};b.enableDOMEvents=function(a){null==a&&(a=!0);var b,c=this._eventListeners;if(!a&&c){for(b in c)a=c[b],a.t.removeEventListener(b,a.f);this._eventListeners=null}else if(a&&!c&&this.canvas){a=window.addEventListener?window:document;var d=this,c=this._eventListeners={};c.mouseup={t:a,f:function(a){d._handleMouseUp(a)}}; +c.mousemove={t:a,f:function(a){d._handleMouseMove(a)}};c.dblclick={t:a,f:function(a){d._handleDoubleClick(a)}};c.mousedown={t:this.canvas,f:function(a){d._handleMouseDown(a)}};for(b in c)a=c[b],a.t.addEventListener(b,a.f)}};b.clone=function(){var a=new c(null);this.cloneProps(a);return a};b.toString=function(){return"[Stage (name="+this.name+")]"};b._getPointerData=function(a){var b=this._pointerData[a];if(!b&&(b=this._pointerData[a]={x:0,y:0},null==this._primaryPointerID||-1==this._primaryPointerID))this._primaryPointerID= +a;return b};b._handleMouseMove=function(a){a||(a=window.event);this._handlePointerMove(-1,a,a.pageX,a.pageY)};b._handlePointerMove=function(a,b,c,d){if(this.canvas){var e=this._getPointerData(a),f=e.inBounds;this._updatePointerPosition(a,c,d);if(f||e.inBounds||this.mouseMoveOutside){if(this.onMouseMove||this.hasEventListener("stagemousemove"))c=new createjs.MouseEvent("stagemousemove",e.x,e.y,this,b,a,a==this._primaryPointerID,e.rawX,e.rawY),this.onMouseMove&&this.onMouseMove(c),this.dispatchEvent(c); +if((d=e.event)&&(d.onMouseMove||d.hasEventListener("mousemove")))c=new createjs.MouseEvent("mousemove",e.x,e.y,d.target,b,a,a==this._primaryPointerID,e.rawX,e.rawY),d.onMouseMove&&d.onMouseMove(c),d.dispatchEvent(c,d.target)}}};b._updatePointerPosition=function(a,b,c){var d=this._getElementRect(this.canvas);b-=d.left;c-=d.top;var e=this.canvas.width,f=this.canvas.height;b/=(d.right-d.left)/e;c/=(d.bottom-d.top)/f;d=this._getPointerData(a);(d.inBounds=0<=b&&0<=c&&b<=e-1&&c<=f-1)?(d.x=b,d.y=c):this.mouseMoveOutside&& +(d.x=0>b?0:b>e-1?e-1:b,d.y=0>c?0:c>f-1?f-1:c);d.rawX=b;d.rawY=c;a==this._primaryPointerID&&(this.mouseX=d.x,this.mouseY=d.y,this.mouseInBounds=d.inBounds)};b._getElementRect=function(a){var b;try{b=a.getBoundingClientRect()}catch(c){b={top:a.offsetTop,left:a.offsetLeft,width:a.offsetWidth,height:a.offsetHeight}}var d=(window.pageXOffset||document.scrollLeft||0)-(document.clientLeft||document.body.clientLeft||0),e=(window.pageYOffset||document.scrollTop||0)-(document.clientTop||document.body.clientTop|| +0),f=window.getComputedStyle?getComputedStyle(a):a.currentStyle;a=parseInt(f.paddingLeft)+parseInt(f.borderLeftWidth);var h=parseInt(f.paddingTop)+parseInt(f.borderTopWidth),k=parseInt(f.paddingRight)+parseInt(f.borderRightWidth),f=parseInt(f.paddingBottom)+parseInt(f.borderBottomWidth);return{left:b.left+d+a,right:b.right+d-k,top:b.top+e+h,bottom:b.bottom+e-f}};b._handleMouseUp=function(a){this._handlePointerUp(-1,a,!1)};b._handlePointerUp=function(a,b,c){var d=this._getPointerData(a),e;if(this.onMouseMove|| +this.hasEventListener("stagemouseup"))e=new createjs.MouseEvent("stagemouseup",d.x,d.y,this,b,a,a==this._primaryPointerID,d.rawX,d.rawY),this.onMouseUp&&this.onMouseUp(e),this.dispatchEvent(e);var f=d.event;if(f&&(f.onMouseUp||f.hasEventListener("mouseup")))e=new createjs.MouseEvent("mouseup",d.x,d.y,f.target,b,a,a==this._primaryPointerID,d.rawX,d.rawY),f.onMouseUp&&f.onMouseUp(e),f.dispatchEvent(e,f.target);if((f=d.target)&&(f.onClick||f.hasEventListener("click"))&&this._getObjectsUnderPoint(d.x, +d.y,null,!0,this._mouseOverIntervalID?3:1)==f)e=new createjs.MouseEvent("click",d.x,d.y,f,b,a,a==this._primaryPointerID,d.rawX,d.rawY),f.onClick&&f.onClick(e),f.dispatchEvent(e);c?(a==this._primaryPointerID&&(this._primaryPointerID=null),delete this._pointerData[a]):d.event=d.target=null};b._handleMouseDown=function(a){this._handlePointerDown(-1,a,!1)};b._handlePointerDown=function(a,b,c,d){var e=this._getPointerData(a);null!=d&&this._updatePointerPosition(a,c,d);if(this.onMouseDown||this.hasEventListener("stagemousedown"))c= +new createjs.MouseEvent("stagemousedown",e.x,e.y,this,b,a,a==this._primaryPointerID,e.rawX,e.rawY),this.onMouseDown&&this.onMouseDown(c),this.dispatchEvent(c);if(d=this._getObjectsUnderPoint(e.x,e.y,null,this._mouseOverIntervalID?3:1))if(e.target=d,d.onPress||d.hasEventListener("mousedown"))if(c=new createjs.MouseEvent("mousedown",e.x,e.y,d,b,a,a==this._primaryPointerID,e.rawX,e.rawY),d.onPress&&d.onPress(c),d.dispatchEvent(c),c.onMouseMove||c.onMouseUp||c.hasEventListener("mousemove")||c.hasEventListener("mouseup"))e.event= +c};b._testMouseOver=function(){if(-1==this._primaryPointerID&&!(this.mouseX==this._mouseOverX&&this.mouseY==this._mouseOverY&&this.mouseInBounds)){var a=null;this.mouseInBounds&&(a=this._getObjectsUnderPoint(this.mouseX,this.mouseY,null,3),this._mouseOverX=this.mouseX,this._mouseOverY=this.mouseY);var b=this._mouseOverTarget;if(b!=a){var c=this._getPointerData(-1);if(b&&(b.onMouseOut||b.hasEventListener("mouseout"))){var d=new createjs.MouseEvent("mouseout",c.x,c.y,b,null,-1,c.rawX,c.rawY);b.onMouseOut&& +b.onMouseOut(d);b.dispatchEvent(d)}b&&(this.canvas.style.cursor="");if(a&&(a.onMouseOver||a.hasEventListener("mouseover")))d=new createjs.MouseEvent("mouseover",c.x,c.y,a,null,-1,c.rawX,c.rawY),a.onMouseOver&&a.onMouseOver(d),a.dispatchEvent(d);a&&(this.canvas.style.cursor=a.cursor||"");this._mouseOverTarget=a}}};b._handleDoubleClick=function(a){var b=this._getPointerData(-1),c=this._getObjectsUnderPoint(b.x,b.y,null,this._mouseOverIntervalID?3:1);if(c&&(c.onDoubleClick||c.hasEventListener("dblclick")))evt= +new createjs.MouseEvent("dblclick",b.x,b.y,c,a,-1,!0,b.rawX,b.rawY),c.onDoubleClick&&c.onDoubleClick(evt),c.dispatchEvent(evt)};createjs.Stage=c})();this.createjs=this.createjs||{}; +(function(){var c=function(a){this.initialize(a)},b=c.prototype=new createjs.DisplayObject;b.image=null;b.snapToPixel=!0;b.sourceRect=null;b.DisplayObject_initialize=b.initialize;b.initialize=function(a){this.DisplayObject_initialize();"string"==typeof a?(this.image=new Image,this.image.src=a):this.image=a};b.isVisible=function(){var a=this.cacheCanvas||this.image&&(this.image.complete||this.image.getContext||2<=this.image.readyState);return!(!this.visible||!(0=d){var e=a.next;this._dispatchAnimationEnd(a,b,c,e,d-1)||(e?this._goto(e):(this.paused=!0,this.currentAnimationFrame=a.frames.length-1,this.currentFrame= +a.frames[this.currentAnimationFrame]))}else this.currentFrame=a.frames[this.currentAnimationFrame];else d=this.spriteSheet.getNumFrames(),b>=d&&!this._dispatchAnimationEnd(a,b,c,d-1)&&(this.currentFrame=0)};b._dispatchAnimationEnd=function(a,b,c,d,e){var f=a?a.name:null;this.onAnimationEnd&&this.onAnimationEnd(this,f,d);this.dispatchEvent({type:"animationend",name:f,next:d});!c&&this.paused&&(this.currentAnimationFrame=e);return this.paused!=c||this._animation!=a||this.currentFrame!=b};b.DisplayObject_cloneProps= +b.cloneProps;b.cloneProps=function(a){this.DisplayObject_cloneProps(a);a.onAnimationEnd=this.onAnimationEnd;a.currentFrame=this.currentFrame;a.currentAnimation=this.currentAnimation;a.paused=this.paused;a.offset=this.offset;a._animation=this._animation;a.currentAnimationFrame=this.currentAnimationFrame};b._goto=function(a){if(isNaN(a)){var b=this.spriteSheet.getAnimation(a);b&&(this.currentAnimationFrame=0,this._animation=b,this.currentAnimation=a,this._normalizeFrame())}else this.currentAnimation= +this._animation=null,this.currentFrame=a};createjs.BitmapAnimation=c})();this.createjs=this.createjs||{}; +(function(){var c=function(a){this.initialize(a)},b=c.prototype=new createjs.DisplayObject;b.graphics=null;b.DisplayObject_initialize=b.initialize;b.initialize=function(a){this.DisplayObject_initialize();this.graphics=a?a:new createjs.Graphics};b.isVisible=function(){var a=this.cacheCanvas||this.graphics&&!this.graphics.isEmpty();return!(!this.visible||!(0this.lineWidth?(b&&this._drawTextLine(a,j,e*d),e++,j=k[l+1]):j+=k[l]+k[l+1];b&&this._drawTextLine(a,j,e*d)}e++}return e};b._drawTextLine=function(a,b,c){this.outline?a.strokeText(b,0,c,this.maxWidth||65535):a.fillText(b,0,c,this.maxWidth||65535)};createjs.Text=c})();this.createjs=this.createjs||{}; +(function(){var c=function(){throw"SpriteSheetUtils cannot be instantiated";};c._workingCanvas=createjs.createCanvas?createjs.createCanvas():document.createElement("canvas");c._workingContext=c._workingCanvas.getContext("2d");c.addFlippedFrames=function(b,a,m,g){if(a||m||g){var d=0;a&&c._flip(b,++d,!0,!1);m&&c._flip(b,++d,!1,!0);g&&c._flip(b,++d,!0,!0)}};c.extractFrame=function(b,a){isNaN(a)&&(a=b.getAnimation(a).frames[0]);var m=b.getFrame(a);if(!m)return null;var g=m.rect,d=c._workingCanvas;d.width= +g.width;d.height=g.height;c._workingContext.drawImage(m.image,g.x,g.y,g.width,g.height,0,0,g.width,g.height);m=new Image;m.src=d.toDataURL("image/png");return m};c.mergeAlpha=function(b,a,c){c||(c=createjs.createCanvas?createjs.createCanvas():document.createElement("canvas"));c.width=Math.max(a.width,b.width);c.height=Math.max(a.height,b.height);var g=c.getContext("2d");g.save();g.drawImage(b,0,0);g.globalCompositeOperation="destination-in";g.drawImage(a,0,0);g.restore();return c};c._flip=function(b, +a,m,g){for(var d=b._images,e=c._workingCanvas,f=c._workingContext,h=d.length/a,k=0;kthis.maxHeight)throw c.ERR_DIMENSIONS;for(var d=0,e=0,f=0;g.length;){var h=this._fillRow(g,d,f,b,a);h.w>e&&(e=h.w);d+=h.h;if(!h.h||!g.length){var k=createjs.createCanvas?createjs.createCanvas():document.createElement("canvas");k.width=this._getSize(e,this.maxWidth);k.height=this._getSize(d,this.maxHeight);this._data.images[f]=k;h.h||(e=d=0,f++)}}};b._getSize=function(a,b){for(var c=4;Math.pow(2,++c)f)throw c.ERR_DIMENSIONS;t>h||k+p>f||(n.img=g,n.rect=new createjs.Rectangle(k,b,p,t),j=j||t,a.splice(l,1),d[n.index]=[k,b,p,t,g,Math.round(-r+q*s.regX-e),Math.round(-u+q*s.regY-e)],k+=p)}return{w:k,h:j}};b._endBuild=function(){this.spriteSheet=new createjs.SpriteSheet(this._data); +this._data=null;this.progress=1;this.onComplete&&this.onComplete(this);this.dispatchEvent("complete")};b._run=function(){for(var a=50*Math.max(0.01,Math.min(0.99,this.timeSlice||0.3)),b=(new Date).getTime()+a,c=!1;b>(new Date).getTime();)if(!this._drawNext()){c=!0;break}if(c)this._endBuild();else{var d=this;this._timerID=setTimeout(function(){d._run()},50-a)}a=this.progress=this._index/this._frames.length;this.onProgress&&this.onProgress(this,a);this.dispatchEvent({type:"progress",progress:a})};b._drawNext= +function(){var a=this._frames[this._index],b=a.scale*this._scale,c=a.rect,d=a.sourceRect,e=this._data.images[a.img].getContext("2d");a.funct&&a.funct.apply(a.scope,a.params);e.save();e.beginPath();e.rect(c.x,c.y,c.width,c.height);e.clip();e.translate(Math.ceil(c.x-d.x*b),Math.ceil(c.y-d.y*b));e.scale(b,b);a.source.draw(e);e.restore();return++this._index + } +}(this, (function () { + + var exports = {}; + + exports.name = "mustache.js"; + exports.version = "0.7.2"; + exports.tags = ["{{", "}}"]; + + exports.Scanner = Scanner; + exports.Context = Context; + exports.Writer = Writer; + + var whiteRe = /\s*/; + var spaceRe = /\s+/; + var nonSpaceRe = /\S/; + var eqRe = /\s*=/; + var curlyRe = /\s*\}/; + var tagRe = /#|\^|\/|>|\{|&|=|!/; + + // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 + // See https://github.com/janl/mustache.js/issues/189 + function testRe(re, string) { + return RegExp.prototype.test.call(re, string); + } + + function isWhitespace(string) { + return !testRe(nonSpaceRe, string); + } + + var isArray = Array.isArray || function (obj) { + return Object.prototype.toString.call(obj) === "[object Array]"; + }; + + function escapeRe(string) { + return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); + } + + var entityMap = { + "&": "&", + "<": "<", + ">": ">", + '"': '"', + "'": ''', + "/": '/' + }; + + function escapeHtml(string) { + return String(string).replace(/[&<>"'\/]/g, function (s) { + return entityMap[s]; + }); + } + + // Export the escaping function so that the user may override it. + // See https://github.com/janl/mustache.js/issues/244 + exports.escape = escapeHtml; + + function Scanner(string) { + this.string = string; + this.tail = string; + this.pos = 0; + } + + /** + * Returns `true` if the tail is empty (end of string). + */ + Scanner.prototype.eos = function () { + return this.tail === ""; + }; + + /** + * Tries to match the given regular expression at the current position. + * Returns the matched text if it can match, the empty string otherwise. + */ + Scanner.prototype.scan = function (re) { + var match = this.tail.match(re); + + if (match && match.index === 0) { + this.tail = this.tail.substring(match[0].length); + this.pos += match[0].length; + return match[0]; + } + + return ""; + }; + + /** + * Skips all text until the given regular expression can be matched. Returns + * the skipped string, which is the entire tail if no match can be made. + */ + Scanner.prototype.scanUntil = function (re) { + var match, pos = this.tail.search(re); + + switch (pos) { + case -1: + match = this.tail; + this.pos += this.tail.length; + this.tail = ""; + break; + case 0: + match = ""; + break; + default: + match = this.tail.substring(0, pos); + this.tail = this.tail.substring(pos); + this.pos += pos; + } + + return match; + }; + + function Context(view, parent) { + this.view = view; + this.parent = parent; + this.clearCache(); + } + + Context.make = function (view) { + return (view instanceof Context) ? view : new Context(view); + }; + + Context.prototype.clearCache = function () { + this._cache = {}; + }; + + Context.prototype.push = function (view) { + return new Context(view, this); + }; + + Context.prototype.lookup = function (name) { + var value = this._cache[name]; + + if (!value) { + if (name === ".") { + value = this.view; + } else { + var context = this; + + while (context) { + if (name.indexOf(".") > 0) { + var names = name.split("."), i = 0; + + value = context.view; + + while (value && i < names.length) { + value = value[names[i++]]; + } + } else { + value = context.view[name]; + } + + if (value != null) { + break; + } + + context = context.parent; + } + } + + this._cache[name] = value; + } + + if (typeof value === "function") { + value = value.call(this.view); + } + + return value; + }; + + function Writer() { + this.clearCache(); + } + + Writer.prototype.clearCache = function () { + this._cache = {}; + this._partialCache = {}; + }; + + Writer.prototype.compile = function (template, tags) { + var fn = this._cache[template]; + + if (!fn) { + var tokens = exports.parse(template, tags); + fn = this._cache[template] = this.compileTokens(tokens, template); + } + + return fn; + }; + + Writer.prototype.compilePartial = function (name, template, tags) { + var fn = this.compile(template, tags); + this._partialCache[name] = fn; + return fn; + }; + + Writer.prototype.compileTokens = function (tokens, template) { + var fn = compileTokens(tokens); + var self = this; + + return function (view, partials) { + if (partials) { + if (typeof partials === "function") { + self._loadPartial = partials; + } else { + for (var name in partials) { + self.compilePartial(name, partials[name]); + } + } + } + + return fn(self, Context.make(view), template); + }; + }; + + Writer.prototype.render = function (template, view, partials) { + return this.compile(template)(view, partials); + }; + + Writer.prototype._section = function (name, context, text, callback) { + var value = context.lookup(name); + + switch (typeof value) { + case "object": + if (isArray(value)) { + var buffer = ""; + + for (var i = 0, len = value.length; i < len; ++i) { + buffer += callback(this, context.push(value[i])); + } + + return buffer; + } + + return value ? callback(this, context.push(value)) : ""; + case "function": + var self = this; + var scopedRender = function (template) { + return self.render(template, context); + }; + + var result = value.call(context.view, text, scopedRender); + return result != null ? result : ""; + default: + if (value) { + return callback(this, context); + } + } + + return ""; + }; + + Writer.prototype._inverted = function (name, context, callback) { + var value = context.lookup(name); + + // Use JavaScript's definition of falsy. Include empty arrays. + // See https://github.com/janl/mustache.js/issues/186 + if (!value || (isArray(value) && value.length === 0)) { + return callback(this, context); + } + + return ""; + }; + + Writer.prototype._partial = function (name, context) { + if (!(name in this._partialCache) && this._loadPartial) { + this.compilePartial(name, this._loadPartial(name)); + } + + var fn = this._partialCache[name]; + + return fn ? fn(context) : ""; + }; + + Writer.prototype._name = function (name, context) { + var value = context.lookup(name); + + if (typeof value === "function") { + value = value.call(context.view); + } + + return (value == null) ? "" : String(value); + }; + + Writer.prototype._escaped = function (name, context) { + return exports.escape(this._name(name, context)); + }; + + /** + * Low-level function that compiles the given `tokens` into a function + * that accepts three arguments: a Writer, a Context, and the template. + */ + function compileTokens(tokens) { + var subRenders = {}; + + function subRender(i, tokens, template) { + if (!subRenders[i]) { + var fn = compileTokens(tokens); + subRenders[i] = function (writer, context) { + return fn(writer, context, template); + }; + } + + return subRenders[i]; + } + + return function (writer, context, template) { + var buffer = ""; + var token, sectionText; + + for (var i = 0, len = tokens.length; i < len; ++i) { + token = tokens[i]; + + switch (token[0]) { + case "#": + sectionText = template.slice(token[3], token[5]); + buffer += writer._section(token[1], context, sectionText, subRender(i, token[4], template)); + break; + case "^": + buffer += writer._inverted(token[1], context, subRender(i, token[4], template)); + break; + case ">": + buffer += writer._partial(token[1], context); + break; + case "&": + buffer += writer._name(token[1], context); + break; + case "name": + buffer += writer._escaped(token[1], context); + break; + case "text": + buffer += token[1]; + break; + } + } + + return buffer; + }; + } + + /** + * Forms the given array of `tokens` into a nested tree structure where + * tokens that represent a section have two additional items: 1) an array of + * all tokens that appear in that section and 2) the index in the original + * template that represents the end of that section. + */ + function nestTokens(tokens) { + var tree = []; + var collector = tree; + var sections = []; + + var token; + for (var i = 0, len = tokens.length; i < len; ++i) { + token = tokens[i]; + switch (token[0]) { + case '#': + case '^': + sections.push(token); + collector.push(token); + collector = token[4] = []; + break; + case '/': + var section = sections.pop(); + section[5] = token[2]; + collector = sections.length > 0 ? sections[sections.length - 1][4] : tree; + break; + default: + collector.push(token); + } + } + + return tree; + } + + /** + * Combines the values of consecutive text tokens in the given `tokens` array + * to a single token. + */ + function squashTokens(tokens) { + var squashedTokens = []; + + var token, lastToken; + for (var i = 0, len = tokens.length; i < len; ++i) { + token = tokens[i]; + if (token[0] === 'text' && lastToken && lastToken[0] === 'text') { + lastToken[1] += token[1]; + lastToken[3] = token[3]; + } else { + lastToken = token; + squashedTokens.push(token); + } + } + + return squashedTokens; + } + + function escapeTags(tags) { + return [ + new RegExp(escapeRe(tags[0]) + "\\s*"), + new RegExp("\\s*" + escapeRe(tags[1])) + ]; + } + + /** + * Breaks up the given `template` string into a tree of token objects. If + * `tags` is given here it must be an array with two string values: the + * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of + * course, the default is to use mustaches (i.e. Mustache.tags). + */ + exports.parse = function (template, tags) { + template = template || ''; + tags = tags || exports.tags; + + if (typeof tags === 'string') tags = tags.split(spaceRe); + if (tags.length !== 2) { + throw new Error('Invalid tags: ' + tags.join(', ')); + } + + var tagRes = escapeTags(tags); + var scanner = new Scanner(template); + + var sections = []; // Stack to hold section tokens + var tokens = []; // Buffer to hold the tokens + var spaces = []; // Indices of whitespace tokens on the current line + var hasTag = false; // Is there a {{tag}} on the current line? + var nonSpace = false; // Is there a non-space char on the current line? + + // Strips all whitespace tokens array for the current line + // if there was a {{#tag}} on it and otherwise only space. + function stripSpace() { + if (hasTag && !nonSpace) { + while (spaces.length) { + tokens.splice(spaces.pop(), 1); + } + } else { + spaces = []; + } + + hasTag = false; + nonSpace = false; + } + + var start, type, value, chr; + while (!scanner.eos()) { + start = scanner.pos; + value = scanner.scanUntil(tagRes[0]); + + if (value) { + for (var i = 0, len = value.length; i < len; ++i) { + chr = value.charAt(i); + + if (isWhitespace(chr)) { + spaces.push(tokens.length); + } else { + nonSpace = true; + } + + tokens.push(["text", chr, start, start + 1]); + start += 1; + + if (chr === "\n") { + stripSpace(); // Check for whitespace on the current line. + } + } + } + + start = scanner.pos; + + // Match the opening tag. + if (!scanner.scan(tagRes[0])) { + break; + } + + hasTag = true; + type = scanner.scan(tagRe) || "name"; + + // Skip any whitespace between tag and value. + scanner.scan(whiteRe); + + // Extract the tag value. + if (type === "=") { + value = scanner.scanUntil(eqRe); + scanner.scan(eqRe); + scanner.scanUntil(tagRes[1]); + } else if (type === "{") { + var closeRe = new RegExp("\\s*" + escapeRe("}" + tags[1])); + value = scanner.scanUntil(closeRe); + scanner.scan(curlyRe); + scanner.scanUntil(tagRes[1]); + type = "&"; + } else { + value = scanner.scanUntil(tagRes[1]); + } + + // Match the closing tag. + if (!scanner.scan(tagRes[1])) { + throw new Error('Unclosed tag at ' + scanner.pos); + } + + // Check section nesting. + if (type === '/') { + if (sections.length === 0) { + throw new Error('Unopened section "' + value + '" at ' + start); + } + + var section = sections.pop(); + + if (section[1] !== value) { + throw new Error('Unclosed section "' + section[1] + '" at ' + start); + } + } + + var token = [type, value, start, scanner.pos]; + tokens.push(token); + + if (type === '#' || type === '^') { + sections.push(token); + } else if (type === "name" || type === "{" || type === "&") { + nonSpace = true; + } else if (type === "=") { + // Set the tags for the next time around. + tags = value.split(spaceRe); + + if (tags.length !== 2) { + throw new Error('Invalid tags at ' + start + ': ' + tags.join(', ')); + } + + tagRes = escapeTags(tags); + } + } + + // Make sure there are no open sections when we're done. + var section = sections.pop(); + if (section) { + throw new Error('Unclosed section "' + section[1] + '" at ' + scanner.pos); + } + + return nestTokens(squashTokens(tokens)); + }; + + // The high-level clearCache, compile, compilePartial, and render functions + // use this default writer. + var _writer = new Writer(); + + /** + * Clears all cached templates and partials in the default writer. + */ + exports.clearCache = function () { + return _writer.clearCache(); + }; + + /** + * Compiles the given `template` to a reusable function using the default + * writer. + */ + exports.compile = function (template, tags) { + return _writer.compile(template, tags); + }; + + /** + * Compiles the partial with the given `name` and `template` to a reusable + * function using the default writer. + */ + exports.compilePartial = function (name, template, tags) { + return _writer.compilePartial(name, template, tags); + }; + + /** + * Compiles the given array of tokens (the output of a parse) to a reusable + * function using the default writer. + */ + exports.compileTokens = function (tokens, template) { + return _writer.compileTokens(tokens, template); + }; + + /** + * Renders the `template` with the given `view` and `partials` using the + * default writer. + */ + exports.render = function (template, view, partials) { + return _writer.render(template, view, partials); + }; + + // This is here for backwards compatibility with 0.4.x. + exports.to_html = function (template, view, partials, send) { + var result = exports.render(template, view, partials); + + if (typeof send === "function") { + send(result); + } else { + return result; + } + }; + + return exports; + +}()))); diff --git a/activities/Paint.activity/lib/require.js b/activities/Paint.activity/lib/require.js new file mode 100644 index 000000000..5b2687543 --- /dev/null +++ b/activities/Paint.activity/lib/require.js @@ -0,0 +1,2000 @@ +/** vim: et:ts=4:sw=4:sts=4 + * @license RequireJS 2.1.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/jrburke/requirejs for details + */ +//Not using strict: uneven strict support in browsers, #392, and causes +//problems with requirejs.exec()/transpiler plugins that may not be strict. +/*jslint regexp: true, nomen: true, sloppy: true */ +/*global window, navigator, document, importScripts, setTimeout, opera */ + +var requirejs, require, define; +(function (global) { + var req, s, head, baseElement, dataMain, src, + interactiveScript, currentlyAddingScript, mainScript, subPath, + version = '2.1.4', + commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, + cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, + jsSuffixRegExp = /\.js$/, + currDirRegExp = /^\.\//, + op = Object.prototype, + ostring = op.toString, + hasOwn = op.hasOwnProperty, + ap = Array.prototype, + apsp = ap.splice, + isBrowser = !!(typeof window !== 'undefined' && navigator && document), + isWebWorker = !isBrowser && typeof importScripts !== 'undefined', + //PS3 indicates loaded and complete, but need to wait for complete + //specifically. Sequence is 'loading', 'loaded', execution, + // then 'complete'. The UA check is unfortunate, but not sure how + //to feature test w/o causing perf issues. + readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ? + /^complete$/ : /^(complete|loaded)$/, + defContextName = '_', + //Oh the tragedy, detecting opera. See the usage of isOpera for reason. + isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]', + contexts = {}, + cfg = {}, + globalDefQueue = [], + useInteractive = false; + + function isFunction(it) { + return ostring.call(it) === '[object Function]'; + } + + function isArray(it) { + return ostring.call(it) === '[object Array]'; + } + + /** + * Helper function for iterating over an array. If the func returns + * a true value, it will break out of the loop. + */ + function each(ary, func) { + if (ary) { + var i; + for (i = 0; i < ary.length; i += 1) { + if (ary[i] && func(ary[i], i, ary)) { + break; + } + } + } + } + + /** + * Helper function for iterating over an array backwards. If the func + * returns a true value, it will break out of the loop. + */ + function eachReverse(ary, func) { + if (ary) { + var i; + for (i = ary.length - 1; i > -1; i -= 1) { + if (ary[i] && func(ary[i], i, ary)) { + break; + } + } + } + } + + function hasProp(obj, prop) { + return hasOwn.call(obj, prop); + } + + function getOwn(obj, prop) { + return hasProp(obj, prop) && obj[prop]; + } + + /** + * Cycles over properties in an object and calls a function for each + * property value. If the function returns a truthy value, then the + * iteration is stopped. + */ + function eachProp(obj, func) { + var prop; + for (prop in obj) { + if (hasProp(obj, prop)) { + if (func(obj[prop], prop)) { + break; + } + } + } + } + + /** + * Simple function to mix in properties from source into target, + * but only if target does not already have a property of the same name. + */ + function mixin(target, source, force, deepStringMixin) { + if (source) { + eachProp(source, function (value, prop) { + if (force || !hasProp(target, prop)) { + if (deepStringMixin && typeof value !== 'string') { + if (!target[prop]) { + target[prop] = {}; + } + mixin(target[prop], value, force, deepStringMixin); + } else { + target[prop] = value; + } + } + }); + } + return target; + } + + //Similar to Function.prototype.bind, but the 'this' object is specified + //first, since it is easier to read/figure out what 'this' will be. + function bind(obj, fn) { + return function () { + return fn.apply(obj, arguments); + }; + } + + function scripts() { + return document.getElementsByTagName('script'); + } + + //Allow getting a global that expressed in + //dot notation, like 'a.b.c'. + function getGlobal(value) { + if (!value) { + return value; + } + var g = global; + each(value.split('.'), function (part) { + g = g[part]; + }); + return g; + } + + /** + * Constructs an error with a pointer to an URL with more information. + * @param {String} id the error ID that maps to an ID on a web page. + * @param {String} message human readable error. + * @param {Error} [err] the original error, if there is one. + * + * @returns {Error} + */ + function makeError(id, msg, err, requireModules) { + var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id); + e.requireType = id; + e.requireModules = requireModules; + if (err) { + e.originalError = err; + } + return e; + } + + if (typeof define !== 'undefined') { + //If a define is already in play via another AMD loader, + //do not overwrite. + return; + } + + if (typeof requirejs !== 'undefined') { + if (isFunction(requirejs)) { + //Do not overwrite and existing requirejs instance. + return; + } + cfg = requirejs; + requirejs = undefined; + } + + //Allow for a require config object + if (typeof require !== 'undefined' && !isFunction(require)) { + //assume it is a config object. + cfg = require; + require = undefined; + } + + function newContext(contextName) { + var inCheckLoaded, Module, context, handlers, + checkLoadedTimeoutId, + config = { + waitSeconds: 7, + baseUrl: './', + paths: {}, + pkgs: {}, + shim: {}, + map: {}, + config: {} + }, + registry = {}, + undefEvents = {}, + defQueue = [], + defined = {}, + urlFetched = {}, + requireCounter = 1, + unnormalizedCounter = 1; + + /** + * Trims the . and .. from an array of path segments. + * It will keep a leading path segment if a .. will become + * the first path segment, to help with module name lookups, + * which act like paths, but can be remapped. But the end result, + * all paths that use this function should look normalized. + * NOTE: this method MODIFIES the input array. + * @param {Array} ary the array of path segments. + */ + function trimDots(ary) { + var i, part; + for (i = 0; ary[i]; i += 1) { + part = ary[i]; + if (part === '.') { + ary.splice(i, 1); + i -= 1; + } else if (part === '..') { + if (i === 1 && (ary[2] === '..' || ary[0] === '..')) { + //End of the line. Keep at least one non-dot + //path segment at the front so it can be mapped + //correctly to disk. Otherwise, there is likely + //no path mapping for a path starting with '..'. + //This can still fail, but catches the most reasonable + //uses of .. + break; + } else if (i > 0) { + ary.splice(i - 1, 2); + i -= 2; + } + } + } + } + + /** + * Given a relative module name, like ./something, normalize it to + * a real name that can be mapped to a path. + * @param {String} name the relative name + * @param {String} baseName a real name that the name arg is relative + * to. + * @param {Boolean} applyMap apply the map config to the value. Should + * only be done if this normalization is for a dependency ID. + * @returns {String} normalized name + */ + function normalize(name, baseName, applyMap) { + var pkgName, pkgConfig, mapValue, nameParts, i, j, nameSegment, + foundMap, foundI, foundStarMap, starI, + baseParts = baseName && baseName.split('/'), + normalizedBaseParts = baseParts, + map = config.map, + starMap = map && map['*']; + + //Adjust any relative paths. + if (name && name.charAt(0) === '.') { + //If have a base name, try to normalize against it, + //otherwise, assume it is a top-level require that will + //be relative to baseUrl in the end. + if (baseName) { + if (getOwn(config.pkgs, baseName)) { + //If the baseName is a package name, then just treat it as one + //name to concat the name with. + normalizedBaseParts = baseParts = [baseName]; + } else { + //Convert baseName to array, and lop off the last part, + //so that . matches that 'directory' and not name of the baseName's + //module. For instance, baseName of 'one/two/three', maps to + //'one/two/three.js', but we want the directory, 'one/two' for + //this normalization. + normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); + } + + name = normalizedBaseParts.concat(name.split('/')); + trimDots(name); + + //Some use of packages may use a . path to reference the + //'main' module name, so normalize for that. + pkgConfig = getOwn(config.pkgs, (pkgName = name[0])); + name = name.join('/'); + if (pkgConfig && name === pkgName + '/' + pkgConfig.main) { + name = pkgName; + } + } else if (name.indexOf('./') === 0) { + // No baseName, so this is ID is resolved relative + // to baseUrl, pull off the leading dot. + name = name.substring(2); + } + } + + //Apply map config if available. + if (applyMap && (baseParts || starMap) && map) { + nameParts = name.split('/'); + + for (i = nameParts.length; i > 0; i -= 1) { + nameSegment = nameParts.slice(0, i).join('/'); + + if (baseParts) { + //Find the longest baseName segment match in the config. + //So, do joins on the biggest to smallest lengths of baseParts. + for (j = baseParts.length; j > 0; j -= 1) { + mapValue = getOwn(map, baseParts.slice(0, j).join('/')); + + //baseName segment has config, find if it has one for + //this name. + if (mapValue) { + mapValue = getOwn(mapValue, nameSegment); + if (mapValue) { + //Match, update name to the new value. + foundMap = mapValue; + foundI = i; + break; + } + } + } + } + + if (foundMap) { + break; + } + + //Check for a star map match, but just hold on to it, + //if there is a shorter segment match later in a matching + //config, then favor over this star map. + if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) { + foundStarMap = getOwn(starMap, nameSegment); + starI = i; + } + } + + if (!foundMap && foundStarMap) { + foundMap = foundStarMap; + foundI = starI; + } + + if (foundMap) { + nameParts.splice(0, foundI, foundMap); + name = nameParts.join('/'); + } + } + + return name; + } + + function removeScript(name) { + if (isBrowser) { + each(scripts(), function (scriptNode) { + if (scriptNode.getAttribute('data-requiremodule') === name && + scriptNode.getAttribute('data-requirecontext') === context.contextName) { + scriptNode.parentNode.removeChild(scriptNode); + return true; + } + }); + } + } + + function hasPathFallback(id) { + var pathConfig = getOwn(config.paths, id); + if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) { + removeScript(id); + //Pop off the first array value, since it failed, and + //retry + pathConfig.shift(); + context.require.undef(id); + context.require([id]); + return true; + } + } + + //Turns a plugin!resource to [plugin, resource] + //with the plugin being undefined if the name + //did not have a plugin prefix. + function splitPrefix(name) { + var prefix, + index = name ? name.indexOf('!') : -1; + if (index > -1) { + prefix = name.substring(0, index); + name = name.substring(index + 1, name.length); + } + return [prefix, name]; + } + + /** + * Creates a module mapping that includes plugin prefix, module + * name, and path. If parentModuleMap is provided it will + * also normalize the name via require.normalize() + * + * @param {String} name the module name + * @param {String} [parentModuleMap] parent module map + * for the module name, used to resolve relative names. + * @param {Boolean} isNormalized: is the ID already normalized. + * This is true if this call is done for a define() module ID. + * @param {Boolean} applyMap: apply the map config to the ID. + * Should only be true if this map is for a dependency. + * + * @returns {Object} + */ + function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) { + var url, pluginModule, suffix, nameParts, + prefix = null, + parentName = parentModuleMap ? parentModuleMap.name : null, + originalName = name, + isDefine = true, + normalizedName = ''; + + //If no name, then it means it is a require call, generate an + //internal name. + if (!name) { + isDefine = false; + name = '_@r' + (requireCounter += 1); + } + + nameParts = splitPrefix(name); + prefix = nameParts[0]; + name = nameParts[1]; + + if (prefix) { + prefix = normalize(prefix, parentName, applyMap); + pluginModule = getOwn(defined, prefix); + } + + //Account for relative paths if there is a base name. + if (name) { + if (prefix) { + if (pluginModule && pluginModule.normalize) { + //Plugin is loaded, use its normalize method. + normalizedName = pluginModule.normalize(name, function (name) { + return normalize(name, parentName, applyMap); + }); + } else { + normalizedName = normalize(name, parentName, applyMap); + } + } else { + //A regular module. + normalizedName = normalize(name, parentName, applyMap); + + //Normalized name may be a plugin ID due to map config + //application in normalize. The map config values must + //already be normalized, so do not need to redo that part. + nameParts = splitPrefix(normalizedName); + prefix = nameParts[0]; + normalizedName = nameParts[1]; + isNormalized = true; + + url = context.nameToUrl(normalizedName); + } + } + + //If the id is a plugin id that cannot be determined if it needs + //normalization, stamp it with a unique ID so two matching relative + //ids that may conflict can be separate. + suffix = prefix && !pluginModule && !isNormalized ? + '_unnormalized' + (unnormalizedCounter += 1) : + ''; + + return { + prefix: prefix, + name: normalizedName, + parentMap: parentModuleMap, + unnormalized: !!suffix, + url: url, + originalName: originalName, + isDefine: isDefine, + id: (prefix ? + prefix + '!' + normalizedName : + normalizedName) + suffix + }; + } + + function getModule(depMap) { + var id = depMap.id, + mod = getOwn(registry, id); + + if (!mod) { + mod = registry[id] = new context.Module(depMap); + } + + return mod; + } + + function on(depMap, name, fn) { + var id = depMap.id, + mod = getOwn(registry, id); + + if (hasProp(defined, id) && + (!mod || mod.defineEmitComplete)) { + if (name === 'defined') { + fn(defined[id]); + } + } else { + getModule(depMap).on(name, fn); + } + } + + function onError(err, errback) { + var ids = err.requireModules, + notified = false; + + if (errback) { + errback(err); + } else { + each(ids, function (id) { + var mod = getOwn(registry, id); + if (mod) { + //Set error on module, so it skips timeout checks. + mod.error = err; + if (mod.events.error) { + notified = true; + mod.emit('error', err); + } + } + }); + + if (!notified) { + req.onError(err); + } + } + } + + /** + * Internal method to transfer globalQueue items to this context's + * defQueue. + */ + function takeGlobalQueue() { + //Push all the globalDefQueue items into the context's defQueue + if (globalDefQueue.length) { + //Array splice in the values since the context code has a + //local var ref to defQueue, so cannot just reassign the one + //on context. + apsp.apply(defQueue, + [defQueue.length - 1, 0].concat(globalDefQueue)); + globalDefQueue = []; + } + } + + handlers = { + 'require': function (mod) { + if (mod.require) { + return mod.require; + } else { + return (mod.require = context.makeRequire(mod.map)); + } + }, + 'exports': function (mod) { + mod.usingExports = true; + if (mod.map.isDefine) { + if (mod.exports) { + return mod.exports; + } else { + return (mod.exports = defined[mod.map.id] = {}); + } + } + }, + 'module': function (mod) { + if (mod.module) { + return mod.module; + } else { + return (mod.module = { + id: mod.map.id, + uri: mod.map.url, + config: function () { + return (config.config && getOwn(config.config, mod.map.id)) || {}; + }, + exports: defined[mod.map.id] + }); + } + } + }; + + function cleanRegistry(id) { + //Clean up machinery used for waiting modules. + delete registry[id]; + } + + function breakCycle(mod, traced, processed) { + var id = mod.map.id; + + if (mod.error) { + mod.emit('error', mod.error); + } else { + traced[id] = true; + each(mod.depMaps, function (depMap, i) { + var depId = depMap.id, + dep = getOwn(registry, depId); + + //Only force things that have not completed + //being defined, so still in the registry, + //and only if it has not been matched up + //in the module already. + if (dep && !mod.depMatched[i] && !processed[depId]) { + if (getOwn(traced, depId)) { + mod.defineDep(i, defined[depId]); + mod.check(); //pass false? + } else { + breakCycle(dep, traced, processed); + } + } + }); + processed[id] = true; + } + } + + function checkLoaded() { + var map, modId, err, usingPathFallback, + waitInterval = config.waitSeconds * 1000, + //It is possible to disable the wait interval by using waitSeconds of 0. + expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(), + noLoads = [], + reqCalls = [], + stillLoading = false, + needCycleCheck = true; + + //Do not bother if this call was a result of a cycle break. + if (inCheckLoaded) { + return; + } + + inCheckLoaded = true; + + //Figure out the state of all the modules. + eachProp(registry, function (mod) { + map = mod.map; + modId = map.id; + + //Skip things that are not enabled or in error state. + if (!mod.enabled) { + return; + } + + if (!map.isDefine) { + reqCalls.push(mod); + } + + if (!mod.error) { + //If the module should be executed, and it has not + //been inited and time is up, remember it. + if (!mod.inited && expired) { + if (hasPathFallback(modId)) { + usingPathFallback = true; + stillLoading = true; + } else { + noLoads.push(modId); + removeScript(modId); + } + } else if (!mod.inited && mod.fetched && map.isDefine) { + stillLoading = true; + if (!map.prefix) { + //No reason to keep looking for unfinished + //loading. If the only stillLoading is a + //plugin resource though, keep going, + //because it may be that a plugin resource + //is waiting on a non-plugin cycle. + return (needCycleCheck = false); + } + } + } + }); + + if (expired && noLoads.length) { + //If wait time expired, throw error of unloaded modules. + err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads); + err.contextName = context.contextName; + return onError(err); + } + + //Not expired, check for a cycle. + if (needCycleCheck) { + each(reqCalls, function (mod) { + breakCycle(mod, {}, {}); + }); + } + + //If still waiting on loads, and the waiting load is something + //other than a plugin resource, or there are still outstanding + //scripts, then just try back later. + if ((!expired || usingPathFallback) && stillLoading) { + //Something is still waiting to load. Wait for it, but only + //if a timeout is not already in effect. + if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) { + checkLoadedTimeoutId = setTimeout(function () { + checkLoadedTimeoutId = 0; + checkLoaded(); + }, 50); + } + } + + inCheckLoaded = false; + } + + Module = function (map) { + this.events = getOwn(undefEvents, map.id) || {}; + this.map = map; + this.shim = getOwn(config.shim, map.id); + this.depExports = []; + this.depMaps = []; + this.depMatched = []; + this.pluginMaps = {}; + this.depCount = 0; + + /* this.exports this.factory + this.depMaps = [], + this.enabled, this.fetched + */ + }; + + Module.prototype = { + init: function (depMaps, factory, errback, options) { + options = options || {}; + + //Do not do more inits if already done. Can happen if there + //are multiple define calls for the same module. That is not + //a normal, common case, but it is also not unexpected. + if (this.inited) { + return; + } + + this.factory = factory; + + if (errback) { + //Register for errors on this module. + this.on('error', errback); + } else if (this.events.error) { + //If no errback already, but there are error listeners + //on this module, set up an errback to pass to the deps. + errback = bind(this, function (err) { + this.emit('error', err); + }); + } + + //Do a copy of the dependency array, so that + //source inputs are not modified. For example + //"shim" deps are passed in here directly, and + //doing a direct modification of the depMaps array + //would affect that config. + this.depMaps = depMaps && depMaps.slice(0); + + this.errback = errback; + + //Indicate this module has be initialized + this.inited = true; + + this.ignore = options.ignore; + + //Could have option to init this module in enabled mode, + //or could have been previously marked as enabled. However, + //the dependencies are not known until init is called. So + //if enabled previously, now trigger dependencies as enabled. + if (options.enabled || this.enabled) { + //Enable this module and dependencies. + //Will call this.check() + this.enable(); + } else { + this.check(); + } + }, + + defineDep: function (i, depExports) { + //Because of cycles, defined callback for a given + //export can be called more than once. + if (!this.depMatched[i]) { + this.depMatched[i] = true; + this.depCount -= 1; + this.depExports[i] = depExports; + } + }, + + fetch: function () { + if (this.fetched) { + return; + } + this.fetched = true; + + context.startTime = (new Date()).getTime(); + + var map = this.map; + + //If the manager is for a plugin managed resource, + //ask the plugin to load it now. + if (this.shim) { + context.makeRequire(this.map, { + enableBuildCallback: true + })(this.shim.deps || [], bind(this, function () { + return map.prefix ? this.callPlugin() : this.load(); + })); + } else { + //Regular dependency. + return map.prefix ? this.callPlugin() : this.load(); + } + }, + + load: function () { + var url = this.map.url; + + //Regular dependency. + if (!urlFetched[url]) { + urlFetched[url] = true; + context.load(this.map.id, url); + } + }, + + /** + * Checks is the module is ready to define itself, and if so, + * define it. + */ + check: function () { + if (!this.enabled || this.enabling) { + return; + } + + var err, cjsModule, + id = this.map.id, + depExports = this.depExports, + exports = this.exports, + factory = this.factory; + + if (!this.inited) { + this.fetch(); + } else if (this.error) { + this.emit('error', this.error); + } else if (!this.defining) { + //The factory could trigger another require call + //that would result in checking this module to + //define itself again. If already in the process + //of doing that, skip this work. + this.defining = true; + + if (this.depCount < 1 && !this.defined) { + if (isFunction(factory)) { + //If there is an error listener, favor passing + //to that instead of throwing an error. + if (this.events.error) { + try { + exports = context.execCb(id, factory, depExports, exports); + } catch (e) { + err = e; + } + } else { + exports = context.execCb(id, factory, depExports, exports); + } + + if (this.map.isDefine) { + //If setting exports via 'module' is in play, + //favor that over return value and exports. After that, + //favor a non-undefined return value over exports use. + cjsModule = this.module; + if (cjsModule && + cjsModule.exports !== undefined && + //Make sure it is not already the exports value + cjsModule.exports !== this.exports) { + exports = cjsModule.exports; + } else if (exports === undefined && this.usingExports) { + //exports already set the defined value. + exports = this.exports; + } + } + + if (err) { + err.requireMap = this.map; + err.requireModules = [this.map.id]; + err.requireType = 'define'; + return onError((this.error = err)); + } + + } else { + //Just a literal value + exports = factory; + } + + this.exports = exports; + + if (this.map.isDefine && !this.ignore) { + defined[id] = exports; + + if (req.onResourceLoad) { + req.onResourceLoad(context, this.map, this.depMaps); + } + } + + //Clean up + delete registry[id]; + + this.defined = true; + } + + //Finished the define stage. Allow calling check again + //to allow define notifications below in the case of a + //cycle. + this.defining = false; + + if (this.defined && !this.defineEmitted) { + this.defineEmitted = true; + this.emit('defined', this.exports); + this.defineEmitComplete = true; + } + + } + }, + + callPlugin: function () { + var map = this.map, + id = map.id, + //Map already normalized the prefix. + pluginMap = makeModuleMap(map.prefix); + + //Mark this as a dependency for this plugin, so it + //can be traced for cycles. + this.depMaps.push(pluginMap); + + on(pluginMap, 'defined', bind(this, function (plugin) { + var load, normalizedMap, normalizedMod, + name = this.map.name, + parentName = this.map.parentMap ? this.map.parentMap.name : null, + localRequire = context.makeRequire(map.parentMap, { + enableBuildCallback: true + }); + + //If current map is not normalized, wait for that + //normalized name to load instead of continuing. + if (this.map.unnormalized) { + //Normalize the ID if the plugin allows it. + if (plugin.normalize) { + name = plugin.normalize(name, function (name) { + return normalize(name, parentName, true); + }) || ''; + } + + //prefix and name should already be normalized, no need + //for applying map config again either. + normalizedMap = makeModuleMap(map.prefix + '!' + name, + this.map.parentMap); + on(normalizedMap, + 'defined', bind(this, function (value) { + this.init([], function () { return value; }, null, { + enabled: true, + ignore: true + }); + })); + + normalizedMod = getOwn(registry, normalizedMap.id); + if (normalizedMod) { + //Mark this as a dependency for this plugin, so it + //can be traced for cycles. + this.depMaps.push(normalizedMap); + + if (this.events.error) { + normalizedMod.on('error', bind(this, function (err) { + this.emit('error', err); + })); + } + normalizedMod.enable(); + } + + return; + } + + load = bind(this, function (value) { + this.init([], function () { return value; }, null, { + enabled: true + }); + }); + + load.error = bind(this, function (err) { + this.inited = true; + this.error = err; + err.requireModules = [id]; + + //Remove temp unnormalized modules for this module, + //since they will never be resolved otherwise now. + eachProp(registry, function (mod) { + if (mod.map.id.indexOf(id + '_unnormalized') === 0) { + cleanRegistry(mod.map.id); + } + }); + + onError(err); + }); + + //Allow plugins to load other code without having to know the + //context or how to 'complete' the load. + load.fromText = bind(this, function (text, textAlt) { + /*jslint evil: true */ + var moduleName = map.name, + moduleMap = makeModuleMap(moduleName), + hasInteractive = useInteractive; + + //As of 2.1.0, support just passing the text, to reinforce + //fromText only being called once per resource. Still + //support old style of passing moduleName but discard + //that moduleName in favor of the internal ref. + if (textAlt) { + text = textAlt; + } + + //Turn off interactive script matching for IE for any define + //calls in the text, then turn it back on at the end. + if (hasInteractive) { + useInteractive = false; + } + + //Prime the system by creating a module instance for + //it. + getModule(moduleMap); + + //Transfer any config to this other module. + if (hasProp(config.config, id)) { + config.config[moduleName] = config.config[id]; + } + + try { + req.exec(text); + } catch (e) { + return onError(makeError('fromtexteval', + 'fromText eval for ' + id + + ' failed: ' + e, + e, + [id])); + } + + if (hasInteractive) { + useInteractive = true; + } + + //Mark this as a dependency for the plugin + //resource + this.depMaps.push(moduleMap); + + //Support anonymous modules. + context.completeLoad(moduleName); + + //Bind the value of that module to the value for this + //resource ID. + localRequire([moduleName], load); + }); + + //Use parentName here since the plugin's name is not reliable, + //could be some weird string with no path that actually wants to + //reference the parentName's path. + plugin.load(map.name, localRequire, load, config); + })); + + context.enable(pluginMap, this); + this.pluginMaps[pluginMap.id] = pluginMap; + }, + + enable: function () { + this.enabled = true; + + //Set flag mentioning that the module is enabling, + //so that immediate calls to the defined callbacks + //for dependencies do not trigger inadvertent load + //with the depCount still being zero. + this.enabling = true; + + //Enable each dependency + each(this.depMaps, bind(this, function (depMap, i) { + var id, mod, handler; + + if (typeof depMap === 'string') { + //Dependency needs to be converted to a depMap + //and wired up to this module. + depMap = makeModuleMap(depMap, + (this.map.isDefine ? this.map : this.map.parentMap), + false, + !this.skipMap); + this.depMaps[i] = depMap; + + handler = getOwn(handlers, depMap.id); + + if (handler) { + this.depExports[i] = handler(this); + return; + } + + this.depCount += 1; + + on(depMap, 'defined', bind(this, function (depExports) { + this.defineDep(i, depExports); + this.check(); + })); + + if (this.errback) { + on(depMap, 'error', this.errback); + } + } + + id = depMap.id; + mod = registry[id]; + + //Skip special modules like 'require', 'exports', 'module' + //Also, don't call enable if it is already enabled, + //important in circular dependency cases. + if (!hasProp(handlers, id) && mod && !mod.enabled) { + context.enable(depMap, this); + } + })); + + //Enable each plugin that is used in + //a dependency + eachProp(this.pluginMaps, bind(this, function (pluginMap) { + var mod = getOwn(registry, pluginMap.id); + if (mod && !mod.enabled) { + context.enable(pluginMap, this); + } + })); + + this.enabling = false; + + this.check(); + }, + + on: function (name, cb) { + var cbs = this.events[name]; + if (!cbs) { + cbs = this.events[name] = []; + } + cbs.push(cb); + }, + + emit: function (name, evt) { + each(this.events[name], function (cb) { + cb(evt); + }); + if (name === 'error') { + //Now that the error handler was triggered, remove + //the listeners, since this broken Module instance + //can stay around for a while in the registry. + delete this.events[name]; + } + } + }; + + function callGetModule(args) { + //Skip modules already defined. + if (!hasProp(defined, args[0])) { + getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]); + } + } + + function removeListener(node, func, name, ieName) { + //Favor detachEvent because of IE9 + //issue, see attachEvent/addEventListener comment elsewhere + //in this file. + if (node.detachEvent && !isOpera) { + //Probably IE. If not it will throw an error, which will be + //useful to know. + if (ieName) { + node.detachEvent(ieName, func); + } + } else { + node.removeEventListener(name, func, false); + } + } + + /** + * Given an event from a script node, get the requirejs info from it, + * and then removes the event listeners on the node. + * @param {Event} evt + * @returns {Object} + */ + function getScriptData(evt) { + //Using currentTarget instead of target for Firefox 2.0's sake. Not + //all old browsers will be supported, but this one was easy enough + //to support and still makes sense. + var node = evt.currentTarget || evt.srcElement; + + //Remove the listeners once here. + removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange'); + removeListener(node, context.onScriptError, 'error'); + + return { + node: node, + id: node && node.getAttribute('data-requiremodule') + }; + } + + function intakeDefines() { + var args; + + //Any defined modules in the global queue, intake them now. + takeGlobalQueue(); + + //Make sure any remaining defQueue items get properly processed. + while (defQueue.length) { + args = defQueue.shift(); + if (args[0] === null) { + return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1])); + } else { + //args are id, deps, factory. Should be normalized by the + //define() function. + callGetModule(args); + } + } + } + + context = { + config: config, + contextName: contextName, + registry: registry, + defined: defined, + urlFetched: urlFetched, + defQueue: defQueue, + Module: Module, + makeModuleMap: makeModuleMap, + nextTick: req.nextTick, + + /** + * Set a configuration for the context. + * @param {Object} cfg config object to integrate. + */ + configure: function (cfg) { + //Make sure the baseUrl ends in a slash. + if (cfg.baseUrl) { + if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') { + cfg.baseUrl += '/'; + } + } + + //Save off the paths and packages since they require special processing, + //they are additive. + var pkgs = config.pkgs, + shim = config.shim, + objs = { + paths: true, + config: true, + map: true + }; + + eachProp(cfg, function (value, prop) { + if (objs[prop]) { + if (prop === 'map') { + mixin(config[prop], value, true, true); + } else { + mixin(config[prop], value, true); + } + } else { + config[prop] = value; + } + }); + + //Merge shim + if (cfg.shim) { + eachProp(cfg.shim, function (value, id) { + //Normalize the structure + if (isArray(value)) { + value = { + deps: value + }; + } + if ((value.exports || value.init) && !value.exportsFn) { + value.exportsFn = context.makeShimExports(value); + } + shim[id] = value; + }); + config.shim = shim; + } + + //Adjust packages if necessary. + if (cfg.packages) { + each(cfg.packages, function (pkgObj) { + var location; + + pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj; + location = pkgObj.location; + + //Create a brand new object on pkgs, since currentPackages can + //be passed in again, and config.pkgs is the internal transformed + //state for all package configs. + pkgs[pkgObj.name] = { + name: pkgObj.name, + location: location || pkgObj.name, + //Remove leading dot in main, so main paths are normalized, + //and remove any trailing .js, since different package + //envs have different conventions: some use a module name, + //some use a file name. + main: (pkgObj.main || 'main') + .replace(currDirRegExp, '') + .replace(jsSuffixRegExp, '') + }; + }); + + //Done with modifications, assing packages back to context config + config.pkgs = pkgs; + } + + //If there are any "waiting to execute" modules in the registry, + //update the maps for them, since their info, like URLs to load, + //may have changed. + eachProp(registry, function (mod, id) { + //If module already has init called, since it is too + //late to modify them, and ignore unnormalized ones + //since they are transient. + if (!mod.inited && !mod.map.unnormalized) { + mod.map = makeModuleMap(id); + } + }); + + //If a deps array or a config callback is specified, then call + //require with those args. This is useful when require is defined as a + //config object before require.js is loaded. + if (cfg.deps || cfg.callback) { + context.require(cfg.deps || [], cfg.callback); + } + }, + + makeShimExports: function (value) { + function fn() { + var ret; + if (value.init) { + ret = value.init.apply(global, arguments); + } + return ret || (value.exports && getGlobal(value.exports)); + } + return fn; + }, + + makeRequire: function (relMap, options) { + options = options || {}; + + function localRequire(deps, callback, errback) { + var id, map, requireMod; + + if (options.enableBuildCallback && callback && isFunction(callback)) { + callback.__requireJsBuild = true; + } + + if (typeof deps === 'string') { + if (isFunction(callback)) { + //Invalid call + return onError(makeError('requireargs', 'Invalid require call'), errback); + } + + //If require|exports|module are requested, get the + //value for them from the special handlers. Caveat: + //this only works while module is being defined. + if (relMap && hasProp(handlers, deps)) { + return handlers[deps](registry[relMap.id]); + } + + //Synchronous access to one module. If require.get is + //available (as in the Node adapter), prefer that. + if (req.get) { + return req.get(context, deps, relMap); + } + + //Normalize module name, if it contains . or .. + map = makeModuleMap(deps, relMap, false, true); + id = map.id; + + if (!hasProp(defined, id)) { + return onError(makeError('notloaded', 'Module name "' + + id + + '" has not been loaded yet for context: ' + + contextName + + (relMap ? '' : '. Use require([])'))); + } + return defined[id]; + } + + //Grab defines waiting in the global queue. + intakeDefines(); + + //Mark all the dependencies as needing to be loaded. + context.nextTick(function () { + //Some defines could have been added since the + //require call, collect them. + intakeDefines(); + + requireMod = getModule(makeModuleMap(null, relMap)); + + //Store if map config should be applied to this require + //call for dependencies. + requireMod.skipMap = options.skipMap; + + requireMod.init(deps, callback, errback, { + enabled: true + }); + + checkLoaded(); + }); + + return localRequire; + } + + mixin(localRequire, { + isBrowser: isBrowser, + + /** + * Converts a module name + .extension into an URL path. + * *Requires* the use of a module name. It does not support using + * plain URLs like nameToUrl. + */ + toUrl: function (moduleNamePlusExt) { + var ext, url, + index = moduleNamePlusExt.lastIndexOf('.'), + segment = moduleNamePlusExt.split('/')[0], + isRelative = segment === '.' || segment === '..'; + + //Have a file extension alias, and it is not the + //dots from a relative path. + if (index !== -1 && (!isRelative || index > 1)) { + ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length); + moduleNamePlusExt = moduleNamePlusExt.substring(0, index); + } + + url = context.nameToUrl(normalize(moduleNamePlusExt, + relMap && relMap.id, true), ext || '.fake'); + return ext ? url : url.substring(0, url.length - 5); + }, + + defined: function (id) { + return hasProp(defined, makeModuleMap(id, relMap, false, true).id); + }, + + specified: function (id) { + id = makeModuleMap(id, relMap, false, true).id; + return hasProp(defined, id) || hasProp(registry, id); + } + }); + + //Only allow undef on top level require calls + if (!relMap) { + localRequire.undef = function (id) { + //Bind any waiting define() calls to this context, + //fix for #408 + takeGlobalQueue(); + + var map = makeModuleMap(id, relMap, true), + mod = getOwn(registry, id); + + delete defined[id]; + delete urlFetched[map.url]; + delete undefEvents[id]; + + if (mod) { + //Hold on to listeners in case the + //module will be attempted to be reloaded + //using a different config. + if (mod.events.defined) { + undefEvents[id] = mod.events; + } + + cleanRegistry(id); + } + }; + } + + return localRequire; + }, + + /** + * Called to enable a module if it is still in the registry + * awaiting enablement. A second arg, parent, the parent module, + * is passed in for context, when this method is overriden by + * the optimizer. Not shown here to keep code compact. + */ + enable: function (depMap) { + var mod = getOwn(registry, depMap.id); + if (mod) { + getModule(depMap).enable(); + } + }, + + /** + * Internal method used by environment adapters to complete a load event. + * A load event could be a script load or just a load pass from a synchronous + * load call. + * @param {String} moduleName the name of the module to potentially complete. + */ + completeLoad: function (moduleName) { + var found, args, mod, + shim = getOwn(config.shim, moduleName) || {}, + shExports = shim.exports; + + takeGlobalQueue(); + + while (defQueue.length) { + args = defQueue.shift(); + if (args[0] === null) { + args[0] = moduleName; + //If already found an anonymous module and bound it + //to this name, then this is some other anon module + //waiting for its completeLoad to fire. + if (found) { + break; + } + found = true; + } else if (args[0] === moduleName) { + //Found matching define call for this script! + found = true; + } + + callGetModule(args); + } + + //Do this after the cycle of callGetModule in case the result + //of those calls/init calls changes the registry. + mod = getOwn(registry, moduleName); + + if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) { + if (config.enforceDefine && (!shExports || !getGlobal(shExports))) { + if (hasPathFallback(moduleName)) { + return; + } else { + return onError(makeError('nodefine', + 'No define call for ' + moduleName, + null, + [moduleName])); + } + } else { + //A script that does not call define(), so just simulate + //the call for it. + callGetModule([moduleName, (shim.deps || []), shim.exportsFn]); + } + } + + checkLoaded(); + }, + + /** + * Converts a module name to a file path. Supports cases where + * moduleName may actually be just an URL. + * Note that it **does not** call normalize on the moduleName, + * it is assumed to have already been normalized. This is an + * internal API, not a public one. Use toUrl for the public API. + */ + nameToUrl: function (moduleName, ext) { + var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url, + parentPath; + + //If a colon is in the URL, it indicates a protocol is used and it is just + //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?) + //or ends with .js, then assume the user meant to use an url and not a module id. + //The slash is important for protocol-less URLs as well as full paths. + if (req.jsExtRegExp.test(moduleName)) { + //Just a plain path, not module name lookup, so just return it. + //Add extension if it is included. This is a bit wonky, only non-.js things pass + //an extension, this method probably needs to be reworked. + url = moduleName + (ext || ''); + } else { + //A module that needs to be converted to a path. + paths = config.paths; + pkgs = config.pkgs; + + syms = moduleName.split('/'); + //For each module name segment, see if there is a path + //registered for it. Start with most specific name + //and work up from it. + for (i = syms.length; i > 0; i -= 1) { + parentModule = syms.slice(0, i).join('/'); + pkg = getOwn(pkgs, parentModule); + parentPath = getOwn(paths, parentModule); + if (parentPath) { + //If an array, it means there are a few choices, + //Choose the one that is desired + if (isArray(parentPath)) { + parentPath = parentPath[0]; + } + syms.splice(0, i, parentPath); + break; + } else if (pkg) { + //If module name is just the package name, then looking + //for the main module. + if (moduleName === pkg.name) { + pkgPath = pkg.location + '/' + pkg.main; + } else { + pkgPath = pkg.location; + } + syms.splice(0, i, pkgPath); + break; + } + } + + //Join the path parts together, then figure out if baseUrl is needed. + url = syms.join('/'); + url += (ext || (/\?/.test(url) ? '' : '.js')); + url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url; + } + + return config.urlArgs ? url + + ((url.indexOf('?') === -1 ? '?' : '&') + + config.urlArgs) : url; + }, + + //Delegates to req.load. Broken out as a separate function to + //allow overriding in the optimizer. + load: function (id, url) { + req.load(context, id, url); + }, + + /** + * Executes a module callack function. Broken out as a separate function + * solely to allow the build system to sequence the files in the built + * layer in the right sequence. + * + * @private + */ + execCb: function (name, callback, args, exports) { + return callback.apply(exports, args); + }, + + /** + * callback for script loads, used to check status of loading. + * + * @param {Event} evt the event from the browser for the script + * that was loaded. + */ + onScriptLoad: function (evt) { + //Using currentTarget instead of target for Firefox 2.0's sake. Not + //all old browsers will be supported, but this one was easy enough + //to support and still makes sense. + if (evt.type === 'load' || + (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) { + //Reset interactive script so a script node is not held onto for + //to long. + interactiveScript = null; + + //Pull out the name of the module and the context. + var data = getScriptData(evt); + context.completeLoad(data.id); + } + }, + + /** + * Callback for script errors. + */ + onScriptError: function (evt) { + var data = getScriptData(evt); + if (!hasPathFallback(data.id)) { + return onError(makeError('scripterror', 'Script error', evt, [data.id])); + } + } + }; + + context.require = context.makeRequire(); + return context; + } + + /** + * Main entry point. + * + * If the only argument to require is a string, then the module that + * is represented by that string is fetched for the appropriate context. + * + * If the first argument is an array, then it will be treated as an array + * of dependency string names to fetch. An optional function callback can + * be specified to execute when all of those dependencies are available. + * + * Make a local req variable to help Caja compliance (it assumes things + * on a require that are not standardized), and to give a short + * name for minification/local scope use. + */ + req = requirejs = function (deps, callback, errback, optional) { + + //Find the right context, use default + var context, config, + contextName = defContextName; + + // Determine if have config object in the call. + if (!isArray(deps) && typeof deps !== 'string') { + // deps is a config object + config = deps; + if (isArray(callback)) { + // Adjust args if there are dependencies + deps = callback; + callback = errback; + errback = optional; + } else { + deps = []; + } + } + + if (config && config.context) { + contextName = config.context; + } + + context = getOwn(contexts, contextName); + if (!context) { + context = contexts[contextName] = req.s.newContext(contextName); + } + + if (config) { + context.configure(config); + } + + return context.require(deps, callback, errback); + }; + + /** + * Support require.config() to make it easier to cooperate with other + * AMD loaders on globally agreed names. + */ + req.config = function (config) { + return req(config); + }; + + /** + * Execute something after the current tick + * of the event loop. Override for other envs + * that have a better solution than setTimeout. + * @param {Function} fn function to execute later. + */ + req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) { + setTimeout(fn, 4); + } : function (fn) { fn(); }; + + /** + * Export require as a global, but only if it does not already exist. + */ + if (!require) { + require = req; + } + + req.version = version; + + //Used to filter out dependencies that are already paths. + req.jsExtRegExp = /^\/|:|\?|\.js$/; + req.isBrowser = isBrowser; + s = req.s = { + contexts: contexts, + newContext: newContext + }; + + //Create default context. + req({}); + + //Exports some context-sensitive methods on global require. + each([ + 'toUrl', + 'undef', + 'defined', + 'specified' + ], function (prop) { + //Reference from contexts instead of early binding to default context, + //so that during builds, the latest instance of the default context + //with its config gets used. + req[prop] = function () { + var ctx = contexts[defContextName]; + return ctx.require[prop].apply(ctx, arguments); + }; + }); + + if (isBrowser) { + head = s.head = document.getElementsByTagName('head')[0]; + //If BASE tag is in play, using appendChild is a problem for IE6. + //When that browser dies, this can be removed. Details in this jQuery bug: + //http://dev.jquery.com/ticket/2709 + baseElement = document.getElementsByTagName('base')[0]; + if (baseElement) { + head = s.head = baseElement.parentNode; + } + } + + /** + * Any errors that require explicitly generates will be passed to this + * function. Intercept/override it if you want custom error handling. + * @param {Error} err the error object. + */ + req.onError = function (err) { + throw err; + }; + + /** + * Does the request to load a module for the browser case. + * Make this a separate function to allow other environments + * to override it. + * + * @param {Object} context the require context to find state. + * @param {String} moduleName the name of the module. + * @param {Object} url the URL to the module. + */ + req.load = function (context, moduleName, url) { + var config = (context && context.config) || {}, + node; + if (isBrowser) { + //In the browser so use a script tag + node = config.xhtml ? + document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') : + document.createElement('script'); + node.type = config.scriptType || 'text/javascript'; + node.charset = 'utf-8'; + node.async = true; + + node.setAttribute('data-requirecontext', context.contextName); + node.setAttribute('data-requiremodule', moduleName); + + //Set up load listener. Test attachEvent first because IE9 has + //a subtle issue in its addEventListener and script onload firings + //that do not match the behavior of all other browsers with + //addEventListener support, which fire the onload event for a + //script right after the script execution. See: + //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution + //UNFORTUNATELY Opera implements attachEvent but does not follow the script + //script execution mode. + if (node.attachEvent && + //Check if node.attachEvent is artificially added by custom script or + //natively supported by browser + //read https://github.com/jrburke/requirejs/issues/187 + //if we can NOT find [native code] then it must NOT natively supported. + //in IE8, node.attachEvent does not have toString() + //Note the test for "[native code" with no closing brace, see: + //https://github.com/jrburke/requirejs/issues/273 + !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) && + !isOpera) { + //Probably IE. IE (at least 6-8) do not fire + //script onload right after executing the script, so + //we cannot tie the anonymous define call to a name. + //However, IE reports the script as being in 'interactive' + //readyState at the time of the define call. + useInteractive = true; + + node.attachEvent('onreadystatechange', context.onScriptLoad); + //It would be great to add an error handler here to catch + //404s in IE9+. However, onreadystatechange will fire before + //the error handler, so that does not help. If addEvenListener + //is used, then IE will fire error before load, but we cannot + //use that pathway given the connect.microsoft.com issue + //mentioned above about not doing the 'script execute, + //then fire the script load event listener before execute + //next script' that other browsers do. + //Best hope: IE10 fixes the issues, + //and then destroys all installs of IE 6-9. + //node.attachEvent('onerror', context.onScriptError); + } else { + node.addEventListener('load', context.onScriptLoad, false); + node.addEventListener('error', context.onScriptError, false); + } + node.src = url; + + //For some cache cases in IE 6-8, the script executes before the end + //of the appendChild execution, so to tie an anonymous define + //call to the module name (which is stored on the node), hold on + //to a reference to this node, but clear after the DOM insertion. + currentlyAddingScript = node; + if (baseElement) { + head.insertBefore(node, baseElement); + } else { + head.appendChild(node); + } + currentlyAddingScript = null; + + return node; + } else if (isWebWorker) { + //In a web worker, use importScripts. This is not a very + //efficient use of importScripts, importScripts will block until + //its script is downloaded and evaluated. However, if web workers + //are in play, the expectation that a build has been done so that + //only one script needs to be loaded anyway. This may need to be + //reevaluated if other use cases become common. + importScripts(url); + + //Account for anonymous modules + context.completeLoad(moduleName); + } + }; + + function getInteractiveScript() { + if (interactiveScript && interactiveScript.readyState === 'interactive') { + return interactiveScript; + } + + eachReverse(scripts(), function (script) { + if (script.readyState === 'interactive') { + return (interactiveScript = script); + } + }); + return interactiveScript; + } + + //Look for a data-main script attribute, which could also adjust the baseUrl. + if (isBrowser) { + //Figure out baseUrl. Get it from the script tag with require.js in it. + eachReverse(scripts(), function (script) { + //Set the 'head' where we can append children by + //using the script's parent. + if (!head) { + head = script.parentNode; + } + + //Look for a data-main attribute to set main script for the page + //to load. If it is there, the path to data main becomes the + //baseUrl, if it is not already set. + dataMain = script.getAttribute('data-main'); + if (dataMain) { + //Set final baseUrl if there is not already an explicit one. + if (!cfg.baseUrl) { + //Pull off the directory of data-main for use as the + //baseUrl. + src = dataMain.split('/'); + mainScript = src.pop(); + subPath = src.length ? src.join('/') + '/' : './'; + + cfg.baseUrl = subPath; + dataMain = mainScript; + } + + //Strip off any trailing .js since dataMain is now + //like a module name. + dataMain = dataMain.replace(jsSuffixRegExp, ''); + + //Put the data-main script in the files to load. + cfg.deps = cfg.deps ? cfg.deps.concat(dataMain) : [dataMain]; + + return true; + } + }); + } + + /** + * The function that handles definitions of modules. Differs from + * require() in that a string for the module should be the first argument, + * and the function to execute after dependencies are loaded should + * return a value to define the module corresponding to the first argument's + * name. + */ + define = function (name, deps, callback) { + var node, context; + + //Allow for anonymous modules + if (typeof name !== 'string') { + //Adjust args appropriately + callback = deps; + deps = name; + name = null; + } + + //This module may not have dependencies + if (!isArray(deps)) { + callback = deps; + deps = []; + } + + //If no name, and callback is a function, then figure out if it a + //CommonJS thing with dependencies. + if (!deps.length && isFunction(callback)) { + //Remove comments from the callback string, + //look for require calls, and pull them into the dependencies, + //but only if there are function args. + if (callback.length) { + callback + .toString() + .replace(commentRegExp, '') + .replace(cjsRequireRegExp, function (match, dep) { + deps.push(dep); + }); + + //May be a CommonJS thing even without require calls, but still + //could use exports, and module. Avoid doing exports and module + //work though if it just needs require. + //REQUIRES the function to expect the CommonJS variables in the + //order listed below. + deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps); + } + } + + //If in IE 6-8 and hit an anonymous define() call, do the interactive + //work. + if (useInteractive) { + node = currentlyAddingScript || getInteractiveScript(); + if (node) { + if (!name) { + name = node.getAttribute('data-requiremodule'); + } + context = contexts[node.getAttribute('data-requirecontext')]; + } + } + + //Always save off evaluating the def call until the script onload handler. + //This allows multiple modules to be in a file without prematurely + //tracing dependencies, and allows for anonymous module support, + //where the module name is not known until the script onload event + //occurs. If no context, use the global queue, and get it processed + //in the onscript load callback. + (context ? context.defQueue : globalDefQueue).push([name, deps, callback]); + }; + + define.amd = { + jQuery: true + }; + + + /** + * Executes the text. Normally just uses eval, but can be modified + * to use a better, environment-specific call. Only used for transpiling + * loader plugins, not for plain JS modules. + * @param {String} text the text to execute/evaluate. + */ + req.exec = function (text) { + /*jslint evil: true */ + return eval(text); + }; + + //Set up with config info. + req(cfg); +}(this)); diff --git a/activities/Paint.activity/lib/sugar-web/LICENSE b/activities/Paint.activity/lib/sugar-web/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/activities/Paint.activity/lib/sugar-web/README.md b/activities/Paint.activity/lib/sugar-web/README.md new file mode 100644 index 000000000..de87a7250 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/README.md @@ -0,0 +1,7 @@ +Sugar Web +========= + +These are the tools that a developer can use to make a web +activity. + +For details see: http://developer.sugarlabs.org/ diff --git a/activities/Paint.activity/lib/sugar-web/activity/activity.js b/activities/Paint.activity/lib/sugar-web/activity/activity.js new file mode 100644 index 000000000..9a407d99a --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/activity/activity.js @@ -0,0 +1,114 @@ +define(["webL10n", + "sugar-web/activity/shortcut", + "sugar-web/bus", + "sugar-web/env", + "sugar-web/datastore", + "sugar-web/graphics/icon", + "sugar-web/graphics/activitypalette"], function ( + l10n, shortcut, bus, env, datastore, icon, activitypalette) { + + var datastoreObject = null; + + var activity = {}; + + activity.setup = function () { + bus.listen(); + + l10n.start(); + + function onPause() { + activity.write(function () {}); + } + + function onStop() { + function onDataStored(error, result) { + activity.close(function () {}); + } + activity.write(onDataStored); + } + + bus.onNotification("activity.pause", onPause); + bus.onNotification("activity.stop", onStop); + + datastoreObject = new datastore.DatastoreObject(); + + var activityButton = document.getElementById("activity-button"); + + var activityPalette = new activitypalette.ActivityPalette( + activityButton, datastoreObject); + + // Colorize the activity icon. + activity.getXOColor(function (error, colors) { + icon.colorize(activityButton, colors); + invokerElem = + document.querySelector("#activity-palette .palette-invoker"); + icon.colorize(invokerElem, colors); + }); + + // Make the activity stop with the stop button. + var stopButton = document.getElementById("stop-button"); + stopButton.addEventListener('click', onStop); + + shortcut.add("Ctrl", "Q", this.close); + + env.getEnvironment(function (error, environment) { + if (!environment.objectId) { + datastoreObject.setMetadata({ + "title": environment.activityName + " Activity", + "title_set_by_user": "0", + "activity": environment.bundleId, + "activity_id": environment.activityId + }); + } + datastoreObject.save(function () { + datastoreObject.getMetadata(function (error, metadata) { + activityPalette.setTitleDescription(metadata); + }); + }); + }); + }; + + activity.getDatastoreObject = function () { + return datastoreObject; + }; + + activity.getXOColor = function (callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null, { + stroke: result[0][0], + fill: result[0][1] + }); + } else { + callback(null, { + stroke: "#00A0FF", + fill: "#8BFF7A" + }); + } + } + + bus.sendMessage("activity.get_xo_color", [], onResponseReceived); + }; + + // Activities should override this function in order to store + // data. + activity.write = function (callback) { + setTimeout(function () { + callback(null); + }, 0); + }; + + activity.close = function (callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null); + } else { + console.log("activity.close called"); + } + } + + bus.sendMessage("activity.close", [], onResponseReceived); + }; + + return activity; +}); diff --git a/activities/Paint.activity/lib/sugar-web/activity/shortcut.js b/activities/Paint.activity/lib/sugar-web/activity/shortcut.js new file mode 100644 index 000000000..403af03e5 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/activity/shortcut.js @@ -0,0 +1,57 @@ +define(function () { + var shortcut = {}; + + shortcut._allShortcuts = []; + + shortcut.add = function (modifiersString, key, callback) { + // Parse the modifiers. For example "Ctrl+Alt" will become + // {'ctrlKey': true, 'altKey': true, 'shiftKey': false} + var modifiersList = modifiersString.toLowerCase().split("+"); + var modifiers = { + 'ctrlKey': modifiersList.indexOf('ctrl') >= 0, + 'altKey': modifiersList.indexOf('alt') >= 0, + 'shiftKey': modifiersList.indexOf('shift') >= 0 + }; + + this._allShortcuts.push({ + 'modifiers': modifiers, + 'key': key.toLowerCase(), + 'callback': callback + }); + }; + + document.onkeypress = function (e) { + e = e || window.event; + + var modifiers = { + 'ctrlKey': e.ctrlKey, + 'altKey': e.altKey, + 'shiftKey': e.shiftKey + }; + + // Obtain the key + var charCode; + if (typeof e.which == "number") { + charCode = e.which; + } else { + charCode = e.keyCode; + } + var key = String.fromCharCode(charCode).toLowerCase(); + + // Search for a matching shortcut + for (i = 0; i < shortcut._allShortcuts.length; i += 1) { + var currentShortcut = shortcut._allShortcuts[i]; + + var match = currentShortcut.key == key && + currentShortcut.modifiers.ctrlKey == modifiers.ctrlKey && + currentShortcut.modifiers.altKey == modifiers.altKey && + currentShortcut.modifiers.shiftKey == modifiers.shiftKey; + if (match) { + currentShortcut.callback(); + return; + } + } + }; + + return shortcut; +}); diff --git a/activities/Paint.activity/lib/sugar-web/bus.js b/activities/Paint.activity/lib/sugar-web/bus.js new file mode 100644 index 000000000..c1b5cbb74 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/bus.js @@ -0,0 +1,229 @@ +define(["sugar-web/env"], function (env) { + var lastId = 0; + var callbacks = {}; + var notificationCallbacks = {}; + var client = null; + var inputStreams = []; + + function WebSocketClient(environment) { + this.queue = []; + this.socket = null; + + var that = this; + + env.getEnvironment(function (error, environment) { + var port = environment.apiSocketPort; + var socket = new WebSocket("ws://localhost:" + port); + + socket.binaryType = "arraybuffer"; + + socket.onopen = function () { + var params = [environment.activityId, + environment.apiSocketKey]; + + socket.send(JSON.stringify({ + "method": "authenticate", + "id": "authenticate", + "params": params + })); + + while (that.queue.length > 0) { + socket.send(that.queue.shift()); + } + }; + + socket.onmessage = function (message) { + that.onMessage(message); + }; + + that.socket = socket; + }); + } + + WebSocketClient.prototype.send = function (data) { + + if (this.socket && this.socket.readyState == WebSocket.OPEN) { + this.socket.send(data); + } else { + this.queue.push(data); + } + }; + + WebSocketClient.prototype.close = function () { + this.socket.close(); + }; + + var bus = {}; + + function InputStream() { + this.streamId = null; + this.readCallback = null; + } + + InputStream.prototype.open = function (callback) { + var that = this; + bus.sendMessage("open_stream", [], function (error, result) { + that.streamId = result[0]; + inputStreams[that.streamId] = that; + callback(error); + }); + }; + + InputStream.prototype.read = function (count, callback) { + if (this.readCallback) { + throw Error("Read already in progress"); + } + + this.readCallback = callback; + + var buffer = new ArrayBuffer(8); + + var headerView = new Uint8Array(buffer, 0, 1); + headerView[0] = this.streamId; + + var bodyView = new Uint32Array(buffer, 4, 1); + bodyView[0] = count; + + bus.sendBinary(buffer); + }; + + InputStream.prototype.gotData = function (buffer) { + var callback = this.readCallback; + + this.readCallback = null; + + callback(null, buffer); + }; + + InputStream.prototype.close = function (callback) { + var that = this; + + function onStreamClosed(error, result) { + if (callback) { + callback(error); + } + delete inputStreams[that.streamId]; + } + + bus.sendMessage("close_stream", [this.streamId], onStreamClosed); + }; + + function OutputStream() { + this.streamId = null; + } + + OutputStream.prototype.open = function (callback) { + var that = this; + bus.sendMessage("open_stream", [], function (error, result) { + that.streamId = result[0]; + callback(error); + }); + }; + + OutputStream.prototype.write = function (data) { + var buffer = new ArrayBuffer(data.byteLength + 1); + + var bufferView = new Uint8Array(buffer); + bufferView[0] = this.streamId; + bufferView.set(new Uint8Array(data), 1); + + bus.sendBinary(buffer); + }; + + OutputStream.prototype.close = function (callback) { + bus.sendMessage("close_stream", [this.streamId], callback); + }; + + bus.createInputStream = function (callback) { + return new InputStream(); + }; + + bus.createOutputStream = function (callback) { + return new OutputStream(); + }; + + bus.sendMessage = function (method, params, callback) { + + var message = { + "method": method, + "id": lastId, + "params": params + }; + + /*if (callback) { + callbacks[lastId] = callback; + } + lastId++; + client.send(JSON.stringify(message));*/ + + console.log("ACTIVITY CALL "+JSON.stringify(message)); + if (method == "activity.close") { + window.location = "../.."; + } else if (method == "activity.get_xo_color") { + var color = JSON.parse(window.localStorage.getItem("settings")).colorvalue; + callback(null, [[color.fill, color.stroke]]); + } + + + }; + + bus.onNotification = function (method, callback) { + notificationCallbacks[method] = callback; + }; + + bus.sendBinary = function (buffer, callback) { + client.send(buffer); + }; + + bus.listen = function (customClient) { + if (customClient) { + client = customClient; + } else { + client = new WebSocketClient(); + } + + client.onMessage = function (message) { + if (typeof message.data != "string") { + var dataView = new Uint8Array(message.data); + var streamId = dataView[0]; + + if (streamId in inputStreams) { + var inputStream = inputStreams[streamId]; + inputStream.gotData(message.data.slice(1)); + } + + return; + } + + var parsed = JSON.parse(message.data); + var responseId = parsed.id; + + if (parsed.method) { + var notificationCallback = notificationCallbacks[parsed.method]; + if (notificationCallback !== undefined) { + notificationCallback(parsed.params); + } + return; + } + + if (responseId in callbacks) { + var callback = callbacks[responseId]; + + if (parsed.error === null) { + callback(null, parsed.result); + } else { + callback(new Error(parsed.error), null); + } + + delete callbacks[responseId]; + } + }; + }; + + bus.close = function () { + client.close(); + client = null; + }; + + return bus; +}); diff --git a/activities/Paint.activity/lib/sugar-web/datastore.js b/activities/Paint.activity/lib/sugar-web/datastore.js new file mode 100644 index 000000000..929177960 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/datastore.js @@ -0,0 +1,221 @@ +define(["sugar-web/bus", "sugar-web/env"], function (bus, env) { + var datastore = {}; + + function DatastoreObject(objectId) { + this.objectId = objectId; + this.newMetadata = {}; + + this.ensureObjectId = function (callback) { + var that = this; + + env.getEnvironment(function (error, environment) { + if (environment.objectId !== null) { + that.objectId = environment.objectId; + } + callback(); + }); + }; + + this.blobToText = function (blob, callback) { + var reader = new FileReader(); + reader.onload = function (e) { + callback(e.target.result); + }; + reader.readAsText(blob); + }; + + this.blobToArrayBuffer = function (blob, callback) { + var reader = new FileReader(); + reader.onload = function (e) { + callback(e.target.result); + }; + reader.readAsArrayBuffer(blob); + }; + + this.saveText = function (metadata, callback) { + var that = this; + + function onSaved(error, outputStream) { + var blob = new Blob([that.newDataAsText]); + + that.blobToArrayBuffer(blob, function (buffer) { + outputStream.write(buffer); + outputStream.close(callback); + }); + } + + datastore.save(this.objectId, metadata, onSaved); + }; + + this.applyChanges = function (metadata, callback) { + for (var key in this.newMetadata) { + metadata[key] = this.newMetadata[key]; + } + + if (this.newDataAsText !== undefined) { + this.saveText(metadata, callback); + } else { + datastore.setMetadata(this.objectId, metadata, callback); + } + }; + } + + DatastoreObject.prototype.getMetadata = function (callback) { + var that = this; + + this.ensureObjectId(function () { + datastore.getMetadata(that.objectId, callback); + }); + }; + + DatastoreObject.prototype.loadAsText = function (callback) { + var that = this; + var inputStream = null; + var arrayBuffers = []; + var metadata = null; + + function onRead(error, data) { + if (data.byteLength === 0) { + var blob = new Blob(arrayBuffers); + + that.blobToText(blob, function (text) { + callback(null, metadata, text); + }); + + inputStream.close(); + + return; + } + + arrayBuffers.push(data); + + inputStream.read(8192, onRead); + } + + function onLoad(error, loadedMetadata, loadedInputStream) { + metadata = loadedMetadata; + inputStream = loadedInputStream; + + inputStream.read(8192, onRead); + } + + this.ensureObjectId(function () { + datastore.load(that.objectId, onLoad); + }); + }; + + DatastoreObject.prototype.setMetadata = function (metadata) { + for (var key in metadata) { + this.newMetadata[key] = metadata[key]; + } + }; + + DatastoreObject.prototype.setDataAsText = function (text) { + this.newDataAsText = text; + }; + + DatastoreObject.prototype.save = function (callback) { + if (callback === undefined) { + callback = function () {}; + } + + var that = this; + + function onCreated(error, objectId) { + that.objectId = objectId; + that.applyChanges({}, callback); + } + + function onGotMetadata(error, metadata) { + that.applyChanges(metadata, callback); + } + + this.ensureObjectId(function () { + if (that.objectId === undefined) { + datastore.create(that.newMetadata, onCreated); + } else { + datastore.getMetadata(that.objectId, onGotMetadata); + } + }); + }; + + datastore.DatastoreObject = DatastoreObject; + + + datastore.setMetadata = function (objectId, metadata, callback) { + function onResponseReceived(error, result) { + if (callback) { + if (error === null) { + callback(null); + } else { + callback(error); + } + } + } + + var params = [objectId, metadata]; + bus.sendMessage("datastore.set_metadata", params, onResponseReceived); + }; + + datastore.getMetadata = function (objectId, callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null, result[0]); + } else { + callback(error, null); + } + } + + var params = [objectId]; + bus.sendMessage("datastore.get_metadata", params, onResponseReceived); + }; + + datastore.load = function (objectId, callback) { + inputStream = bus.createInputStream(); + + inputStream.open(function (error) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, result[0], inputStream); + } else { + callback(responseError, null, null); + } + } + + var params = [objectId, inputStream.streamId]; + bus.sendMessage("datastore.load", params, onResponseReceived); + }); + }; + + datastore.create = function (metadata, callback) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, result[0]); + } else { + callback(responseError, null); + } + } + + var params = [metadata]; + bus.sendMessage("datastore.create", params, onResponseReceived); + }; + + datastore.save = function (objectId, metadata, callback) { + outputStream = bus.createOutputStream(); + + outputStream.open(function (error) { + function onResponseReceived(responseError, result) { + if (responseError === null) { + callback(null, outputStream); + } else { + callback(responseError, null); + } + } + + var params = [objectId, metadata, outputStream.streamId]; + bus.sendMessage("datastore.save", params, onResponseReceived); + }); + }; + + return datastore; +}); diff --git a/activities/Paint.activity/lib/sugar-web/env.js b/activities/Paint.activity/lib/sugar-web/env.js new file mode 100644 index 000000000..1a948eb30 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/env.js @@ -0,0 +1,27 @@ +define(function () { + + var env = {}; + + env.getEnvironment = function (callback) { + var sugar; + + if (window.top.sugar) { + sugar = window.top.sugar; + } else { + sugar = {}; + window.top.sugar = sugar; + } + + if (sugar.environment) { + setTimeout(function () { + callback(null, sugar.environment); + }, 0); + } else { + sugar.onEnvironmentSet = function () { + callback(null, sugar.environment); + }; + } + }; + + return env; +}); diff --git a/activities/Paint.activity/lib/sugar-web/graphics/README.md b/activities/Paint.activity/lib/sugar-web/graphics/README.md new file mode 100644 index 000000000..e3b7091ba --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/README.md @@ -0,0 +1,39 @@ +Sugar Graphics +============== + +Sugar widgets and graphics, implementing the [Sugar Interface +Guidelines](http://wiki.sugarlabs.org/go/Human_Interface_Guidelines). + +Modifying the CSS +----------------- + +We use [LESS](http://lesscss.org) and then compile the CSS. This is +to be able to use calculations and variables for colors and measures. +And to be able to output different CSS files for different screen +resolutions, which is planned. + +To compile the CSS do: + + lessc graphics/css/sugar.less graphics/css/sugar.css + +Be sure to compile it before commit. + +The grid helper +--------------- + +The grid is a visual debug tool that allows you to check if the +elements have the correct size. To activate the grid, open the +inspector console and paste the following. + +If RequireJS is not in the page head (ie. in the sugar-web-samples), +load it: + + var script = document.createElement('script'); + script.src = 'lib/require.js'; + document.head.appendChild(script); + + requirejs.config({ baseUrl: "lib" }); + +Then do: + + require(["sugar-web/graphics/grid"], function (grid) { grid.addGrid(11) }); diff --git a/activities/Paint.activity/lib/sugar-web/graphics/activitypalette.js b/activities/Paint.activity/lib/sugar-web/graphics/activitypalette.js new file mode 100644 index 000000000..aca036ebe --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/activitypalette.js @@ -0,0 +1,70 @@ +define(["sugar-web/graphics/palette"], function (palette) { + + var activitypalette = {}; + + activitypalette.ActivityPalette = function (activityButton, + datastoreObject) { + + palette.Palette.call(this, activityButton); + + var activityTitle; + var descriptionLabel; + var descriptionBox; + + this.getPalette().id = "activity-palette"; + + this.template = + '
        ' + + '' + + '
        ' + + '
        ' + + '' + + '
        ' + + '
        ' + + '' + + '
        '; + + var containerElem = document.createElement('div'); + containerElem.innerHTML = this.template; + this.setContent([containerElem]); + + this.titleElem = containerElem.querySelector('#title'); + this.descriptionElem = containerElem.querySelector('#description'); + + this.titleElem.onblur = function () { + datastoreObject.setMetadata({ + "title": this.value, + "title_set_by_user": "1" + }); + datastoreObject.save(); + }; + + this.descriptionElem.onblur = function () { + datastoreObject.setMetadata({ + "description": this.value + }); + datastoreObject.save(); + }; + }; + + // Fill the text inputs with the received metadata. + var setTitleDescription = function (metadata) { + this.titleElem.value = metadata.title; + + if (metadata.description !== undefined) { + this.descriptionElem.value = metadata.description; + } + }; + + activitypalette.ActivityPalette.prototype = + Object.create(palette.Palette.prototype, { + setTitleDescription: { + value: setTitleDescription, + enumerable: true, + configurable: true, + writable: true + } + }); + + return activitypalette; +}); diff --git a/activities/Paint.activity/lib/sugar-web/graphics/css/sugar.css b/activities/Paint.activity/lib/sugar-web/graphics/css/sugar.css new file mode 100644 index 000000000..cc1419d65 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/css/sugar.css @@ -0,0 +1,456 @@ +* { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +html { + height: 100%; +} +body { + margin: 0; + height: 100%; + background-color: #c0c0c0; + position: relative; + font-family: sans-serif; + font-size: 10pt; +} +.unselectable { + -moz-user-select: none; + -webkit-user-select: none; +} +.pull-right { + float: right; +} +a { + color: #0076c3; + text-decoration: none; +} +/* Toolbar */ +.toolbar { + color: white; + background-color: #282828; + padding: 0 57px; + height: 55px; + -moz-user-select: none; + -webkit-user-select: none; +} +/* Toolbar separator */ +.toolbar hr { + display: inline-block; + height: 33px; + border: 1px solid #808080; + margin: 2.5px; + margin-bottom: -17px; +} +/* Toolbar toolbutton */ +.toolbar .toolbutton { + background-color: transparent; + background-position: center; + background-size: contain; + background-repeat: no-repeat; + color: white; + color: transparent; + border: 0; + border-radius: 5.5px; + margin: 4px 2px; + width: 47px; + height: 47px; + position: relative; +} +.toolbar .toolbutton:hover { + background-color: black; +} +.toolbar .toolbutton:active, +.toolbar .toolbutton.active { + background-color: #808080; +} +.toolbar .toolbutton img { + width: 100%; + height: 100%; +} +.toolbar .toolbutton:before { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: 55px; + height: 11px; + bottom: -4px; + right: -4px; +} +.toolbar .toolbutton.invoker:before { + background-image: url('../icons/emblems/arrow-down.svg'); +} +.toolbar #stop-button { + background-image: url('../icons/actions/activity-stop.svg'); +} +/* Canvas */ +#canvas { + color: black; + background-color: #c0c0c0; + position: absolute; + bottom: 0; + top: 55px; + overflow-y: auto; + width: 100%; +} +/* Button */ +button { + background-color: #808080; + color: white; + border: 2px solid transparent; + border-radius: 22px; + line-height: 22px; + padding: 2px 4px; + -moz-user-select: none; + -webkit-user-select: none; +} +button:hover { + background-color: #a2a2a2; + border-color: #a2a2a2; +} +button:active { + background-color: white; + color: black; + border-color: #808080; +} +button:focus { + border-color: white; +} +.toolbar button { + margin-top: 12.5px; +} +/* Button with icon */ +button.icon { + position: relative; + padding-left: 26px; +} +button.icon span.ok { + background-image: url(../icons/actions/dialog-ok.svg); +} +button.icon:active span.ok { + background-image: url(../icons/actions/dialog-ok-active.svg); +} +button.icon span.cancel { + background-image: url(../icons/actions/dialog-cancel.svg); +} +button.icon:active span.cancel { + background-image: url(../icons/actions/dialog-cancel-active.svg); +} +button.icon span { + display: inline-block; + width: 22px; + height: 22px; + background-color: transparent; + background-position: center; + background-size: 22px 22px; + background-repeat: no-repeat; + position: absolute; +} +button.icon span { + top: 2px; + left: 2px; +} +/* One line text input */ +input[type='text'] { + background-color: #e5e5e5; + border: 2px solid #e5e5e5; + border-radius: 22px; + padding: 4px; + width: 165px; + line-height: 22px; + outline: 0; +} +input[type='text']:focus { + background-color: white; +} +input[type='text']:disabled { + border-color: #808080; + background-color: #808080; +} +.toolbar input[type='text'], +.palette .row input[type='text'] { + margin-top: 10.5px; +} +.palette .row input[type='text']:nth-last-child(1) { + margin-top: 8.5px; +} +input[type='text'].expand { + width: calc(100% - 12px); +} +/* One line text input with buttons inside */ +.icon-input { + display: inline-block; + position: relative; +} +.icon-input input[type='text'] { + width: 135px; + padding-right: 34px; +} +.icon-input.expand { + width: 100%; +} +.icon-input.expand input[type='text'] { + width: calc(100% - 42px); +} +.icon-input button { + width: 34px; + height: 34px; + padding: 0; + position: absolute; + background-size: 22px 22px; +} +.icon-input button.right { + margin: 0 0 0 -34px; + border-radius: 0 22px 22px 0; + right: 0; +} +.icon-input button { + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + border: 0; +} +.icon-input button { + top: 2px; +} +.toolbar .icon-input button:hover { + background-color: transparent; +} +.toolbar .icon-input button { + top: 10.5px; +} +button.cancel { + background-image: url(../icons/actions/entry-cancel.svg); +} +button.cancel:active { + background-image: url(../icons/actions/entry-cancel-active.svg); +} +button.cancel:disabled { + background-image: url(../icons/actions/entry-cancel-disabled.svg); +} +/* Slider */ +/* FIXME this is not fully Sugarized yet */ +input[type='range'] { + -webkit-appearance: none !important; + background-color: #808080; + border-radius: 22px; + height: 11px; + cursor: pointer; +} +input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none !important; + background-color: #c0c0c0; + border: 4px solid #808080; + border-radius: 11px; + height: 22px; + width: 22px; +} +input[type='range']::-webkit-slider-thumb:hover { + background-color: #e2e2e2; + border-color: #a2a2a2; +} +.toolbar input[type='range'] { + margin-top: 22px; +} +/* Label */ +label { + -moz-user-select: none; + -webkit-user-select: none; +} +/* Palette */ +.palette { + color: white; + background-color: transparent; + position: absolute; + pointer-events: none; +} +.palette-invoker { + width: 51px; + height: 53px; + background-color: black; + border: 2px solid #808080; + border-bottom: 0; + background-position: 2px 2px; + background-size: 47px; + background-repeat: no-repeat; +} +.palette-invoker:after { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: 55px; + height: 11px; + top: 44px; + left: 0; + background-image: url('../icons/emblems/arrow-up.svg'); +} +.palette-invoker:before { + content: ""; + background-color: black; + position: absolute; + display: block; + top: 55px; + width: 51px; + left: 2px; + height: 2px; +} +.palette .wrapper { + background-color: black; + border: 2px solid #808080; + max-width: 271px; + min-width: 161px; + min-height: 51px; + pointer-events: auto; +} +.palette .header { + height: 51px; + line-height: 51px; + -moz-user-select: none; + -webkit-user-select: none; + margin: 0 5.5px; + font-weight: bold; +} +.palette hr { + border: 1px solid #808080; +} +.palette hr.header-separator { + margin-top: 0; +} +.palette .row { + height: 55px; + margin: 0 5.5px; +} +.palette .row:nth-last-child(1) { + height: 51px; +} +.palette .row.small { + height: 22px; +} +.palette .row.expand { + height: auto; +} +/* Palette menu */ +.palette .menu { + list-style-type: none; + padding: 0; + margin-top: 11px; + margin-bottom: 11px; +} +.palette .menu button { + background-color: transparent; + border-radius: 0; + border: 0; + width: 100%; + text-align: left; + line-height: 33px; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; +} +.palette .menu button:hover { + background-color: #808080; + color: white; +} +.palette .menu button.icon { + padding-left: 37px; +} +.palette .menu button.icon span { + top: 0; + left: 0; + width: 33px; + height: 33px; +} +/* Scrollbar */ +::-webkit-scrollbar { + background-color: #808080; + width: 11px; +} +::-webkit-scrollbar-thumb { + background-color: white; + border: 2px solid #dddddd; + border-radius: 11px; +} +/* Grid for visual debugging and layout */ +.grid { + background-color: transparent; + position: absolute; + top: 0; + left: 0; +} +/* Checkbox and radio */ +input[type='checkbox'], +input[type='radio'] { + background-position: center; + background-size: contain; + width: 22px; + height: 22px; + margin: 2px 2px 4px 2px; + vertical-align: middle; + -moz-appearance: none; + appearance: none; + -webkit-appearance: none; +} +.toolbar input[type='checkbox'], +.toolbar input[type='radio'] { + margin-top: 14.5px; + margin-bottom: 18.5px; +} +input[type='checkbox'] { + background-image: url(../icons/actions/checkbox-unchecked.svg); +} +input[type='checkbox']:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} +input[type='checkbox']:checked:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} +input[type='checkbox']:checked { + background-image: url(../icons/actions/checkbox-checked.svg); +} +input[type='radio'] { + background-image: url(../icons/actions/radio.svg); +} +input[type='radio']:active { + background-image: url(../icons/actions/radio-selected.svg); +} +input[type='radio']:checked:active { + background-image: url(../icons/actions/radio-active-selected.svg); +} +input[type='radio']:checked { + background-image: url(../icons/actions/radio-active.svg); +} +/* Textarea */ +textarea { + border: 2px solid #808080; + margin: 2px; +} +textarea.expand { + width: calc(100% - 12px); +} +/* Lists */ +ul.flat-list { + list-style-type: none; + padding: 0; + margin: 0; +} +ul.flat-list li { + border-bottom: 2px dotted #c0c0c0; + background-color: white; + height: 31px; + line-height: 31px; +} +ul.flat-list li:nth-last-child(1) { + border-bottom: none; +} +ul.flat-list.big li { + height: 42px; + line-height: 42px; +} +ul.flat-list.striped li:nth-child(odd) { + background-color: #e5e5e5; +} +/* ActivityPalette */ +#activity-palette .wrapper { + width: 271px; +} diff --git a/activities/Paint.activity/lib/sugar-web/graphics/css/sugar.less b/activities/Paint.activity/lib/sugar-web/graphics/css/sugar.less new file mode 100644 index 000000000..8fa9766d6 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/css/sugar.less @@ -0,0 +1,589 @@ +@toolbar-grey: #282828; +@button-grey: #808080; +@panel-grey: #C0C0C0; +@text-field-grey: #E5E5E5; +@link-blue: #0076C3; + +@line-width: 2px; +@subcell-size: 11px; +@cell-size: 5 * @subcell-size; +@font-size: 10pt; + +@toolbar-height: @cell-size; +@icon-small-size: 2 * @subcell-size; + +@toolbutton-size: @toolbar-height - (4 * @line-width); +@toolbar-button-margin-top: (@toolbar-height / 2) - (2 * @line-width) - + (@icon-small-size / 2); + +@input-text-padding: (2 * @line-width); +@input-text-line-height: 2 * @subcell-size; +@input-text-width: 3 * @cell-size; +@toolbar-input-height: (@toolbar-height / 2) - (@input-text-line-height / 2) - + (@input-text-padding + @line-width); + +@button-small-size: @input-text-line-height + (2 * @input-text-padding) + + (2 * @line-width); + +* { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +html { + height: 100%; +} + +body { + margin: 0; + height: 100%; + background-color: @panel-grey; + position: relative; + font-family: sans-serif; + font-size: @font-size; +} + +.unselectable { + -moz-user-select: none; + -webkit-user-select: none; +} + +.pull-right { + float: right; +} + +a { + color: @link-blue; + text-decoration: none; +} + +/* Toolbar */ + +.toolbar { + color: white; + background-color: @toolbar-grey; + padding: 0 @toolbutton-size + (5 * @line-width); + height: @toolbar-height; + .unselectable; +} + +/* Toolbar separator */ + +.toolbar hr { + display: inline-block; + height: 3 * @subcell-size; + border: (@line-width / 2) solid @button-grey; + margin: (@subcell-size - (@line-width * 3)) / 2; + margin-bottom: -1 * (@subcell-size + (@line-width * 3)); +} + +/* Toolbar toolbutton */ + +.toolbar .toolbutton { + background-color: transparent; + background-position: center; + background-size: contain; + background-repeat: no-repeat; + color: white; + color: transparent; + border: 0; + border-radius: @subcell-size / 2; + margin: (2 * @line-width) @line-width; + width: @toolbutton-size; + height: @toolbutton-size; + position: relative; +} + +.toolbar .toolbutton:hover { + background-color: black; +} + +.toolbar .toolbutton:active, .toolbar .toolbutton.active { + background-color: @button-grey; +} + +.toolbar .toolbutton img { + width: 100%; + height: 100%; +} + +.toolbar .toolbutton:before { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: @cell-size; + height: @subcell-size; + bottom: -2 * @line-width; + right: -2 * @line-width; +} + +.toolbar .toolbutton.invoker:before { + background-image: url('../icons/emblems/arrow-down.svg'); +} + +.toolbar #stop-button { + background-image: url('../icons/actions/activity-stop.svg'); +} + +/* Canvas */ + +#canvas { + color: black; + background-color: @panel-grey; + position: absolute; + bottom: 0; + top: @toolbar-height; + overflow-y: auto; + width: 100%; +} + +/* Button */ + +button { + background-color: @button-grey; + color: white; + border: @line-width solid transparent; + border-radius: 2 * @subcell-size; + line-height: @icon-small-size; + padding: @line-width (2 * @line-width); + .unselectable; +} + +button:hover { + background-color: @button-grey + #222; + border-color: @button-grey + #222; +} + +button:active { + background-color: white; + color: black; + border-color: @button-grey; +} + +button:focus { + border-color: white; +} + +.toolbar button { + margin-top: @toolbar-button-margin-top; +} + +/* Button with icon */ + +button.icon { + position: relative; + padding-left: @icon-small-size + (2 * @line-width); +} + +button.icon span.ok { + background-image: url(../icons/actions/dialog-ok.svg); +} + +button.icon:active span.ok { + background-image: url(../icons/actions/dialog-ok-active.svg); +} + +button.icon span.cancel { + background-image: url(../icons/actions/dialog-cancel.svg); +} + +button.icon:active span.cancel { + background-image: url(../icons/actions/dialog-cancel-active.svg); +} + +button.icon span { + display: inline-block; + width: @icon-small-size; + height: @icon-small-size; + background-color: transparent; + background-position: center; + background-size: @icon-small-size @icon-small-size; + background-repeat: no-repeat; + position: absolute; +} + +button.icon span { + top: @line-width; + left: @line-width; +} + +/* One line text input */ + +input[type='text'] { + background-color: @text-field-grey; + border: @line-width solid @text-field-grey; + border-radius: 2 * @subcell-size; + padding: @input-text-padding; + width: @input-text-width; + line-height: @input-text-line-height; + outline: 0; +} + +input[type='text']:focus { + background-color: white; +} + +input[type='text']:disabled { + border-color: @button-grey; + background-color: @button-grey; +} + +.toolbar input[type='text'], .palette .row input[type='text'] { + margin-top: @toolbar-input-height; +} + +.palette .row input[type='text']:nth-last-child(1) { + margin-top: @toolbar-input-height - @line-width; +} + +input[type='text'].expand { + width: calc(~"100%" - (2 * @input-text-padding + 2 * @line-width)); +} + +/* One line text input with buttons inside */ + +.icon-input { + display: inline-block; + position: relative; +} + +.icon-input input[type='text'] { + width: @input-text-width + @input-text-padding - @button-small-size; + padding-right: @button-small-size; +} + +.icon-input.expand { + width: 100%; +} + +.icon-input.expand input[type='text'] { + width: calc(~"100%" - (@input-text-padding + 2 * @line-width + + @button-small-size)); +} + +.icon-input button { + width: @button-small-size; + height: @button-small-size; + padding: 0; + position: absolute; + background-size: @icon-small-size @icon-small-size; +} + +.icon-input button.right { + margin: 0 0 0 -@button-small-size; + border-radius: 0 (2 * @subcell-size) (2 * @subcell-size) 0; + right: 0; +} + +.icon-input button { + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + border: 0; +} + +.icon-input button { + top: @line-width; +} + +.toolbar .icon-input button:hover { + background-color: transparent; +} + +.toolbar .icon-input button { + top: @toolbar-input-height; +} + +button.cancel { + background-image: url(../icons/actions/entry-cancel.svg); +} + +button.cancel:active { + background-image: url(../icons/actions/entry-cancel-active.svg); +} + +button.cancel:disabled { + background-image: url(../icons/actions/entry-cancel-disabled.svg); +} + +/* Slider */ +/* FIXME this is not fully Sugarized yet */ + +input[type='range'] { + -webkit-appearance: none !important; + background-color: @button-grey; + border-radius: 2 * @subcell-size; + height: @subcell-size; + cursor: pointer; +} + +input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none !important; + background-color: @panel-grey; + border: (2 * @line-width) solid @button-grey; + border-radius: @subcell-size; + height: @icon-small-size; + width: @icon-small-size; +} + +input[type='range']::-webkit-slider-thumb:hover { + background-color: @panel-grey + #222; + border-color: @button-grey + #222; +} + +.toolbar input[type='range'] { + margin-top: (@toolbar-height / 2) - (@subcell-size / 2); +} + +/* Label */ + +label { + .unselectable; +} + +/* Palette */ + +.palette { + color: white; + background-color: transparent; + position: absolute; + pointer-events: none; +} + +.palette-invoker { + width: @cell-size - 2 * @line-width; + height: @cell-size - @line-width; + background-color: black; + border: @line-width solid @button-grey; + border-bottom: 0; + background-position: @line-width @line-width; + background-size: @toolbutton-size; + background-repeat: no-repeat; +} + +.palette-invoker:after { + content: ""; + background-color: transparent; + position: absolute; + display: block; + width: @cell-size; + height: @subcell-size; + top: 4 * @subcell-size; + left: 0; + background-image: url('../icons/emblems/arrow-up.svg'); +} + +.palette-invoker:before { + content: ""; + background-color: black; + position: absolute; + display: block; + top: @cell-size; + width: @cell-size - 2 * @line-width; + left: @line-width; + height: @line-width; +} + +.palette .wrapper { + background-color: black; + border: @line-width solid @button-grey; + max-width: 5 * @cell-size - 2 * @line-width; + min-width: 3 * @cell-size - 2 * @line-width; + min-height: @cell-size - 2 * @line-width; + pointer-events: auto; +} + +.palette .header { + height: @cell-size - 2 * @line-width; + line-height: @cell-size - 2 * @line-width; + .unselectable; + margin: 0 (@subcell-size / 2); + font-weight: bold; +} + +.palette hr { + border: (@line-width / 2) solid @button-grey; +} + +.palette hr.header-separator { + margin-top: 0; +} + +.palette .row { + height: @cell-size; + margin: 0 (@subcell-size / 2); +} + +.palette .row:nth-last-child(1) { + height: @cell-size - 2 * @line-width; +} + +.palette .row.small { + height: 2 * @subcell-size; +} + +.palette .row.expand { + height: auto; +} + +/* Palette menu */ + +.palette .menu { + list-style-type: none; + padding: 0; + margin-top: @subcell-size; + margin-bottom: @subcell-size; +} + +.palette .menu button { + background-color: transparent; + border-radius: 0; + border: 0; + width: 100%; + text-align: left; + line-height: 3 * @subcell-size; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; +} + +.palette .menu button:hover { + background-color: @button-grey; + color: white; +} + +.palette .menu button.icon { + padding-left: 3 * @subcell-size + 2 * @line-width; +} + +.palette .menu button.icon span { + top: 0; + left: 0; + width: 3 * @subcell-size; + height: 3 * @subcell-size; +} + +/* Scrollbar */ + +::-webkit-scrollbar { + background-color: @button-grey; + width: @subcell-size; +} + +::-webkit-scrollbar-thumb { + background-color: white; + border: @line-width solid #ddd; + border-radius: @subcell-size; +} + +/* Grid for visual debugging and layout */ + +.grid { + background-color: transparent; + position: absolute; + top: 0; + left: 0; +} + +.appearance(@value) { + -moz-appearance: @value; + appearance: @value; + -webkit-appearance: @value; +} + +/* Checkbox and radio */ + +input[type='checkbox'], +input[type='radio'] { + background-position: center; + background-size: contain; + width: @icon-small-size; + height: @icon-small-size; + margin: @line-width @line-width (2 * @line-width) @line-width; + vertical-align: middle; + .appearance(none); +} + +.toolbar input[type='checkbox'], +.toolbar input[type='radio'] { + margin-top: (@toolbar-height - @icon-small-size) / 2 - @line-width; + margin-bottom: (@toolbar-height - @icon-small-size) / 2 + @line-width; +} + +input[type='checkbox'] { + background-image: url(../icons/actions/checkbox-unchecked.svg); +} + +input[type='checkbox']:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} + +input[type='checkbox']:checked:active { + background-image: url(../icons/actions/checkbox-checked-selected.svg); +} + +input[type='checkbox']:checked { + background-image: url(../icons/actions/checkbox-checked.svg); +} + +input[type='radio'] { + background-image: url(../icons/actions/radio.svg); +} + +input[type='radio']:active { + background-image: url(../icons/actions/radio-selected.svg); +} + +input[type='radio']:checked:active { + background-image: url(../icons/actions/radio-active-selected.svg); +} + +input[type='radio']:checked { + background-image: url(../icons/actions/radio-active.svg); +} + +/* Textarea */ + +textarea { + border: @line-width solid @button-grey; + margin: @line-width; +} + +textarea.expand { + width: calc(~"100%" - (6 * (@line-width))); +} + +/* Lists */ + +ul.flat-list { + list-style-type: none; + padding: 0; + margin: 0; +} + +ul.flat-list li { + border-bottom: @line-width dotted @panel-grey; + background-color: white; + height: 3 * @subcell-size - @line-width; + line-height: 3 * @subcell-size - @line-width; +} + +ul.flat-list li:nth-last-child(1) { + border-bottom: none; +} + +ul.flat-list.big li { + height: 4 * @subcell-size - @line-width; + line-height: 4 * @subcell-size - @line-width; +} + +ul.flat-list.striped li:nth-child(odd) { + background-color: @text-field-grey; +} + +/* ActivityPalette */ + +#activity-palette .wrapper { + width: 5 * @cell-size - 2 * @line-width; +} \ No newline at end of file diff --git a/activities/Paint.activity/lib/sugar-web/graphics/grid.js b/activities/Paint.activity/lib/sugar-web/graphics/grid.js new file mode 100644 index 000000000..503713ab7 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/grid.js @@ -0,0 +1,54 @@ +define(function () { + var grid = {}; + + // Add a grid overlay with lines spaced by subcellSize, for visual + // debugging. This is useful while doing the activity layout or + // while developing widgets. + grid.addGrid = function (subcellSize) { + var canvas = document.createElement('canvas'); + canvas.className = "grid"; + document.body.appendChild(canvas); + + var updateGrid = function () { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + var ctx = canvas.getContext("2d"); + ctx.strokeStyle = "#00FFFF"; + + var subcellsVertical = window.innerHeight / subcellSize; + for (i = 0; i < subcellsVertical; i++) { + if ((i + 1) % 5 === 0) { + ctx.lineWidth = 1; + } else { + ctx.lineWidth = 0.5; + } + ctx.beginPath(); + ctx.moveTo(0, subcellSize * (i + 1)); + ctx.lineTo(canvas.width, subcellSize * (i + 1)); + ctx.stroke(); + } + + var subcellsHorizontal = window.innerWidth / subcellSize; + for (i = 0; i < subcellsHorizontal; i++) { + if ((i + 1) % 5 === 0) { + ctx.lineWidth = 1; + } else { + ctx.lineWidth = 0.5; + } + ctx.beginPath(); + ctx.moveTo(subcellSize * (i + 1), 0); + ctx.lineTo(subcellSize * (i + 1), canvas.height); + ctx.stroke(); + } + }; + + updateGrid(); + + window.onresize = function (event) { + updateGrid(); + }; + }; + + return grid; +}); diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icon.js b/activities/Paint.activity/lib/sugar-web/graphics/icon.js new file mode 100644 index 000000000..f2c07915a --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icon.js @@ -0,0 +1,81 @@ +define(function () { + var icon = {}; + + function changeColors(iconData, fillColor, strokeColor) { + var re; + + if (fillColor) { + re = /()/; + iconData = iconData.replace(re, "$1" + fillColor + "$3"); + } + + if (strokeColor) { + re = /()/; + iconData = iconData.replace(re, "$1" + strokeColor + "$3"); + } + + return iconData; + } + + icon.load = function (iconInfo, callback) { + var source; + var dataHeader = "data:image/svg+xml,"; + + if ("uri" in iconInfo) { + source = iconInfo.uri; + } else if ("name" in iconInfo) { + source = "lib/graphics/icons/" + iconInfo.name + ".svg"; + } + + var fillColor = iconInfo.fillColor; + var strokeColor = iconInfo.strokeColor; + + // If source is already a data uri, read it instead of doing + // the XMLHttpRequest + if (source.substring(0, 4) == 'data') { + var iconData = unescape(source.slice(dataHeader.length)); + var newData = changeColors(iconData, fillColor, strokeColor); + callback(dataHeader + escape(newData)); + return; + } + + var client = new XMLHttpRequest(); + + client.onload = function () { + var iconData = this.responseText; + var newData = changeColors(iconData, fillColor, strokeColor); + callback(dataHeader + escape(newData)); + }; + + client.open("GET", source); + client.send(); + }; + + function getBackgroundURL(elem) { + var style = elem.currentStyle || window.getComputedStyle(elem, ''); + // Remove prefix 'url(' and suffix ')' before return + return style.backgroundImage.slice(4, -1); + } + + function setBackgroundURL(elem, url) { + elem.style.backgroundImage = "url('" + url + "')"; + } + + icon.colorize = function (elem, colors, callback) { + var iconInfo = { + "uri": getBackgroundURL(elem), + "strokeColor": colors.stroke, + "fillColor": colors.fill + }; + + icon.load(iconInfo, function (url) { + setBackgroundURL(elem, url); + if (callback) { + callback(); + } + }); + + }; + + return icon; +}); diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg new file mode 100644 index 000000000..11b82e817 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg new file mode 100644 index 000000000..8ec1223a7 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg @@ -0,0 +1,27 @@ + + + +]> + + + + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg new file mode 100644 index 000000000..3cfce18f6 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg @@ -0,0 +1,27 @@ + + + +]> + + + + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg new file mode 100644 index 000000000..2263279eb --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg @@ -0,0 +1,22 @@ + + +]> + + + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg new file mode 100644 index 000000000..e1587823e --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg @@ -0,0 +1,22 @@ + + +]> + + + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg new file mode 100644 index 000000000..dc0d688f6 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg @@ -0,0 +1,6 @@ + + +]> + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg new file mode 100644 index 000000000..dab4ae2d9 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg new file mode 100644 index 000000000..45de8401a --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg @@ -0,0 +1,6 @@ + + +]> + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg new file mode 100644 index 000000000..69e5a2a13 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg new file mode 100644 index 000000000..467509e75 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg @@ -0,0 +1,23 @@ + + + +]> + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg new file mode 100644 index 000000000..55b4cdb87 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg @@ -0,0 +1,21 @@ + + + +]> + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg new file mode 100644 index 000000000..5339a7e94 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg @@ -0,0 +1,21 @@ + + + +]> + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg new file mode 100644 index 000000000..c1d1085a0 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg @@ -0,0 +1,31 @@ + + + +]> + + + + + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg new file mode 100644 index 000000000..a5fe59156 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg @@ -0,0 +1,31 @@ + + + +]> + + + + + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg new file mode 100644 index 000000000..a24b97e65 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg @@ -0,0 +1,26 @@ + + + +]> + + + + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio.svg new file mode 100644 index 000000000..d25028624 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/radio.svg @@ -0,0 +1,26 @@ + + + +]> + + + + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg new file mode 100644 index 000000000..b88462ff5 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg new file mode 100644 index 000000000..5578fecbf --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg new file mode 100644 index 000000000..8d3f8d134 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg new file mode 100644 index 000000000..2de1a9e34 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg @@ -0,0 +1,20 @@ + + + +]> + + + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg b/activities/Paint.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg new file mode 100644 index 000000000..a977f4a86 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg @@ -0,0 +1,20 @@ + + + +]> + + + + + diff --git a/activities/Paint.activity/lib/sugar-web/graphics/menupalette.js b/activities/Paint.activity/lib/sugar-web/graphics/menupalette.js new file mode 100644 index 000000000..df61cb2a7 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/menupalette.js @@ -0,0 +1,64 @@ +define(["sugar-web/graphics/palette", "mustache"], function (palette, mustache) { + + var menupalette = {}; + + menupalette.MenuPalette = function (invoker, primaryText, menuData) { + palette.Palette.call(this, invoker, primaryText); + + this.selectItemEvent = new CustomEvent( + "selectItem", { + detail: { + item: undefined + }, + bubbles: true, + cancelable: true + }); + + this.template = + '{{#.}}' + + '
      • ' + + '{{ #icon }}{{ /icon }}' + + '{{ label }}
      • ' + + '{{/.}}'; + + var menuElem = document.createElement('ul'); + menuElem.className = "menu"; + menuElem.innerHTML = mustache.render(this.template, menuData); + this.setContent([menuElem]); + + // Pop-down the palette when a item in the menu is clicked. + + this.buttons = menuElem.querySelectorAll('button'); + + var that = this; + + function popDownOnButtonClick(event) { + that.selectItemEvent.detail.target = event.target; + that.getPalette().dispatchEvent(that.selectItemEvent); + that.popDown(); + } + + for (var i = 0; i < this.buttons.length; i++) { + this.buttons[i].addEventListener('click', popDownOnButtonClick); + } + }; + + var addEventListener = function (type, listener, useCapture) { + return this.getPalette().addEventListener(type, listener, useCapture); + }; + + menupalette.MenuPalette.prototype = + Object.create(palette.Palette.prototype, { + addEventListener: { + value: addEventListener, + enumerable: true, + configurable: true, + writable: true + } + }); + + return menupalette; +}); diff --git a/activities/Paint.activity/lib/sugar-web/graphics/palette.js b/activities/Paint.activity/lib/sugar-web/graphics/palette.js new file mode 100644 index 000000000..9a194a148 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/palette.js @@ -0,0 +1,179 @@ +define(function () { + var palettesGroup = []; + + function getOffset(elem) { + // Ugly hack to consider the palette margin. + var style = elem.currentStyle || window.getComputedStyle(elem, ''); + + // Remove 'px' from the strings. + var x = -2 * style.marginLeft.slice(0, -2); + var y = -1 * style.marginTop.slice(0, -2); + + var rect = elem.getBoundingClientRect(); + x += rect.left; + y += rect.top; + return { + top: y, + left: x, + width: rect.width, + height: rect.height + }; + } + + var palette = {}; + + palette.Palette = function (invoker, primaryText) { + this.invoker = invoker; + if (this.invoker.classList.contains("toolbutton")) { + this.invoker.classList.add("invoker"); + } + this.primaryText = primaryText; + var paletteElem; + var wrapperElem; + var headerElem; + var headerSeparatorElem; + var containerElem; + var that = this; + palettesGroup.push(this); + + invoker.addEventListener('click', function (event) { + if (!that.invoker.classList.contains("toolbutton")) { + updatePosition(event.x, event.y); + } + that.toggle(); + }); + + function updatePosition(clickX, clickY) { + var paletteX; + var paletteY; + + if (typeof (clickX) !== 'undefined' && + typeof (clickY) !== 'undefined') { + paletteX = clickX; + paletteY = clickY; + } else { + var invokerOffset = getOffset(that.invoker); + paletteX = invokerOffset.left; + paletteY = invokerOffset.top; + } + + paletteElem.style.left = paletteX + "px"; + paletteElem.style.top = paletteY + "px"; + } + + // A palette element can have a header, content, one or both. + + function createPaletteElement() { + if (paletteElem !== undefined) { + return; + } + paletteElem = document.createElement('div'); + paletteElem.className = "palette"; + paletteElem.style.visibility = "hidden"; + document.body.appendChild(paletteElem); + + if (that.invoker.classList.contains("toolbutton")) { + invokerElem = document.createElement('div'); + invokerElem.className = "palette-invoker"; + var style = that.invoker.currentStyle || + window.getComputedStyle(that.invoker, ''); + invokerElem.style.backgroundImage = style.backgroundImage; + + invokerElem.addEventListener('click', function (e) { + that.toggle(); + }); + + paletteElem.appendChild(invokerElem); + + } + + wrapperElem = document.createElement('div'); + wrapperElem.className = "wrapper"; + paletteElem.appendChild(wrapperElem); + + if (that.primaryText !== undefined) { + headerElem = document.createElement('div'); + headerElem.className = "header"; + headerElem.innerText = that.primaryText; + wrapperElem.appendChild(headerElem); + } + + headerSeparatorElem = document.createElement('hr'); + headerSeparatorElem.className = "header-separator"; + headerSeparatorElem.style.display = "none"; + wrapperElem.appendChild(headerSeparatorElem); + + containerElem = document.createElement('div'); + containerElem.className = "container"; + wrapperElem.appendChild(containerElem); + + updatePosition(); + } + + this.getPalette = function () { + if (paletteElem === undefined) { + createPaletteElement(); + } + return paletteElem; + }; + + this.setContent = function (elementsList) { + if (paletteElem === undefined) { + createPaletteElement(); + } + + (function removePreviousContent() { + for (var i = 0; i < containerElem.children.length; i++) { + var child = containerElem.children[i]; + containerElem.removeChild(child); + } + }()); + + (function addNewContent() { + for (var i = 0; i < elementsList.length; i++) { + var child = elementsList[i]; + containerElem.appendChild(child); + } + }()); + + // The header separator will be visible only if there are + // both, header and content. + if (elementsList.length > 0 && this.primaryText !== undefined) { + headerSeparatorElem.style.display = "block"; + } else { + headerSeparatorElem.style.display = "none"; + } + }; + + this.isDown = function () { + return paletteElem === undefined || + paletteElem.style.visibility == "hidden"; + }; + + }; + + palette.Palette.prototype.popUp = function () { + for (var i = 0; i < palettesGroup.length; i++) { + otherPalette = palettesGroup[i]; + if (otherPalette != this) { + otherPalette.popDown(); + } + } + this.getPalette().style.visibility = "visible"; + }; + + palette.Palette.prototype.popDown = function () { + this.getPalette().style.visibility = "hidden"; + }; + + palette.Palette.prototype.toggle = function () { + if (this.isDown()) { + this.popUp(); + } else { + this.popDown(); + } + }; + + return palette; + +}); diff --git a/activities/Paint.activity/lib/sugar-web/graphics/radiobuttonsgroup.js b/activities/Paint.activity/lib/sugar-web/graphics/radiobuttonsgroup.js new file mode 100644 index 000000000..712681bff --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/radiobuttonsgroup.js @@ -0,0 +1,59 @@ +define(function () { + var radioButtonsGroup = {}; + + // ## RadioButtonsGroup + // + // A group of elements where only one can be active at the same + // time. + // + // When an element is clicked, it becomes the active one. The + // active element gains the 'active' CSS class. + // + // Parameters: + // + // * **elems** Array of elements of the group. + radioButtonsGroup.RadioButtonsGroup = function (elems) { + this.elems = elems; + var active; + + for (i = 0; i < elems.length; i++) { + var elem = elems[i]; + elem.addEventListener("click", clickHandler); + + // The first element that has 'active' CSS class is made + // the active of the group on startup. + if (active === undefined && elem.classList.contains('active')) { + active = elem; + } + } + + // If no element has 'active' CSS class, the first element of + // the array is made the active. + if (active === undefined) { + active = elems[0]; + updateClasses(); + } + + function clickHandler(evt) { + active = evt.target; + updateClasses(); + } + + function updateClasses() { + for (i = 0; i < elems.length; i++) { + var elem = elems[i]; + elem.classList.remove('active'); + } + active.classList.add('active'); + } + + // Get the active element. + this.getActive = function () { + return active; + }; + + }; + + return radioButtonsGroup; + +}); diff --git a/activities/Paint.activity/lib/sugar-web/graphics/xocolor.js b/activities/Paint.activity/lib/sugar-web/graphics/xocolor.js new file mode 100644 index 000000000..1184ffa5c --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/graphics/xocolor.js @@ -0,0 +1,729 @@ +define(function () { + + var xocolor = {}; + + xocolor.colors = [ + { + stroke: '#B20008', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#B20008' + }, + { + stroke: '#E6000A', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#E6000A' + }, + { + stroke: '#FFADCE', + fill: '#FF2B34' + }, + { + stroke: '#9A5200', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#FF2B34' + }, + { + stroke: '#807500', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#FF2B34' + }, + { + stroke: '#008009', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#FF2B34' + }, + { + stroke: '#00588C', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#FF2B34' + }, + { + stroke: '#5E008C', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#FF2B34' + }, + { + stroke: '#FF2B34', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#FF2B34' + }, + { + stroke: '#9A5200', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#9A5200' + }, + { + stroke: '#C97E00', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#C97E00' + }, + { + stroke: '#FFC169', + fill: '#FF8F00' + }, + { + stroke: '#807500', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#FF8F00' + }, + { + stroke: '#008009', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#FF8F00' + }, + { + stroke: '#00588C', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#FF8F00' + }, + { + stroke: '#5E008C', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#5E008C' + }, + { + stroke: '#A700FF', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#A700FF' + }, + { + stroke: '#D1A3FF', + fill: '#FF8F00' + }, + { + stroke: '#B20008', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#FF8F00' + }, + { + stroke: '#FF8F00', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#FF8F00' + }, + { + stroke: '#807500', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#BE9E00' + }, + { + stroke: '#FFFA00', + fill: '#EDDE00' + }, + { + stroke: '#008009', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#008009' + }, + { + stroke: '#00EA11', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00EA11' + }, + { + stroke: '#8BFF7A', + fill: '#F8E800' + }, + { + stroke: '#00588C', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00588C' + }, + { + stroke: '#00A0FF', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#00A0FF' + }, + { + stroke: '#BCCEFF', + fill: '#F8E800' + }, + { + stroke: '#5E008C', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#5E008C' + }, + { + stroke: '#AC32FF', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#AC32FF' + }, + { + stroke: '#D1A3FF', + fill: '#F8E800' + }, + { + stroke: '#B20008', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#F8E800' + }, + { + stroke: '#9A5200', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#F8E800' + }, + { + stroke: '#F8E800', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#F8E800' + }, + { + stroke: '#008009', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#00EA11' + }, + { + stroke: '#00588C', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#00EA11' + }, + { + stroke: '#5E008C', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#00EA11' + }, + { + stroke: '#B20008', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#00EA11' + }, + { + stroke: '#9A5200', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#00EA11' + }, + { + stroke: '#807500', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#00EA11' + }, + { + stroke: '#00EA11', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#00EA11' + }, + { + stroke: '#00588C', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#00A0FF' + }, + { + stroke: '#5E008C', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#5E008C' + }, + { + stroke: '#9900E6', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#9900E6' + }, + { + stroke: '#D1A3FF', + fill: '#00A0FF' + }, + { + stroke: '#B20008', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#00A0FF' + }, + { + stroke: '#9A5200', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#00A0FF' + }, + { + stroke: '#807500', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#00A0FF' + }, + { + stroke: '#008009', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#00A0FF' + }, + { + stroke: '#00A0FF', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#00A0FF' + }, + { + stroke: '#5E008C', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#5E008C' + }, + { + stroke: '#7F00BF', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#7F00BF' + }, + { + stroke: '#D1A3FF', + fill: '#AC32FF' + }, + { + stroke: '#B20008', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#B20008' + }, + { + stroke: '#FF2B34', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#FF2B34' + }, + { + stroke: '#FFADCE', + fill: '#AC32FF' + }, + { + stroke: '#9A5200', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#9A5200' + }, + { + stroke: '#FF8F00', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#FF8F00' + }, + { + stroke: '#FFC169', + fill: '#AC32FF' + }, + { + stroke: '#807500', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#807500' + }, + { + stroke: '#BE9E00', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#BE9E00' + }, + { + stroke: '#F8E800', + fill: '#AC32FF' + }, + { + stroke: '#008009', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#008009' + }, + { + stroke: '#00B20D', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#00B20D' + }, + { + stroke: '#8BFF7A', + fill: '#AC32FF' + }, + { + stroke: '#00588C', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#00588C' + }, + { + stroke: '#005FE4', + fill: '#AC32FF' + }, + { + stroke: '#AC32FF', + fill: '#005FE4' + }, + { + stroke: '#BCCDFF', + fill: '#AC32FF' + } + ]; + + return xocolor; +}); diff --git a/activities/Paint.activity/lib/sugar-web/package.json b/activities/Paint.activity/lib/sugar-web/package.json new file mode 100644 index 000000000..000de4e20 --- /dev/null +++ b/activities/Paint.activity/lib/sugar-web/package.json @@ -0,0 +1,9 @@ +{ + "volo": { + "baseUrl": "lib", + "dependencies": { + "webL10n": "github:sugarlabs/webL10n", + "mustache": "github:janl/mustache.js/0.7.2" + } + } +} \ No newline at end of file diff --git a/activities/Paint.activity/lib/webL10n.js b/activities/Paint.activity/lib/webL10n.js new file mode 100644 index 000000000..f76d66a5e --- /dev/null +++ b/activities/Paint.activity/lib/webL10n.js @@ -0,0 +1,1029 @@ +/** + * Copyright (c) 2011-2013 Fabien Cazenave, Mozilla. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/*jshint browser: true, devel: true, es5: true, globalstrict: true */ +'use strict'; + +define(function (require) { + var gL10nData = {}; + var gTextData = ''; + var gTextProp = 'textContent'; + var gLanguage = ''; + var gMacros = {}; + var gReadyState = 'loading'; + + /** + * Synchronously loading l10n resources significantly minimizes flickering + * from displaying the app with non-localized strings and then updating the + * strings. Although this will block all script execution on this page, we + * expect that the l10n resources are available locally on flash-storage. + * + * As synchronous XHR is generally considered as a bad idea, we're still + * loading l10n resources asynchronously -- but we keep this in a setting, + * just in case... and applications using this library should hide their + * content until the `localized' event happens. + */ + + var gAsyncResourceLoading = true; // read-only + + + /** + * Debug helpers + * + * gDEBUG == 0: don't display any console message + * gDEBUG == 1: display only warnings, not logs + * gDEBUG == 2: display all console messages + */ + + var gDEBUG = 1; + + function consoleLog(message) { + if (gDEBUG >= 2) { + console.log('[l10n] ' + message); + } + }; + + function consoleWarn(message) { + if (gDEBUG) { + console.warn('[l10n] ' + message); + } + }; + + + /** + * DOM helpers for the so-called "HTML API". + * + * These functions are written for modern browsers. For old versions of IE, + * they're overridden in the 'startup' section at the end of this file. + */ + + function getL10nResourceLinks() { + return document.querySelectorAll('link[type="application/l10n"]'); + } + + function getL10nDictionary() { + var script = document.querySelector('script[type="application/l10n"]'); + // TODO: support multiple and external JSON dictionaries + return script ? JSON.parse(script.innerHTML) : null; + } + + function getTranslatableChildren(element) { + return element ? element.querySelectorAll('*[data-l10n-id]') : []; + } + + function getL10nAttributes(element) { + if (!element) + return {}; + + var l10nId = element.getAttribute('data-l10n-id'); + var l10nArgs = element.getAttribute('data-l10n-args'); + var args = {}; + if (l10nArgs) { + try { + args = JSON.parse(l10nArgs); + } catch (e) { + consoleWarn('could not parse arguments for #' + l10nId); + } + } + return { id: l10nId, args: args }; + } + + function fireL10nReadyEvent(lang) { + var evtObject = document.createEvent('Event'); + evtObject.initEvent('localized', true, false); + evtObject.language = lang; + document.dispatchEvent(evtObject); + } + + function xhrLoadText(url, onSuccess, onFailure, asynchronous) { + onSuccess = onSuccess || function _onSuccess(data) {}; + onFailure = onFailure || function _onFailure() { + consoleWarn(url + ' not found.'); + }; + + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, asynchronous); + if (xhr.overrideMimeType) { + xhr.overrideMimeType('text/plain; charset=utf-8'); + } + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + if (xhr.status == 200 || xhr.status === 0) { + onSuccess(xhr.responseText); + } else { + onFailure(); + } + } + }; + xhr.onerror = onFailure; + xhr.ontimeout = onFailure; + + // in Firefox OS with the app:// protocol, trying to XHR a non-existing + // URL will raise an exception here -- hence this ugly try...catch. + try { + xhr.send(null); + } catch (e) { + onFailure(); + } + } + + + /** + * l10n resource parser: + * - reads (async XHR) the l10n resource matching `lang'; + * - imports linked resources (synchronously) when specified; + * - parses the text data (fills `gL10nData' and `gTextData'); + * - triggers success/failure callbacks when done. + * + * @param {string} href + * URL of the l10n resource to parse. + * + * @param {string} lang + * locale (language) to parse. + * + * @param {Function} successCallback + * triggered when the l10n resource has been successully parsed. + * + * @param {Function} failureCallback + * triggered when the an error has occured. + * + * @return {void} + * uses the following global variables: gL10nData, gTextData, gTextProp. + */ + + function parseResource(href, lang, successCallback, failureCallback) { + var baseURL = href.replace(/[^\/]*$/, '') || './'; + + // handle escaped characters (backslashes) in a string + function evalString(text) { + if (text.lastIndexOf('\\') < 0) + return text; + return text.replace(/\\\\/g, '\\') + .replace(/\\n/g, '\n') + .replace(/\\r/g, '\r') + .replace(/\\t/g, '\t') + .replace(/\\b/g, '\b') + .replace(/\\f/g, '\f') + .replace(/\\{/g, '{') + .replace(/\\}/g, '}') + .replace(/\\"/g, '"') + .replace(/\\'/g, "'"); + } + + // parse *.properties text data into an l10n dictionary + function parseProperties(text) { + var dictionary = []; + + // token expressions + var reBlank = /^\s*|\s*$/; + var reComment = /^\s*#|^\s*$/; + var reSection = /^\s*\[(.*)\]\s*$/; + var reImport = /^\s*@import\s+url\((.*)\)\s*$/i; + var reSplit = /^([^=\s]*)\s*=\s*(.+)$/; // TODO: escape EOLs with '\' + + // parse the *.properties file into an associative array + function parseRawLines(rawText, extendedSyntax) { + var entries = rawText.replace(reBlank, '').split(/[\r\n]+/); + var currentLang = '*'; + var genericLang = lang.replace(/-[a-z]+$/i, ''); + var skipLang = false; + var match = ''; + + for (var i = 0; i < entries.length; i++) { + var line = entries[i]; + + // comment or blank line? + if (reComment.test(line)) + continue; + + // the extended syntax supports [lang] sections and @import rules + if (extendedSyntax) { + if (reSection.test(line)) { // section start? + match = reSection.exec(line); + currentLang = match[1]; + skipLang = (currentLang !== '*') && + (currentLang !== lang) && (currentLang !== genericLang); + continue; + } else if (skipLang) { + continue; + } + if (reImport.test(line)) { // @import rule? + match = reImport.exec(line); + loadImport(baseURL + match[1]); // load the resource synchronously + } + } + + // key-value pair + var tmp = line.match(reSplit); + if (tmp && tmp.length == 3) { + dictionary[tmp[1]] = evalString(tmp[2]); + } + } + } + + // import another *.properties file + function loadImport(url) { + xhrLoadText(url, function(content) { + parseRawLines(content, false); // don't allow recursive imports + }, null, false); // load synchronously + } + + // fill the dictionary + parseRawLines(text, true); + return dictionary; + } + + // load and parse l10n data (warning: global variables are used here) + xhrLoadText(href, function(response) { + gTextData += response; // mostly for debug + + // parse *.properties text data into an l10n dictionary + var data = parseProperties(response); + + // find attribute descriptions, if any + for (var key in data) { + var id, prop, index = key.lastIndexOf('.'); + if (index > 0) { // an attribute has been specified + id = key.substring(0, index); + prop = key.substr(index + 1); + } else { // no attribute: assuming text content by default + id = key; + prop = gTextProp; + } + if (!gL10nData[id]) { + gL10nData[id] = {}; + } + gL10nData[id][prop] = data[key]; + } + + // trigger callback + if (successCallback) { + successCallback(); + } + }, failureCallback, gAsyncResourceLoading); + }; + + // load and parse all resources for the specified locale + function loadLocale(lang, callback) { + callback = callback || function _callback() {}; + + clear(); + gLanguage = lang; + + // check all nodes + // and load the resource files + var langLinks = getL10nResourceLinks(); + var langCount = langLinks.length; + if (langCount == 0) { + // we might have a pre-compiled dictionary instead + var dict = getL10nDictionary(); + if (dict && dict.locales && dict.default_locale) { + consoleLog('using the embedded JSON directory, early way out'); + gL10nData = dict.locales[lang] || dict.locales[dict.default_locale]; + callback(); + } else { + consoleLog('no resource to load, early way out'); + } + // early way out + fireL10nReadyEvent(lang); + gReadyState = 'complete'; + return; + } + + // start the callback when all resources are loaded + var onResourceLoaded = null; + var gResourceCount = 0; + onResourceLoaded = function() { + gResourceCount++; + if (gResourceCount >= langCount) { + callback(); + fireL10nReadyEvent(lang); + gReadyState = 'complete'; + } + }; + + // load all resource files + function l10nResourceLink(link) { + var href = link.href; + var type = link.type; + this.load = function(lang, callback) { + var applied = lang; + parseResource(href, lang, callback, function() { + consoleWarn(href + ' not found.'); + applied = ''; + }); + return applied; // return lang if found, an empty string if not found + }; + } + + for (var i = 0; i < langCount; i++) { + var resource = new l10nResourceLink(langLinks[i]); + var rv = resource.load(lang, onResourceLoaded); + if (rv != lang) { // lang not found, used default resource instead + consoleWarn('"' + lang + '" resource not found'); + gLanguage = ''; + } + } + } + + // clear all l10n data + function clear() { + gL10nData = {}; + gTextData = ''; + gLanguage = ''; + // TODO: clear all non predefined macros. + // There's no such macro /yet/ but we're planning to have some... + } + + + /** + * Get rules for plural forms (shared with JetPack), see: + * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p + * + * @param {string} lang + * locale (language) used. + * + * @return {Function} + * returns a function that gives the plural form name for a given integer: + * var fun = getPluralRules('en'); + * fun(1) -> 'one' + * fun(0) -> 'other' + * fun(1000) -> 'other'. + */ + + function getPluralRules(lang) { + var locales2rules = { + 'af': 3, + 'ak': 4, + 'am': 4, + 'ar': 1, + 'asa': 3, + 'az': 0, + 'be': 11, + 'bem': 3, + 'bez': 3, + 'bg': 3, + 'bh': 4, + 'bm': 0, + 'bn': 3, + 'bo': 0, + 'br': 20, + 'brx': 3, + 'bs': 11, + 'ca': 3, + 'cgg': 3, + 'chr': 3, + 'cs': 12, + 'cy': 17, + 'da': 3, + 'de': 3, + 'dv': 3, + 'dz': 0, + 'ee': 3, + 'el': 3, + 'en': 3, + 'eo': 3, + 'es': 3, + 'et': 3, + 'eu': 3, + 'fa': 0, + 'ff': 5, + 'fi': 3, + 'fil': 4, + 'fo': 3, + 'fr': 5, + 'fur': 3, + 'fy': 3, + 'ga': 8, + 'gd': 24, + 'gl': 3, + 'gsw': 3, + 'gu': 3, + 'guw': 4, + 'gv': 23, + 'ha': 3, + 'haw': 3, + 'he': 2, + 'hi': 4, + 'hr': 11, + 'hu': 0, + 'id': 0, + 'ig': 0, + 'ii': 0, + 'is': 3, + 'it': 3, + 'iu': 7, + 'ja': 0, + 'jmc': 3, + 'jv': 0, + 'ka': 0, + 'kab': 5, + 'kaj': 3, + 'kcg': 3, + 'kde': 0, + 'kea': 0, + 'kk': 3, + 'kl': 3, + 'km': 0, + 'kn': 0, + 'ko': 0, + 'ksb': 3, + 'ksh': 21, + 'ku': 3, + 'kw': 7, + 'lag': 18, + 'lb': 3, + 'lg': 3, + 'ln': 4, + 'lo': 0, + 'lt': 10, + 'lv': 6, + 'mas': 3, + 'mg': 4, + 'mk': 16, + 'ml': 3, + 'mn': 3, + 'mo': 9, + 'mr': 3, + 'ms': 0, + 'mt': 15, + 'my': 0, + 'nah': 3, + 'naq': 7, + 'nb': 3, + 'nd': 3, + 'ne': 3, + 'nl': 3, + 'nn': 3, + 'no': 3, + 'nr': 3, + 'nso': 4, + 'ny': 3, + 'nyn': 3, + 'om': 3, + 'or': 3, + 'pa': 3, + 'pap': 3, + 'pl': 13, + 'ps': 3, + 'pt': 3, + 'rm': 3, + 'ro': 9, + 'rof': 3, + 'ru': 11, + 'rwk': 3, + 'sah': 0, + 'saq': 3, + 'se': 7, + 'seh': 3, + 'ses': 0, + 'sg': 0, + 'sh': 11, + 'shi': 19, + 'sk': 12, + 'sl': 14, + 'sma': 7, + 'smi': 7, + 'smj': 7, + 'smn': 7, + 'sms': 7, + 'sn': 3, + 'so': 3, + 'sq': 3, + 'sr': 11, + 'ss': 3, + 'ssy': 3, + 'st': 3, + 'sv': 3, + 'sw': 3, + 'syr': 3, + 'ta': 3, + 'te': 3, + 'teo': 3, + 'th': 0, + 'ti': 4, + 'tig': 3, + 'tk': 3, + 'tl': 4, + 'tn': 3, + 'to': 0, + 'tr': 0, + 'ts': 3, + 'tzm': 22, + 'uk': 11, + 'ur': 3, + 've': 3, + 'vi': 0, + 'vun': 3, + 'wa': 4, + 'wae': 3, + 'wo': 0, + 'xh': 3, + 'xog': 3, + 'yo': 0, + 'zh': 0, + 'zu': 3 + }; + + // utility functions for plural rules methods + function isIn(n, list) { + return list.indexOf(n) !== -1; + } + function isBetween(n, start, end) { + return start <= n && n <= end; + } + + // list of all plural rules methods: + // map an integer to the plural form name to use + var pluralRules = { + '0': function(n) { + return 'other'; + }, + '1': function(n) { + if ((isBetween((n % 100), 3, 10))) + return 'few'; + if (n === 0) + return 'zero'; + if ((isBetween((n % 100), 11, 99))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '2': function(n) { + if (n !== 0 && (n % 10) === 0) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '3': function(n) { + if (n == 1) + return 'one'; + return 'other'; + }, + '4': function(n) { + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '5': function(n) { + if ((isBetween(n, 0, 2)) && n != 2) + return 'one'; + return 'other'; + }, + '6': function(n) { + if (n === 0) + return 'zero'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '7': function(n) { + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '8': function(n) { + if ((isBetween(n, 3, 6))) + return 'few'; + if ((isBetween(n, 7, 10))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '9': function(n) { + if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '10': function(n) { + if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) + return 'few'; + if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19))) + return 'one'; + return 'other'; + }, + '11': function(n) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if ((n % 10) === 0 || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 11, 14))) + return 'many'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '12': function(n) { + if ((isBetween(n, 2, 4))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '13': function(n) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if (n != 1 && (isBetween((n % 10), 0, 1)) || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 12, 14))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '14': function(n) { + if ((isBetween((n % 100), 3, 4))) + return 'few'; + if ((n % 100) == 2) + return 'two'; + if ((n % 100) == 1) + return 'one'; + return 'other'; + }, + '15': function(n) { + if (n === 0 || (isBetween((n % 100), 2, 10))) + return 'few'; + if ((isBetween((n % 100), 11, 19))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '16': function(n) { + if ((n % 10) == 1 && n != 11) + return 'one'; + return 'other'; + }, + '17': function(n) { + if (n == 3) + return 'few'; + if (n === 0) + return 'zero'; + if (n == 6) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '18': function(n) { + if (n === 0) + return 'zero'; + if ((isBetween(n, 0, 2)) && n !== 0 && n != 2) + return 'one'; + return 'other'; + }, + '19': function(n) { + if ((isBetween(n, 2, 10))) + return 'few'; + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '20': function(n) { + if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !( + isBetween((n % 100), 10, 19) || + isBetween((n % 100), 70, 79) || + isBetween((n % 100), 90, 99) + )) + return 'few'; + if ((n % 1000000) === 0 && n !== 0) + return 'many'; + if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92])) + return 'two'; + if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91])) + return 'one'; + return 'other'; + }, + '21': function(n) { + if (n === 0) + return 'zero'; + if (n == 1) + return 'one'; + return 'other'; + }, + '22': function(n) { + if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) + return 'one'; + return 'other'; + }, + '23': function(n) { + if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) + return 'one'; + return 'other'; + }, + '24': function(n) { + if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) + return 'few'; + if (isIn(n, [2, 12])) + return 'two'; + if (isIn(n, [1, 11])) + return 'one'; + return 'other'; + } + }; + + // return a function that gives the plural form name for a given integer + var index = locales2rules[lang.replace(/-.*$/, '')]; + if (!(index in pluralRules)) { + consoleWarn('plural form unknown for [' + lang + ']'); + return function() { return 'other'; }; + } + return pluralRules[index]; + } + + // pre-defined 'plural' macro + gMacros.plural = function(str, param, key, prop) { + var n = parseFloat(param); + if (isNaN(n)) + return str; + + // TODO: support other properties (l20n still doesn't...) + if (prop != gTextProp) + return str; + + // initialize _pluralRules + if (!gMacros._pluralRules) { + gMacros._pluralRules = getPluralRules(gLanguage); + } + var index = '[' + gMacros._pluralRules(n) + ']'; + + // try to find a [zero|one|two] key if it's defined + if (n === 0 && (key + '[zero]') in gL10nData) { + str = gL10nData[key + '[zero]'][prop]; + } else if (n == 1 && (key + '[one]') in gL10nData) { + str = gL10nData[key + '[one]'][prop]; + } else if (n == 2 && (key + '[two]') in gL10nData) { + str = gL10nData[key + '[two]'][prop]; + } else if ((key + index) in gL10nData) { + str = gL10nData[key + index][prop]; + } else if ((key + '[other]') in gL10nData) { + str = gL10nData[key + '[other]'][prop]; + } + + return str; + }; + + + /** + * l10n dictionary functions + */ + + // fetch an l10n object, warn if not found, apply `args' if possible + function getL10nData(key, args) { + var data = gL10nData[key]; + if (!data) { + consoleWarn('#' + key + ' missing for [' + gLanguage + ']'); + } + + /** This is where l10n expressions should be processed. + * The plan is to support C-style expressions from the l20n project; + * until then, only two kinds of simple expressions are supported: + * {[ index ]} and {{ arguments }}. + */ + var rv = {}; + for (var prop in data) { + var str = data[prop]; + str = substIndexes(str, args, key, prop); + str = substArguments(str, args); + rv[prop] = str; + } + return rv; + } + + // replace {[macros]} with their values + function substIndexes(str, args, key, prop) { + var reIndex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)\s*\]\}/; + var reMatch = reIndex.exec(str); + if (!reMatch || !reMatch.length) + return str; + + // an index/macro has been found + // Note: at the moment, only one parameter is supported + var macroName = reMatch[1]; + var paramName = reMatch[2]; + var param; + if (args && paramName in args) { + param = args[paramName]; + } else if (paramName in gL10nData) { + param = gL10nData[paramName]; + } + + // there's no macro parser yet: it has to be defined in gMacros + if (macroName in gMacros) { + var macro = gMacros[macroName]; + str = macro(str, param, key, prop); + } + return str; + } + + // replace {{arguments}} with their values + function substArguments(str, args) { + var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/; + var match = reArgs.exec(str); + while (match) { + if (!match || match.length < 2) + return str; // argument key not found + + var arg = match[1]; + var sub = ''; + if (arg in args) { + sub = args[arg]; + } else if (arg in gL10nData) { + sub = gL10nData[arg][gTextProp]; + } else { + consoleWarn('could not find argument {{' + arg + '}}'); + return str; + } + + str = str.substring(0, match.index) + sub + + str.substr(match.index + match[0].length); + match = reArgs.exec(str); + } + return str; + } + + // translate an HTML element + function translateElement(element) { + var l10n = getL10nAttributes(element); + if (!l10n.id) + return; + + // get the related l10n object + var data = getL10nData(l10n.id, l10n.args); + if (!data) { + consoleWarn('#' + l10n.id + ' missing for [' + gLanguage + ']'); + return; + } + + // translate element (TODO: security checks?) + // for the node content, replace the content of the first child textNode + // and clear other child textNodes + if (data[gTextProp]) { // XXX + if (getChildElementCount(element) === 0) { + element[gTextProp] = data[gTextProp]; + } else { + var children = element.childNodes, + found = false; + for (var i = 0, l = children.length; i < l; i++) { + if (children[i].nodeType === 3 && + /\S/.test(children[i].textContent)) { // XXX + // using nodeValue seems cross-browser + if (found) { + children[i].nodeValue = ''; + } else { + children[i].nodeValue = data[gTextProp]; + found = true; + } + } + } + if (!found) { + consoleWarn('unexpected error, could not translate element content'); + } + } + delete data[gTextProp]; + } + + for (var k in data) { + element[k] = data[k]; + } + } + + // webkit browsers don't currently support 'children' on SVG elements... + function getChildElementCount(element) { + if (element.children) { + return element.children.length; + } + if (typeof element.childElementCount !== 'undefined') { + return element.childElementCount; + } + var count = 0; + for (var i = 0; i < element.childNodes.length; i++) { + count += element.nodeType === 1 ? 1 : 0; + } + return count; + } + + // translate an HTML subtree + function translateFragment(element) { + element = element || document.documentElement; + + // check all translatable children (= w/ a `data-l10n-id' attribute) + var children = getTranslatableChildren(element); + var elementCount = children.length; + for (var i = 0; i < elementCount; i++) { + translateElement(children[i]); + } + + // translate element itself if necessary + translateElement(element); + } + + // Startup & Public API + + function l10nStartup() { + gReadyState = 'interactive'; + consoleLog('loading [' + navigator.language + '] resources, ' + + (gAsyncResourceLoading ? 'asynchronously.' : 'synchronously.')); + + // load the default locale and translate the document if required + if (document.documentElement.lang === navigator.language) { + loadLocale(navigator.language); + } else { + loadLocale(navigator.language, translateFragment); + } + } + + // public API + var l10n = { + start: function() { + if (document.readyState === 'complete' || + document.readyState === 'interactive') { + window.setTimeout(l10nStartup); + } else { + document.addEventListener('DOMContentLoaded', l10nStartup); + } + }, + + // get a localized string + get: function l10n_get(key, args, fallback) { + var data = getL10nData(key, args) || fallback; + if (data) { + return 'textContent' in data ? data.textContent : ''; + } + return '{{' + key + '}}'; + }, + + // get|set the document language and direction + get language() { + return { + // get|set the document language (ISO-639-1) + get code() { return gLanguage; }, + set code(lang) { loadLocale(lang, translateFragment); }, + + // get the direction (ltr|rtl) of the current language + get direction() { + // http://www.w3.org/International/questions/qa-scripts + // Arabic, Hebrew, Farsi, Pashto, Urdu + var rtlList = ['ar', 'he', 'fa', 'ps', 'ur']; + return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr'; + } + }; + }, + + // translate an element or document fragment + translate: translateFragment, + + // get (a clone of) the dictionary for the current locale + get dictionary() { return JSON.parse(JSON.stringify(gL10nData)); }, + + // this can be used to prevent race conditions + get readyState() { return gReadyState; }, + ready: function l10n_ready(callback) { + if (!callback) + return; + + if (gReadyState == 'complete') { + window.setTimeout(callback); + } else { + window.addEventListener('localized', callback); + } + } + }; + + return l10n; +}); diff --git a/activities/Paint.activity/package.json b/activities/Paint.activity/package.json new file mode 100644 index 000000000..bf4bea57d --- /dev/null +++ b/activities/Paint.activity/package.json @@ -0,0 +1,12 @@ +{ + "amd": {}, + "volo": { + "baseUrl": "lib", + "dependencies": { + "sugar-web": "github:sugarlabs/sugar-web/master", + "domReady": "github:requirejs/domReady/2.0.1", + "webL10n": "github:sugarlabs/webL10n/master", + "mustache": "github:janl/mustache.js/0.7.2" + } + } +} \ No newline at end of file diff --git a/activities/Paint.activity/setup.py b/activities/Paint.activity/setup.py new file mode 100644 index 000000000..ad218b212 --- /dev/null +++ b/activities/Paint.activity/setup.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +from sugar3.activity import bundlebuilder + +bundlebuilder.start() diff --git a/activities/Stopwatch.activity/LICENSE b/activities/Stopwatch.activity/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/activities/Stopwatch.activity/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/activities/Stopwatch.activity/activity/activity-icon.svg b/activities/Stopwatch.activity/activity/activity-icon.svg new file mode 100644 index 000000000..5f57582e3 --- /dev/null +++ b/activities/Stopwatch.activity/activity/activity-icon.svg @@ -0,0 +1,83 @@ + + + +]> +image/svg+xml + + + + + + + + + diff --git a/activities/Stopwatch.activity/activity/activity.info b/activities/Stopwatch.activity/activity/activity.info new file mode 100644 index 000000000..ba1a22836 --- /dev/null +++ b/activities/Stopwatch.activity/activity/activity.info @@ -0,0 +1,6 @@ +[Activity] +name = Stopwatch +activity_version = 1 +bundle_id = org.sugarlabs.StopwatchActivity +exec = sugar-activity-web +icon = activity-icon diff --git a/activities/Stopwatch.activity/css/activity.css b/activities/Stopwatch.activity/css/activity.css new file mode 100644 index 000000000..8953e1bf3 --- /dev/null +++ b/activities/Stopwatch.activity/css/activity.css @@ -0,0 +1,120 @@ +#main-toolbar #activity-button { + background-image: url(../activity/activity-icon.svg); +} + +#stopwatch-app { + text-align: center; + margin: 0 55px; +} + +#stopwatch-list { + text-align: left; + margin-top: 11px; + margin-bottom: 11px; +} + +#stopwatch-list li { + position: relative; +} + +#stopwatch-list li p { + line-height: 1.2; +} + +.counter, .marks { + display: inline-block; +} + +.marks { + margin-left: 11px; +} + +.counter { + width: 110px; + text-align: center; + font-weight: bold; + /* font-size: 20px; */ +} + +.buttons-group { + display: inline-block; +} + +.buttons-group button { + border-radius: 0; + margin-left: 0; + margin-right: 0; +} + +.buttons-group button:first-child { + border-radius: 22px 0 0 22px; + margin-right: 0; +} + +.buttons-group button:last-child { + border-radius: 0 22px 22px 0; + margin-left: 0; +} + +button.remove { + position: absolute; + top: 0; + right: 0; + width: 42px; + height: 42px; + background-color: transparent; + background-position: center; + background-size: 22px 22px; + background-repeat: no-repeat; + background-image: url(../icons/list-remove.svg); + border: 0; + border-radius: 0; +} + +#add-stopwatch { + width: 84px; + height: 42px; + background-position: center; + background-size: 22px 22px; + background-repeat: no-repeat; + background-image: url(../icons/list-add.svg); +} + +/* Only for stopwatch */ +.buttons-group button { + margin-top: 5.5px; + height: 33px; + width: 33px; + padding: 0; + background-position: center; + background-size: 22px 22px; + background-repeat: no-repeat; +} + +.buttons-group button:first-child { + padding-left: 11px; +} + +.buttons-group button:last-child { + padding-right: 11px; +} + +.buttons-group button.start { + background-image: url(../icons/media-playback-start.svg); +} + +.buttons-group button.stop { + background-image: url(../icons/media-playback-pause.svg); +} + +.buttons-group button.reset-button { + background-image: url(../icons/media-playback-stop.svg); +} + +.buttons-group button.mark-button { + background-image: url(../icons/edit-mark.svg); +} + +.buttons-group button.clear-marks-button { + background-image: url(../icons/edit-clear.svg); +} diff --git a/activities/Stopwatch.activity/icons/edit-clear.svg b/activities/Stopwatch.activity/icons/edit-clear.svg new file mode 100644 index 000000000..bf2a7ac7d --- /dev/null +++ b/activities/Stopwatch.activity/icons/edit-clear.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Stopwatch.activity/icons/edit-mark.svg b/activities/Stopwatch.activity/icons/edit-mark.svg new file mode 100644 index 000000000..af8fc60ee --- /dev/null +++ b/activities/Stopwatch.activity/icons/edit-mark.svg @@ -0,0 +1,25 @@ + + + +]> + diff --git a/activities/Stopwatch.activity/icons/list-add.svg b/activities/Stopwatch.activity/icons/list-add.svg new file mode 100644 index 000000000..89b7d45bb --- /dev/null +++ b/activities/Stopwatch.activity/icons/list-add.svg @@ -0,0 +1,6 @@ + + +]> + + diff --git a/activities/Stopwatch.activity/icons/list-remove.svg b/activities/Stopwatch.activity/icons/list-remove.svg new file mode 100644 index 000000000..740f03339 --- /dev/null +++ b/activities/Stopwatch.activity/icons/list-remove.svg @@ -0,0 +1,6 @@ + + +]> + + diff --git a/activities/Stopwatch.activity/icons/media-playback-pause.svg b/activities/Stopwatch.activity/icons/media-playback-pause.svg new file mode 100644 index 000000000..aa4a5852e --- /dev/null +++ b/activities/Stopwatch.activity/icons/media-playback-pause.svg @@ -0,0 +1,9 @@ + + +]> + + + + + \ No newline at end of file diff --git a/activities/Stopwatch.activity/icons/media-playback-start.svg b/activities/Stopwatch.activity/icons/media-playback-start.svg new file mode 100644 index 000000000..e2e005178 --- /dev/null +++ b/activities/Stopwatch.activity/icons/media-playback-start.svg @@ -0,0 +1,15 @@ + + +]> + + + + + + + + + + + \ No newline at end of file diff --git a/activities/Stopwatch.activity/icons/media-playback-stop.svg b/activities/Stopwatch.activity/icons/media-playback-stop.svg new file mode 100644 index 000000000..4779fd569 --- /dev/null +++ b/activities/Stopwatch.activity/icons/media-playback-stop.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Stopwatch.activity/index.html b/activities/Stopwatch.activity/index.html new file mode 100644 index 000000000..6561acd93 --- /dev/null +++ b/activities/Stopwatch.activity/index.html @@ -0,0 +1,25 @@ + + + + + + Stopwatch Activity + + + + + + +
        + + +
        +
        +
        +
          + +
          +
          + + + diff --git a/activities/Stopwatch.activity/js/activity.js b/activities/Stopwatch.activity/js/activity.js new file mode 100644 index 000000000..612c871d7 --- /dev/null +++ b/activities/Stopwatch.activity/js/activity.js @@ -0,0 +1,186 @@ +define(function (require) { + var activity = require("sugar-web/activity/activity"); + var mustache = require("mustache"); + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + // Initialize the activity. + activity.setup(); + + var requestAnimationFrame = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + window.msRequestAnimationFrame; + + // Utility to fill a string number with zeros. + function pad(n, width, z) { + z = z || '0'; + width = width || 2; + n = n + ''; + if (n.length >= width) { + return n; + } + else { + return new Array(width - n.length + 1).join(z) + n; + } + } + + function Stopwatch() { + this.elem = document.createElement('li'); + var stopwatchList = document.getElementById('stopwatch-list'); + stopwatchList.appendChild(this.elem); + + this.template = + '

          00:00:00

          ' + + '
          ' + + '' + + '' + + '' + + '' + + '
          ' + + '

          ' + + ''; + + this.elem.innerHTML = mustache.render(this.template, {}); + + this.counterElem = this.elem.querySelector('.counter'); + this.marksElem = this.elem.querySelector('.marks'); + this.running = false; + this.previousTime = Date.now(); + this.tenthsOfSecond = 0; + this.seconds = 0; + this.minutes = 0; + this.marks = []; + + var that = this; + + this.startStopButton = this.elem.querySelector('.start-stop-button'); + this.startStopButton.onclick = function () { + that.onStartStopClicked(); + }; + + this.resetButton = this.elem.querySelector('.reset-button'); + this.resetButton.onclick = function () { + that.onResetClicked(); + }; + + this.markButton = this.elem.querySelector('.mark-button'); + this.markButton.onclick = function () { + that.onMarkClicked(); + }; + + this.clearButton = this.elem.querySelector('.clear-marks-button'); + this.clearButton.onclick = function () { + that.onClearMarksClicked(); + }; + + this.removeButton = this.elem.querySelector('.remove'); + this.removeButton.onclick = function () { + that.onRemoveClicked(); + }; + } + + Stopwatch.prototype.onStartStopClicked = function () { + if (!this.running) { + this.running = true; + this.tick(); + } + else { + this.running = false; + } + this.updateButtons(); + }; + + Stopwatch.prototype.onResetClicked = function () { + this.tenthsOfSecond = 0; + this.seconds = 0; + this.minutes = 0; + if (!this.running) { + this.updateView(); + } + }; + + Stopwatch.prototype.onMarkClicked = function () { + this.marks.push(pad(this.minutes) + ':' + pad(this.seconds) + '.' + + pad(this.tenthsOfSecond)); + this.updateMarks(); + }; + + Stopwatch.prototype.onClearMarksClicked = function () { + this.marks = []; + this.updateMarks(); + }; + + Stopwatch.prototype.onRemoveClicked = function () { + var stopwatchList = document.getElementById('stopwatch-list'); + stopwatchList.removeChild(this.elem); + }; + + Stopwatch.prototype.tick = function () { + if (!this.running) { + return; + } + + var currentTime = Date.now(); + + if ((currentTime - this.previousTime) > 100) { + this.previousTime = currentTime; + this.update(); + this.updateView(); + } + + requestAnimationFrame(this.tick.bind(this)); + }; + + Stopwatch.prototype.update = function () { + this.tenthsOfSecond += 1; + if (this.tenthsOfSecond == 10) { + this.tenthsOfSecond = 0; + this.seconds += 1; + } + if (this.seconds == 60) { + this.seconds = 0; + this.minutes += 1; + } + }; + + Stopwatch.prototype.updateView = function () { + this.counterElem.innerHTML = pad(this.minutes) + ':' + + pad(this.seconds) + '.' + pad(this.tenthsOfSecond); + }; + + Stopwatch.prototype.updateMarks = function () { + this.marksElem.innerHTML = ''; + for (var i = 0; i < this.marks.length; i++) { + this.marksElem.innerHTML += this.marks[i]; + if (i !== (this.marks.length -1)) { + this.marksElem.innerHTML += ' - '; + } + } + }; + + Stopwatch.prototype.updateButtons = function () { + if (this.running) { + this.startStopButton.classList.add("stop"); + this.startStopButton.classList.remove("start"); + } + else { + this.startStopButton.classList.add("start"); + this.startStopButton.classList.remove("stop"); + } + }; + + // Start with five stopwatches. + for (var i = 0; i < 5; i++) { + new Stopwatch(); + } + + // Button to add more stopwatches. + var addButton = document.getElementById('add-stopwatch'); + addButton.onclick = function () { + new Stopwatch(); + }; + }); + +}); diff --git a/activities/Stopwatch.activity/js/loader.js b/activities/Stopwatch.activity/js/loader.js new file mode 100644 index 000000000..01021e981 --- /dev/null +++ b/activities/Stopwatch.activity/js/loader.js @@ -0,0 +1,8 @@ +requirejs.config({ + baseUrl: "lib", + paths: { + activity: "../js" + } +}); + +requirejs(["activity/activity"]); diff --git a/activities/Stopwatch.activity/lib/domReady.js b/activities/Stopwatch.activity/lib/domReady.js new file mode 100644 index 000000000..2b5412209 --- /dev/null +++ b/activities/Stopwatch.activity/lib/domReady.js @@ -0,0 +1,129 @@ +/** + * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/domReady for details + */ +/*jslint */ +/*global require: false, define: false, requirejs: false, + window: false, clearInterval: false, document: false, + self: false, setInterval: false */ + + +define(function () { + 'use strict'; + + var isTop, testDiv, scrollIntervalId, + isBrowser = typeof window !== "undefined" && window.document, + isPageLoaded = !isBrowser, + doc = isBrowser ? document : null, + readyCalls = []; + + function runCallbacks(callbacks) { + var i; + for (i = 0; i < callbacks.length; i += 1) { + callbacks[i](doc); + } + } + + function callReady() { + var callbacks = readyCalls; + + if (isPageLoaded) { + //Call the DOM ready callbacks + if (callbacks.length) { + readyCalls = []; + runCallbacks(callbacks); + } + } + } + + /** + * Sets the page as loaded. + */ + function pageLoaded() { + if (!isPageLoaded) { + isPageLoaded = true; + if (scrollIntervalId) { + clearInterval(scrollIntervalId); + } + + callReady(); + } + } + + if (isBrowser) { + if (document.addEventListener) { + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. + document.addEventListener("DOMContentLoaded", pageLoaded, false); + window.addEventListener("load", pageLoaded, false); + } else if (window.attachEvent) { + window.attachEvent("onload", pageLoaded); + + testDiv = document.createElement('div'); + try { + isTop = window.frameElement === null; + } catch (e) {} + + //DOMContentLoaded approximation that uses a doScroll, as found by + //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, + //but modified by other contributors, including jdalton + if (testDiv.doScroll && isTop && window.external) { + scrollIntervalId = setInterval(function () { + try { + testDiv.doScroll(); + pageLoaded(); + } catch (e) {} + }, 30); + } + } + + //Check if document already complete, and if so, just trigger page load + //listeners. Latest webkit browsers also use "interactive", and + //will fire the onDOMContentLoaded before "interactive" but not after + //entering "interactive" or "complete". More details: + //http://dev.w3.org/html5/spec/the-end.html#the-end + //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded + //Hmm, this is more complicated on further use, see "firing too early" + //bug: https://github.com/requirejs/domReady/issues/1 + //so removing the || document.readyState === "interactive" test. + //There is still a window.onload binding that should get fired if + //DOMContentLoaded is missed. + if (document.readyState === "complete") { + pageLoaded(); + } + } + + /** START OF PUBLIC API **/ + + /** + * Registers a callback for DOM ready. If DOM is already ready, the + * callback is called immediately. + * @param {Function} callback + */ + function domReady(callback) { + if (isPageLoaded) { + callback(doc); + } else { + readyCalls.push(callback); + } + return domReady; + } + + domReady.version = '2.0.1'; + + /** + * Loader Plugin API method + */ + domReady.load = function (name, req, onLoad, config) { + if (config.isBuild) { + onLoad(null); + } else { + domReady(onLoad); + } + }; + + /** END OF PUBLIC API **/ + + return domReady; +}); diff --git a/activities/Stopwatch.activity/lib/mustache.js b/activities/Stopwatch.activity/lib/mustache.js new file mode 100644 index 000000000..932052b4a --- /dev/null +++ b/activities/Stopwatch.activity/lib/mustache.js @@ -0,0 +1,610 @@ +/*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + */ + +/*global define: false*/ + +(function (root, factory) { + if (typeof exports === "object" && exports) { + module.exports = factory; // CommonJS + } else if (typeof define === "function" && define.amd) { + define(factory); // AMD + } else { + root.Mustache = factory; // + + + + + +
          + +
          + + + + +
          +
          + + +
          +
          +
          +
          +
          +
          +
          +
          +
          + +
          + + + + diff --git a/activities/WelcomeWeb.activity/js/activity.js b/activities/WelcomeWeb.activity/js/activity.js new file mode 100644 index 000000000..61814a4a2 --- /dev/null +++ b/activities/WelcomeWeb.activity/js/activity.js @@ -0,0 +1,93 @@ +define(function (require) { + var activity = require("sugar-web/activity/activity"); + var datastore = require("sugar-web/datastore"); + + var model = require("activity/model"); + var view = require("activity/view"); + var controller = require("activity/controller"); + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + var todo; + + // Initialize the activity. + activity.setup(); + + activity.write = function (callback) { + console.log("writing..."); + var jsonData = JSON.stringify(todo.model.items); + this.getDatastoreObject().setDataAsText(jsonData); + this.getDatastoreObject().save(function (error) { + if (error === null) { + console.log("write done."); + } + else { + console.log("write failed."); + } + callback(error); + }); + }; + + // Set up a brand new TODO list + + function Todo() { + this.model = new model.Model(); + this.view = new view.View(); + this.controller = new controller.Controller(this.model, this.view); + } + + todo = new Todo(); + var datastoreObject = activity.getDatastoreObject(); + function onLoaded(error, metadata, data) { + todo.controller.loadItems(JSON.parse(data)); + } + datastoreObject.loadAsText(onLoaded); + + var input = document.getElementById("new-todo"); + input.addEventListener('keypress', function (e) { + if (e.keyCode === todo.controller.ENTER_KEY) { + var success = todo.controller.addItem(e.target.value); + if (success) { + e.target.value = ''; + } + } + }); + + var inputButton = document.getElementById("new-todo-button"); + inputButton.addEventListener('click', function (e) { + var success = todo.controller.addItem(input.value); + if (success) { + input.value = ''; + } + }); + + // Find the model ID of the clicked DOM element + + function lookupId(target) { + var lookup = target; + + while (lookup.nodeName !== 'LI') { + lookup = lookup.parentNode; + } + + return lookup.dataset.id; + } + + var list = document.getElementById("todo-list"); + list.addEventListener('click', function (e) { + var target = e.target; + + if (target.className.indexOf('remove') > -1) { + todo.controller.removeItem(lookupId(target)); + } + + if (target.className.indexOf('toggle') > -1) { + todo.controller.toggleComplete(lookupId(target), target); + } + + }); + + }); + +}); diff --git a/activities/WelcomeWeb.activity/js/activity_custom.js b/activities/WelcomeWeb.activity/js/activity_custom.js new file mode 100644 index 000000000..ab0583c1e --- /dev/null +++ b/activities/WelcomeWeb.activity/js/activity_custom.js @@ -0,0 +1,95 @@ +var Sufijo = ""; +var Actual = 1; +var Maximo = 1; +var Numero = 1; + +function Actualizar() { + if (Actual == 0) {Sufijo = "Init-";Maximo = 1;} + if (Actual == 1) {Sufijo = "1-PrenderPesonalizar-";Maximo = 11;} + if (Actual == 2) {Sufijo = "2-ElFrame-";Maximo = 9;} + if (Actual == 3) {Sufijo = "3-LasVistas-";Maximo = 4;} + if (Actual == 4) {Sufijo = "4-AbrirCerrar-";Maximo = 8;} + if (Actual == 5) {Sufijo = "5-Journal-";Maximo = 6;} + if (Actual == 6) {Sufijo = "6-ApagarXO-";Maximo = 5;} + + $('#help').remove(); + $('#here').after(""); + /* Puntos */ + + $('#points').remove(); + $('#select_0').remove(); + $('#select_1').remove(); + $('#select_2').remove(); + $('#select_3').remove(); + $('#select_4').remove(); + $('#select_5').remove(); + $('#select_6').remove(); + + if (Actual == 0) {txt = "
          "} + + if (Actual == 1) {txt = "
          "} + + if (Actual == 2) {txt = "
          "} + + if (Actual == 3) {txt = "
          "} + + if (Actual == 4) {txt = "
          "} + + if (Actual == 5) {txt = "
          "} + + if (Actual == 6) {txt = "
          "} + + $("#help").after(txt); + + $("#select_0").click(function() {Actual = 0;Numero = 1;}); + $("#select_1").click(function() {Actual = 1;Numero = 1;}); + $("#select_2").click(function() {Actual = 2;Numero = 1;}); + $("#select_3").click(function() {Actual = 3;Numero = 1;}); + $("#select_4").click(function() {Actual = 4;Numero = 1;}); + $("#select_5").click(function() {Actual = 5;Numero = 1;}); + $("#select_6").click(function() {Actual = 6;Numero = 1;}); + + + Numero += 1; + if (Numero > Maximo) { + Actual += 1; + Numero = 1; + if (Actual > 6) { + Actual = 0 + }; + }; + + setTimeout(Actualizar, 1000); +} + +$(document).ready(function() { + $("#unfullscreen").hide(); + $("#prev-bt").click(function() { + Numero = 1; + Actual -= 1; + if (Actual < 0) { + Actual = 6 + }; + }); + + $("#next-bt").click(function() { + Numero = 1; + Actual += 1; + if (Actual > 6) { + Actual = 0 + }; + }); + $("#fullscreen").click(function() { + $(".toolbar").fadeIn('slow'); + $("#canvas").css('top', '0px'); + $("#unfullscreen").show(); + }); + + $("#unfullscreen").click(function() { + $(".toolbar").show() + $("#canvas").css('top', '55px'); + $("#unfullscreen").hide(); + }); +}); +setTimeout(Actualizar, 1000); + diff --git a/activities/WelcomeWeb.activity/js/controller.js b/activities/WelcomeWeb.activity/js/controller.js new file mode 100644 index 000000000..36962ba8a --- /dev/null +++ b/activities/WelcomeWeb.activity/js/controller.js @@ -0,0 +1,43 @@ +define(function (require) { + + var controller = {}; + + // Take a model and view and act as the controller between them. + controller.Controller = function (model, view) { + this.model = model; + this.view = view; + + this.ENTER_KEY = 13; + this.ESCAPE_KEY = 27; + }; + + controller.Controller.prototype.loadItems = function (items) { + this.model.load(items); + var list = document.getElementById("todo-list"); + list.innerHTML = this.view.show(items); + }; + + controller.Controller.prototype.addItem = function (title) { + if (title.trim() === '') { + return false; + } + var item = this.model.create(title); + var list = document.getElementById("todo-list"); + list.innerHTML += this.view.show([item]); + return true; + }; + + controller.Controller.prototype.removeItem = function (id) { + this.model.remove(id); + this.loadItems(this.model.items); + }; + + controller.Controller.prototype.toggleComplete = function (id, checkbox) { + var completed = checkbox.checked ? 1 : 0; + this.model.update(id, {completed: completed}); + this.loadItems(this.model.items); + }; + + return controller; + +}); diff --git a/activities/WelcomeWeb.activity/js/jquery.js b/activities/WelcomeWeb.activity/js/jquery.js new file mode 100644 index 000000000..981a8a0f1 --- /dev/null +++ b/activities/WelcomeWeb.activity/js/jquery.js @@ -0,0 +1,9404 @@ +/*! + * jQuery JavaScript Library v1.7.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Jun 4 13:11:32 UTC 2012 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document, + navigator = window.navigator, + location = window.location; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z]|[0-9])/ig, + rmsPrefix = /^-ms-/, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context ? context.ownerDocument || context : document ); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.7.2", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.add( fn ); + + return this; + }, + + eq: function( i ) { + i = +i; + return i === -1 ? + this.slice( i ) : + this.slice( i, i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.fireWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).off( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery.Callbacks( "once memory" ); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + var xml, tmp; + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction( object ); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type( array ); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array, i ) { + var len; + + if ( array ) { + if ( indexOf ) { + return indexOf.call( array, elem, i ); + } + + len = array.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in array && array[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, pass ) { + var exec, + bulk = key == null, + i = 0, + length = elems.length; + + // Sets many values + if ( key && typeof key === "object" ) { + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); + } + chainable = 1; + + // Sets one value + } else if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = pass === undefined && jQuery.isFunction( value ); + + if ( bulk ) { + // Bulk operations only iterate when executing function values + if ( exec ) { + exec = fn; + fn = function( elem, key, value ) { + return exec.call( jQuery( elem ), value ); + }; + + // Otherwise they run against the entire set + } else { + fn.call( elems, value ); + fn = null; + } + } + + if ( fn ) { + for (; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + } + + chainable = 1; + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +return jQuery; + +})(); + + +// String to Object flags format cache +var flagsCache = {}; + +// Convert String-formatted flags into Object-formatted ones and store in cache +function createFlags( flags ) { + var object = flagsCache[ flags ] = {}, + i, length; + flags = flags.split( /\s+/ ); + for ( i = 0, length = flags.length; i < length; i++ ) { + object[ flags[i] ] = true; + } + return object; +} + +/* + * Create a callback list using the following parameters: + * + * flags: an optional list of space-separated flags that will change how + * the callback list behaves + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible flags: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( flags ) { + + // Convert flags from String-formatted to Object-formatted + // (we check in cache first) + flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + + var // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = [], + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Add one or several callbacks to the list + add = function( args ) { + var i, + length, + elem, + type, + actual; + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + // Inspect recursively + add( elem ); + } else if ( type === "function" ) { + // Add if not in unique mode and callback is not in + if ( !flags.unique || !self.has( elem ) ) { + list.push( elem ); + } + } + } + }, + // Fire callbacks + fire = function( context, args ) { + args = args || []; + memory = !flags.memory || [ context, args ]; + fired = true; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { + memory = true; // Mark as halted + break; + } + } + firing = false; + if ( list ) { + if ( !flags.once ) { + if ( stack && stack.length ) { + memory = stack.shift(); + self.fireWith( memory[ 0 ], memory[ 1 ] ); + } + } else if ( memory === true ) { + self.disable(); + } else { + list = []; + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + var length = list.length; + add( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away, unless previous + // firing was halted (stopOnFalse) + } else if ( memory && memory !== true ) { + firingStart = length; + fire( memory[ 0 ], memory[ 1 ] ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + var args = arguments, + argIndex = 0, + argLength = args.length; + for ( ; argIndex < argLength ; argIndex++ ) { + for ( var i = 0; i < list.length; i++ ) { + if ( args[ argIndex ] === list[ i ] ) { + // Handle firingIndex and firingLength + if ( firing ) { + if ( i <= firingLength ) { + firingLength--; + if ( i <= firingIndex ) { + firingIndex--; + } + } + } + // Remove the element + list.splice( i--, 1 ); + // If we have some unicity property then + // we only need to do this once + if ( flags.unique ) { + break; + } + } + } + } + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + if ( list ) { + var i = 0, + length = list.length; + for ( ; i < length; i++ ) { + if ( fn === list[ i ] ) { + return true; + } + } + } + return false; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory || memory === true ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( stack ) { + if ( firing ) { + if ( !flags.once ) { + stack.push( [ context, args ] ); + } + } else if ( !( flags.once && memory ) ) { + fire( context, args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + + + +var // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + + Deferred: function( func ) { + var doneList = jQuery.Callbacks( "once memory" ), + failList = jQuery.Callbacks( "once memory" ), + progressList = jQuery.Callbacks( "memory" ), + state = "pending", + lists = { + resolve: doneList, + reject: failList, + notify: progressList + }, + promise = { + done: doneList.add, + fail: failList.add, + progress: progressList.add, + + state: function() { + return state; + }, + + // Deprecated + isResolved: doneList.fired, + isRejected: failList.fired, + + then: function( doneCallbacks, failCallbacks, progressCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); + return this; + }, + always: function() { + deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); + return this; + }, + pipe: function( fnDone, fnFail, fnProgress ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ], + progress: [ fnProgress, "notify" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + obj = promise; + } else { + for ( var key in promise ) { + obj[ key ] = promise[ key ]; + } + } + return obj; + } + }, + deferred = promise.promise({}), + key; + + for ( key in lists ) { + deferred[ key ] = lists[ key ].fire; + deferred[ key + "With" ] = lists[ key ].fireWith; + } + + // Handle state + deferred.done( function() { + state = "resolved"; + }, failList.disable, progressList.lock ).fail( function() { + state = "rejected"; + }, doneList.disable, progressList.lock ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = sliceDeferred.call( arguments, 0 ), + i = 0, + length = args.length, + pValues = new Array( length ), + count = length, + pCount = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(), + promise = deferred.promise(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( deferred, args ); + } + }; + } + function progressFunc( i ) { + return function( value ) { + pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + deferred.notifyWith( promise, pValues ); + }; + } + if ( length > 1 ) { + for ( ; i < length; i++ ) { + if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return promise; + } +}); + + + + +jQuery.support = (function() { + + var support, + all, + a, + select, + opt, + input, + fragment, + tds, + events, + eventName, + i, + isSupported, + div = document.createElement( "div" ), + documentElement = document.documentElement; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
          a"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + pixelMargin: true + }; + + // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead + jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat"); + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for ( i in { + submit: 1, + change: 1, + focusin: 1 + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + fragment.removeChild( div ); + + // Null elements to avoid leaks in IE + fragment = select = opt = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, outer, inner, table, td, offsetSupport, + marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight, + paddingMarginBorderVisibility, paddingMarginBorder, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + paddingMarginBorder = "padding:0;margin:0;border:"; + positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;"; + paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;"; + style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;"; + html = "
          " + + "" + + "
          "; + + container = document.createElement("div"); + container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = "
          t
          "; + tds = div.getElementsByTagName( "td" ); + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( window.getComputedStyle ) { + div.innerHTML = ""; + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + if ( typeof div.style.zoom !== "undefined" ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.innerHTML = ""; + div.style.width = div.style.padding = "1px"; + div.style.border = 0; + div.style.overflow = "hidden"; + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = "block"; + div.style.overflow = "visible"; + div.innerHTML = "
          "; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + } + + div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder: ( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + if ( window.getComputedStyle ) { + div.style.marginTop = "1%"; + support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%"; + } + + if ( typeof container.style.zoom !== "undefined" ) { + container.style.zoom = 1; + } + + body.removeChild( container ); + marginDiv = div = container = null; + + jQuery.extend( support, offsetSupport ); + }); + + return support; +})(); + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var privateCache, thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + privateCache = thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if ( isEvents && !thisCache[ name ] ) { + return privateCache.events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ internalKey ] : internalKey; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + // Ensure that `cache` is not a window object #10080 + if ( jQuery.support.deleteExpando || !cache.setInterval ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the cache and need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ internalKey ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + } else { + elem[ internalKey ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var parts, part, attr, name, l, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attr = elem.attributes; + for ( l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + parts = key.split( ".", 2 ); + parts[1] = parts[1] ? "." + parts[1] : ""; + part = parts[1] + "!"; + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + data = this.triggerHandler( "getData" + part, [ parts[0] ] ); + + // Try to fetch any internally stored data first + if ( data === undefined && elem ) { + data = jQuery.data( elem, key ); + data = dataAttr( elem, key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } + + parts[1] = value; + this.each(function() { + var self = jQuery( this ); + + self.triggerHandler( "setData" + part, parts ); + jQuery.data( this, key, value ); + self.triggerHandler( "changeData" + part, parts ); + }); + }, null, value, arguments.length > 1, null, false ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + jQuery.isNumeric( data ) ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery._data( elem, deferDataKey ); + if ( defer && + ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && + ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery._data( elem, queueDataKey ) && + !jQuery._data( elem, markDataKey ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.fire(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = ( type || "fx" ) + "mark"; + jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); + } + }, + + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); + if ( count ) { + jQuery._data( elem, key, count ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + var q; + if ( elem ) { + type = ( type || "fx" ) + "queue"; + q = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + hooks = {}; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + jQuery._data( elem, type + ".run", hooks ); + fn.call( elem, function() { + jQuery.dequeue( elem, type ); + }, hooks ); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue " + type + ".run", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { + count++; + tmp.add( resolve ); + } + } + resolve(); + return defer.promise( object ); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + nodeHook, boolHook, fixSpecified; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classNames, i, l, elem, className, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + classNames = ( value || "" ).split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + className = (" " + elem.className + " ").replace( rclass, " " ); + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[ c ] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var self = jQuery(this), val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, i, max, option, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + i = one ? index : 0; + max = one ? index + 1 : options.length; + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var propName, attrNames, name, l, isBool, + i = 0; + + if ( value && elem.nodeType === 1 ) { + attrNames = value.toLowerCase().split( rspace ); + l = attrNames.length; + + for ( ; i < l; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; + isBool = rboolean.test( name ); + + // See #9699 for explanation of this approach (setting first, then removal) + // Do not do this for boolean attributes (see #10870) + if ( !isBool ) { + jQuery.attr( elem, name, "" ); + } + elem.removeAttribute( getSetAttribute ? name : propName ); + + // Set corresponding property to false for boolean attributes + if ( isBool && propName in elem ) { + elem[ propName ] = false; + } + } + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) +jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true, + coords: true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return ( ret.nodeValue = value + "" ); + } + }; + + // Apply the nodeHook to tabindex + jQuery.attrHooks.tabindex.set = nodeHook.set; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = "" + value ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); + + + + +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /(?:^|\s)hover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function( selector ) { + var quick = rquickIs.exec( selector ); + if ( quick ) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); + } + return quick; + }, + quickIs = function( elem, m ) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) + ); + }, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + quick: selector && quickParse( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, [ "events", "handle" ], true ); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var type = event.type || event, + namespaces = [], + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "!" ) >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf( "." ) >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + old = null; + for ( ; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old && old === elem.ownerDocument ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments, 0 ), + run_all = !event.exclusive && !event.namespace, + special = jQuery.event.special[ event.type ] || {}, + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers that should run if there are delegated events + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + + // Don't process events on disabled elements (#6911, #8165) + if ( cur.disabled !== true ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) + ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; + } + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady + }, + + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !form._submit_attached ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + jQuery.event.simulate( "change", this, event, true ); + } + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + elem._change_attached = true; + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { // && selector != null + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( var type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + expando = "sizcache" + (Math.random() + '').replace('.', ''), + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rReturn = /\r\n/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context, seed ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set, seed ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set, i, len, match, type, left; + + if ( !expr ) { + return []; + } + + for ( i = 0, len = Expr.order.length; i < len; i++ ) { + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + type, found, item, filter, left, + i, pass, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + filter = Expr.filter[ type ]; + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + pass = not ^ found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Utility function for retreiving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +var getText = Sizzle.getText = function( elem ) { + var i, node, + nodeType = elem.nodeType, + ret = ""; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent || innerText for elements + if ( typeof elem.textContent === 'string' ) { + return elem.textContent; + } else if ( typeof elem.innerText === 'string' ) { + // Replace IE's carriage returns + return elem.innerText.replace( rReturn, '' ); + } else { + // Traverse it's children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + } else { + + // If no nodeType, this is expected to be an array + for ( i = 0; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + if ( node.nodeType !== 8 ) { + ret += getText( node ); + } + } + } + return ret; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var first, last, + doneName, parent, cache, + count, diff, + type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + /* falls through */ + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + first = match[2]; + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + doneName = match[0]; + parent = elem.parentNode; + + if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { + count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent[ expando ] = doneName; + } + + diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Sizzle.attr ? + Sizzle.attr( elem, name ) : + Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + !type && Sizzle.attr ? + result != null : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} +// Expose origPOS +// "global" as in regardless of relation to brackets/parens +Expr.match.globalPOS = origPOS; + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

          "; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
          "; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context, seed ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet, seed ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +Sizzle.selectors.attrMap = {}; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.globalPOS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + var ret = this.pushStack( "", "find", selector ), + length, n, r; + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + POS.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array (deprecated as of jQuery 1.7) + if ( jQuery.isArray( selectors ) ) { + var level = 1; + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( i = 0; i < selectors.length; i++ ) { + + if ( jQuery( cur ).is( selectors[ i ] ) ) { + ret.push({ selector: selectors[ i ], elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + + return ret; + } + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call( arguments ).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} + + + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /]", "i"), + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /\/(java|ecma)script/i, + rcleanScript = /^\s*", "" ], + legend: [ 1, "
          ", "
          " ], + thead: [ 1, "", "
          " ], + tr: [ 2, "", "
          " ], + td: [ 3, "", "
          " ], + col: [ 2, "", "
          " ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }, + safeFragment = createSafeFragment( document ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and + + + + + + + + + + + + + + + + + +
          +
          + + +
          +
          + + + diff --git a/js/app.js b/js/app.js new file mode 100644 index 000000000..f2e9b2007 --- /dev/null +++ b/js/app.js @@ -0,0 +1,67 @@ + +// Sugar Libraries (initialized by require) +var iconLib; +var xoPalette; + + +// Constants +var sizeOwner = 100; +var sizeRadius = 200; + + +// Main app class +enyo.kind({ + name: "SugarDesktop", + kind: enyo.Control, + components: [ + {name: "owner", onclick: "changeColor", classes: "owner-icon"}, + {name: "activitybox", components: []} + ], + + // Constructor + create: function() { + this.inherited(arguments); + this.init(); + }, + + // Init desktop + init: function() { + // Clean desktop + var items = []; + enyo.forEach(this.$.activitybox.getControls(), function(item) { items.push(item); }); + for (var i = 0 ; i < items.length ; i++) { items[i].destroy(); }; + + // Get desktop size + var canvas = document.getElementById("canvas"); + var canvas_centery = parseFloat(canvas.offsetHeight)/2.0; + var canvas_centerx = parseFloat(canvas.offsetWidth)/2.0 + + // Draw XO owner + this.$.owner.applyStyle("margin-left", (canvas_centerx-sizeOwner/4)+"px"); + this.$.owner.applyStyle("margin-top", (canvas_centery-sizeOwner/4)+"px"); + + // Draw activity icons; + var activitiesList = preferences.getActivities(); + var base_angle = ((Math.PI*2.0)/parseFloat(activitiesList.length)); + for (var i = 0 ; i < activitiesList.length ; i++) { + var activity = activitiesList[i]; + var angle = base_angle*parseFloat(i); + this.$.activitybox.createComponent( + {kind: "SugarWebActivity", activity: activity, x: canvas_centerx+Math.cos(angle)*sizeRadius, y: canvas_centery+Math.sin(angle)*sizeRadius}, + {owner: this}).render(); + } + }, + + // Render + rendered: function() { + this.inherited(arguments); + iconLib.colorize(this.$.owner.hasNode(), preferences.getColor()); + }, + + // Change color + changeColor: function() { + preferences.nextColor(); + this.init(); + this.render(); + } +}); diff --git a/js/desktop.js b/js/desktop.js new file mode 100644 index 000000000..ec093462f --- /dev/null +++ b/js/desktop.js @@ -0,0 +1,15 @@ + + +// Desktop handling +define(function (require) { + iconLib = require("sugar-web/graphics/icon"); + xoPalette = require("sugar-web/graphics/xocolor"); + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + // Initialize the desktop + var app = new SugarDesktop(); + app.renderInto(document.getElementById("canvas")); + }); + +}); diff --git a/js/loader.js b/js/loader.js new file mode 100644 index 000000000..241851516 --- /dev/null +++ b/js/loader.js @@ -0,0 +1,8 @@ +requirejs.config({ + baseUrl: "lib", + paths: { + activity: "../js" + } +}); + +requirejs(["activity/desktop"]); diff --git a/js/settings.js b/js/settings.js new file mode 100644 index 000000000..6fef1e39c --- /dev/null +++ b/js/settings.js @@ -0,0 +1,83 @@ + + + +// Activity list +var defaultActivities = [ + {id: "org.sugarlabs.Clock", name: "Clock Web", directory: "activities/Clock.activity", icon: "activity/activity-clock.svg", instances: []}, + {id: "org.sugarlabs.ConnectTheDots", name: "Connect the Dots", directory: "activities/ConnecttheDots.activity", icon: "activity/activity-icon.svg", instances: []}, + {id: "org.sugarlabs.GearsActivity", name: "Gears", directory: "activities/Gears.activity", icon: "activity/activity-icon.svg", instances: []}, + {id: "org.sugarlabs.GTDActivity", name: "Get Things Done", directory: "activities/GetThingsDone.activity", icon: "activity/activity-icon.svg", instances: []}, + {id: "org.olpc-france.LOLActivity", name: "LOL", directory: "activities/LastOneLoses.activity", icon: "activity/activity-icon.svg", instances: []}, + {id: "org.sugarlabs.Markdown", name: "Markdown Activity", directory: "activities/Markdown.activity", icon: "activity/activity-icon.svg", instances: []}, + {id: "org.sugarlabs.MemorizeActivity", name: "Memorize Web", directory: "activities/Memorize.activity", icon: "activity/activity-icon.svg", instances: []}, + {id: "org.sugarlabs.PaintActivity", name: "Paint Web", directory: "activities/Paint.activity", icon: "activity/activity-icon.svg", instances: []}, + {id: "org.sugarlabs.StopwatchActivity", name: "Stopwatch Web", directory: "activities/Stopwatch.activity", icon: "activity/activity-icon.svg", instances: []}, + {id: "org.laptop.WelcomeWebActivity", name: "WelcomeWeb", directory: "activities/WelcomeWeb.activity", icon: "activity/welcome-activity.svg", instances: []} +]; + + +// Settings handling +var preferences = new Settings(); +function Settings() { + + // Load settings from local storage + this.load = function() { + var settings = LocalStorage.getValue('settings'); + if (settings == null) + return; + name = settings.name; + color = settings.color; + colorvalue = settings.colorvalue; + activities = settings.activities; + }; + + // Save settings to local storage + this.save = function() { + LocalStorage.setValue('settings', {name: name, color: color, colorvalue: xoPalette.colors[color], activities: activities}); + }; + + // Get properties + this.getName = function() { + return name; + }; + this.getColor = function() { + return xoPalette.colors[color]; + }; + this.getActivities = function() { + return activities; + }; + + // Set properties + this.setName = function(newname) { + name = newname; + }; + this.setColor = function(newcolor) { + if (newcolor >= 0 && newcolor < xoPalette.colors.length) + color = newcolor; + }; + + // Color playing + this.nextColor = function() { + color = (color+1)%xoPalette.colors.length; + }; + + // Run activity + this.runActivity = function(activity) { + activity.instances.push(1); + this.save(); + window.location = activity.directory; + }; + + // Default value + var name = ""; + var color = 22; + var colorvalue = null; + var activities = defaultActivities; + + // Load settings + this.load(); +} + + + + diff --git a/js/storage.js b/js/storage.js new file mode 100644 index 000000000..d02ab8488 --- /dev/null +++ b/js/storage.js @@ -0,0 +1,30 @@ +// HTML5 local storage handling + +LocalStorage = {}; + +// Test if HTML5 storage is available +LocalStorage.test = function() { + return (typeof(Storage)!=="undefined" && typeof(window.localStorage)!=="undefined"); +}; + +// Set a value in the storage +LocalStorage.setValue = function(key, value) { + if (this.test()) { + try { + window.localStorage.setItem(key, JSON.stringify(value)); + } catch(err) { + } + } +}; + +// Get a value in the storage +LocalStorage.getValue = function(key) { + if (this.test()) { + try { + return JSON.parse(window.localStorage.getItem(key)); + } catch(err) { + return null; + } + } + return null; +}; \ No newline at end of file diff --git a/js/webactivity.js b/js/webactivity.js new file mode 100644 index 000000000..50e573a6b --- /dev/null +++ b/js/webactivity.js @@ -0,0 +1,51 @@ +// Class for a Sugar Web activity +enyo.kind({ + name: "SugarWebActivity", + kind: enyo.Control, + published: { activity: null, size: 55, x: 0, y: 0 }, + classes: "web-activity", + components: [ + { name: "icon", onclick: "runActivity", classes: "web-activity-icon"} + ], + + // Constructor + create: function() { + this.inherited(arguments); + this.iconChanged(); + this.sizeChanged(); + this.xChanged(); + this.yChanged(); + }, + + // Render + rendered: function() { + this.inherited(arguments); + if (this.activity.instances.length > 0) + iconLib.colorize(this.$.icon.hasNode(), preferences.getColor()); + }, + + // Property changed + xChanged: function() { + if (this.x != -1) this.applyStyle("margin-left", this.x+"px"); + }, + + yChanged: function() { + if (this.y != -1) this.applyStyle("margin-top", this.y+"px"); + }, + + iconChanged: function() { + this.$.icon.applyStyle("background-image", "url(" + this.activity.directory+"/"+this.activity.icon + ")"); + }, + + sizeChanged: function() { + this.$.icon.applyStyle("width", this.size+"px"); + this.$.icon.applyStyle("height", this.size+"px"); + this.$.icon.applyStyle("background-size", this.size+"px "+this.size+"px"); + }, + + // Run activity + runActivity: function() { + // Save context then run the activity in the context + preferences.runActivity(this.activity); + } +}); \ No newline at end of file diff --git a/lib/domReady.js b/lib/domReady.js new file mode 100644 index 000000000..2b5412209 --- /dev/null +++ b/lib/domReady.js @@ -0,0 +1,129 @@ +/** + * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/domReady for details + */ +/*jslint */ +/*global require: false, define: false, requirejs: false, + window: false, clearInterval: false, document: false, + self: false, setInterval: false */ + + +define(function () { + 'use strict'; + + var isTop, testDiv, scrollIntervalId, + isBrowser = typeof window !== "undefined" && window.document, + isPageLoaded = !isBrowser, + doc = isBrowser ? document : null, + readyCalls = []; + + function runCallbacks(callbacks) { + var i; + for (i = 0; i < callbacks.length; i += 1) { + callbacks[i](doc); + } + } + + function callReady() { + var callbacks = readyCalls; + + if (isPageLoaded) { + //Call the DOM ready callbacks + if (callbacks.length) { + readyCalls = []; + runCallbacks(callbacks); + } + } + } + + /** + * Sets the page as loaded. + */ + function pageLoaded() { + if (!isPageLoaded) { + isPageLoaded = true; + if (scrollIntervalId) { + clearInterval(scrollIntervalId); + } + + callReady(); + } + } + + if (isBrowser) { + if (document.addEventListener) { + //Standards. Hooray! Assumption here that if standards based, + //it knows about DOMContentLoaded. + document.addEventListener("DOMContentLoaded", pageLoaded, false); + window.addEventListener("load", pageLoaded, false); + } else if (window.attachEvent) { + window.attachEvent("onload", pageLoaded); + + testDiv = document.createElement('div'); + try { + isTop = window.frameElement === null; + } catch (e) {} + + //DOMContentLoaded approximation that uses a doScroll, as found by + //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, + //but modified by other contributors, including jdalton + if (testDiv.doScroll && isTop && window.external) { + scrollIntervalId = setInterval(function () { + try { + testDiv.doScroll(); + pageLoaded(); + } catch (e) {} + }, 30); + } + } + + //Check if document already complete, and if so, just trigger page load + //listeners. Latest webkit browsers also use "interactive", and + //will fire the onDOMContentLoaded before "interactive" but not after + //entering "interactive" or "complete". More details: + //http://dev.w3.org/html5/spec/the-end.html#the-end + //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded + //Hmm, this is more complicated on further use, see "firing too early" + //bug: https://github.com/requirejs/domReady/issues/1 + //so removing the || document.readyState === "interactive" test. + //There is still a window.onload binding that should get fired if + //DOMContentLoaded is missed. + if (document.readyState === "complete") { + pageLoaded(); + } + } + + /** START OF PUBLIC API **/ + + /** + * Registers a callback for DOM ready. If DOM is already ready, the + * callback is called immediately. + * @param {Function} callback + */ + function domReady(callback) { + if (isPageLoaded) { + callback(doc); + } else { + readyCalls.push(callback); + } + return domReady; + } + + domReady.version = '2.0.1'; + + /** + * Loader Plugin API method + */ + domReady.load = function (name, req, onLoad, config) { + if (config.isBuild) { + onLoad(null); + } else { + domReady(onLoad); + } + }; + + /** END OF PUBLIC API **/ + + return domReady; +}); diff --git a/lib/enyo/enyo.css b/lib/enyo/enyo.css new file mode 100644 index 000000000..6d05291ff --- /dev/null +++ b/lib/enyo/enyo.css @@ -0,0 +1,181 @@ + +/* ../source/dom/dom.css */ + +/* things we always want */ +body { + font-family: 'Helvetica Neue', 'Nimbus Sans L', Arial, sans-serif; +} + +/* allow hw-accelerated scrolling on platforms that support it */ +body.webkitOverflowScrolling { + -webkit-overflow-scrolling: touch; +} + +/* for apps */ +.enyo-document-fit { + margin: 0; + height: 100%; + /* note: giving html overflow: auto is odd and was only ever done to avoid duplication + however, android 4.04 sometimes does not hide nodes when their display is set to none + if document is overflow auto. + */ + position: relative; +} + +.enyo-body-fit { + margin: 0; + height: 100%; + /* helps prevent ios page scroll */ + overflow: auto; + position: relative; +} + +.enyo-no-touch-action { + -ms-touch-action: none; +} + +/* reset */ + +button { + font-size: inherit; + font-family: inherit; +} +button::-moz-focus-inner { + border: 0; + padding: 0; +} + +/* user selection */ + +.enyo-unselectable { + cursor: default; + -ms-user-select: none; + -webkit-user-select: none; + -moz-user-select: -moz-none; + user-select: none; +} + +.enyo-unselectable::selection, .enyo-unselectable ::selection { + color: transparent; +} + +.enyo-selectable { + cursor: auto; + -ms-user-select: element; + -webkit-user-select: text; + -moz-user-select: text; + user-select: text; +} + +.enyo-selectable::selection, .enyo-selectable ::selection { + background: #3297FD; + color: #FFF; +} + +/* layout */ + +body .enyo-fit { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; +} + +.enyo-clip { + overflow: hidden; +} + +.enyo-border-box { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* compositing */ + +.enyo-composite { + -webkit-transform: translateZ(0); + -moz-transform: translateZ(0); + -ms-transform: translateZ(0); + -o-transform: translateZ(0); + transform: translateZ(0); +} + + +/* ../source/touch/Thumb.css */ + +.enyo-thumb { + position: absolute; + -moz-box-sizing: border-box; + box-sizing: border-box; + border-radius: 4px; + background: #333; + border: 1px solid #666; + opacity: 0.75; + z-index: 1; +} + +.enyo-vthumb { + top: 0; + right: 2px; + width: 4px; +} + +.enyo-hthumb { + left: 0; + bottom: 2px; + height: 4px; +} + + +/* ../source/touch/Scroller.css */ + +.enyo-scroller { + position: relative; +} + +.enyo-fit.enyo-scroller { + position: absolute; +} + +.enyo-touch-scroller { + overflow: hidden; +} + +.enyo-touch-strategy-container { + overflow: hidden; +} + +.enyo-scrollee-fit { + height: 100%; +} + +/* ../source/ui/ui.css */ + +.enyo-inline, .enyo-tool-decorator { + display: inline-block; +} + +.enyo-children-inline > *, .enyo-tool-decorator > * { + display: inline-block; +} + +.enyo-children-middle > *, .enyo-tool-decorator > * { + vertical-align: middle; +} + +.enyo-positioned { + position: relative; +} + +.enyo-fill { + position: relative; + width: 100%; + height: 100%; +} + +.enyo-popup { + position: absolute; + z-index: 10; +} diff --git a/lib/enyo/enyo.js b/lib/enyo/enyo.js new file mode 100644 index 000000000..d15030ffd --- /dev/null +++ b/lib/enyo/enyo.js @@ -0,0 +1,4306 @@ + +// enyo.js + +(function() { +var e = "enyo.js"; +enyo = window.enyo || {}, enyo.locateScript = function(e) { +var t = document.getElementsByTagName("script"); +for (var n = t.length - 1, r, i, s = e.length; n >= 0 && (r = t[n]); n--) if (!r.located) { +i = r.getAttribute("src") || ""; +if (i.slice(-s) == e) return r.located = !0, { +path: i.slice(0, Math.max(0, i.lastIndexOf("/"))), +node: r +}; +} +}, enyo.args = enyo.args || {}; +var t = enyo.locateScript(e); +if (t) { +enyo.args.root = (enyo.args.root || t.path).replace("/source", ""); +for (var n = 0, r = t.node.attributes.length, i; n < r && (i = t.node.attributes.item(n)); n++) enyo.args[i.nodeName] = i.value; +} +})(); + +// ../../loader.js + +(function() { +enyo = window.enyo || {}, enyo.pathResolverFactory = function() { +this.paths = {}; +}, enyo.pathResolverFactory.prototype = { +addPath: function(e, t) { +return this.paths[e] = t; +}, +addPaths: function(e) { +if (e) for (var t in e) this.addPath(t, e[t]); +}, +includeTrailingSlash: function(e) { +return e && e.slice(-1) !== "/" ? e + "/" : e; +}, +rewritePattern: /\$([^\/\\]*)(\/)?/g, +rewrite: function(e) { +var t, n = this.includeTrailingSlash, r = this.paths, i = function(e, i) { +return t = !0, n(r[i]) || ""; +}, s = e; +do t = !1, s = s.replace(this.rewritePattern, i); while (t); +return s; +} +}, enyo.path = new enyo.pathResolverFactory, enyo.loaderFactory = function(e, t) { +this.machine = e, this.packages = [], this.modules = [], this.sheets = [], this.stack = [], this.pathResolver = t || enyo.path, this.packageName = "", this.packageFolder = "", this.finishCallbacks = {}; +}, enyo.loaderFactory.prototype = { +verbose: !1, +loadScript: function(e, t, n) { +this.machine.script(e, t, n); +}, +loadSheet: function(e) { +this.machine.sheet(e); +}, +loadPackage: function(e) { +this.machine.script(e); +}, +report: function() {}, +load: function() { +this.more({ +index: 0, +depends: arguments || [] +}); +}, +more: function(e) { +if (e && this.continueBlock(e)) return; +var t = this.stack.pop(); +t ? (enyo.runtimeLoading && e.failed && (t.failed = t.failed || [], t.failed.push.apply(t.failed, e.failed)), this.verbose && console.groupEnd("* finish package (" + (t.packageName || "anon") + ")"), this.packageFolder = t.folder, this.packageName = "", this.more(t)) : this.finish(e); +}, +finish: function(e) { +this.packageFolder = "", this.verbose && console.log("-------------- fini"); +for (var t in this.finishCallbacks) this.finishCallbacks[t] && (this.finishCallbacks[t](e), this.finishCallbacks[t] = null); +}, +continueBlock: function(e) { +while (e.index < e.depends.length) { +var t = e.depends[e.index++]; +if (t) if (typeof t == "string") { +if (this.require(t, e)) return !0; +} else this.pathResolver.addPaths(t); +} +}, +require: function(e, t) { +var n = this.pathResolver.rewrite(e), r = this.getPathPrefix(e); +n = r + n; +if (n.slice(-4) != ".css" && n.slice(-5) != ".less") return n.slice(-3) == ".js" && n.slice(-10) != "package.js" ? (this.verbose && console.log("+ module: [" + r + "][" + e + "]"), this.requireScript(e, n, t)) : (this.requirePackage(n, t), !0); +this.verbose && console.log("+ stylesheet: [" + r + "][" + e + "]"), this.requireStylesheet(n); +}, +getPathPrefix: function(e) { +var t = e.slice(0, 1); +return t != "/" && t != "\\" && t != "$" && !/^https?:/i.test(e) ? this.packageFolder : ""; +}, +requireStylesheet: function(e) { +this.sheets.push(e), this.loadSheet(e); +}, +requireScript: function(e, t, n) { +this.modules.push({ +packageName: this.packageName, +rawPath: e, +path: t +}); +if (enyo.runtimeLoading) { +var r = this, i = function() { +r.more(n); +}, s = function() { +n.failed = n.failed || [], n.failed.push(t), r.more(n); +}; +this.loadScript(t, i, s); +} else this.loadScript(t); +return enyo.runtimeLoading; +}, +decodePackagePath: function(e) { +var t = "", n = "", r = "", i = "package.js", s = e.replace(/\\/g, "/").replace(/\/\//g, "/").replace(/:\//, "://").split("/"), o, u; +if (s.length) { +var a = s.pop() || s.pop() || ""; +a.slice(-i.length) !== i ? s.push(a) : i = a, r = s.join("/"), r = r ? r + "/" : "", i = r + i; +for (o = s.length - 1; o >= 0; o--) if (s[o] == "source") { +s.splice(o, 1); +break; +} +n = s.join("/"); +for (o = s.length - 1; u = s[o]; o--) if (u == "lib" || u == "enyo") { +s = s.slice(o + 1); +break; +} +for (o = s.length - 1; u = s[o]; o--) (u == ".." || u == ".") && s.splice(o, 1); +t = s.join("-"); +} +return { +alias: t, +target: n, +folder: r, +manifest: i +}; +}, +aliasPackage: function(e) { +var t = this.decodePackagePath(e); +this.manifest = t.manifest, t.alias && (this.pathResolver.addPath(t.alias, t.target), this.packageName = t.alias, this.packages.push({ +name: t.alias, +folder: t.folder +})), this.packageFolder = t.folder; +}, +requirePackage: function(e, t) { +t.folder = this.packageFolder, this.aliasPackage(e), t.packageName = this.packageName, this.stack.push(t), this.report("loading package", this.packageName), this.verbose && console.group("* start package [" + this.packageName + "]"), this.loadPackage(this.manifest); +} +}; +})(); + +// boot.js + +enyo.execUnsafeLocalFunction = function(e) { +typeof MSApp == "undefined" ? e() : MSApp.execUnsafeLocalFunction(e); +}, enyo.machine = { +sheet: function(e) { +var t = "text/css", n = "stylesheet", r = e.slice(-5) == ".less"; +r && (window.less ? (t = "text/less", n = "stylesheet/less") : e = e.slice(0, e.length - 4) + "css"); +var i; +enyo.runtimeLoading || r ? (i = document.createElement("link"), i.href = e, i.media = "screen", i.rel = n, i.type = t, document.getElementsByTagName("head")[0].appendChild(i)) : (i = function() { +document.write(''); +}, enyo.execUnsafeLocalFunction(i)), r && window.less && (less.sheets.push(i), enyo.loader.finishCallbacks.lessRefresh || (enyo.loader.finishCallbacks.lessRefresh = function() { +less.refresh(!0); +})); +}, +script: function(e, t, n) { +if (!enyo.runtimeLoading) document.write('