From d9e777a83fec1427bc31aa5d3e9ed991963c4fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lionel=20Lask=C3=A9?= Date: Sat, 10 Jan 2015 22:19:18 +0100 Subject: [PATCH] Add Puneet Cordova test activity --- activities.json | 1 + activities/Cordova.activity/LICENSE | 202 ++ .../activity/accelerometer.svg | 34 + .../activity/activity-icon.svg | 11 + .../activity/activity-icon1.svg | 26 + .../Cordova.activity/activity/activity.info | 6 + .../Cordova.activity/activity/camera.svg | 27 + .../Cordova.activity/activity/device.svg | 19 + .../Cordova.activity/activity/dialog.svg | 19 + .../activity/globalization.svg | 50 + .../Cordova.activity/activity/network.svg | 53 + activities/Cordova.activity/cordova.js | 1729 ++++++++++++++ .../Cordova.activity/cordova_plugins.js | 123 + activities/Cordova.activity/css/activity.css | 32 + activities/Cordova.activity/css/iframe.css | 10 + activities/Cordova.activity/css/index.css | 144 ++ .../Cordova.activity/img/accelerometer.svg | 34 + activities/Cordova.activity/img/camera.svg | 27 + activities/Cordova.activity/img/device.svg | 19 + activities/Cordova.activity/img/dialog.svg | 19 + .../Cordova.activity/img/globalization.svg | 50 + activities/Cordova.activity/img/logo.png | Bin 0 -> 21814 bytes activities/Cordova.activity/img/network.svg | 53 + activities/Cordova.activity/index.html | 321 +++ activities/Cordova.activity/js/activity.js | 12 + activities/Cordova.activity/js/index.js | 68 + activities/Cordova.activity/js/loader.js | 8 + activities/Cordova.activity/lib/domReady.js | 129 ++ activities/Cordova.activity/lib/require.js | 2000 +++++++++++++++++ .../Cordova.activity/lib/sugar-web/LICENSE | 202 ++ .../Cordova.activity/lib/sugar-web/README.md | 7 + .../lib/sugar-web/activity/activity.js | 133 ++ .../lib/sugar-web/activity/shortcut.js | 60 + .../Cordova.activity/lib/sugar-web/bus.js | 14 + .../lib/sugar-web/bus/sugarizer.js | 77 + .../lib/sugar-web/bus/sugaros.js | 223 ++ .../lib/sugar-web/datastore.js | 14 + .../lib/sugar-web/datastore/sugarizer.js | 213 ++ .../lib/sugar-web/datastore/sugaros.js | 224 ++ .../lib/sugar-web/dictstore.js | 68 + .../Cordova.activity/lib/sugar-web/env.js | 82 + .../lib/sugar-web/graphics/README.md | 40 + .../sugar-web/graphics/activitypalette.html | 9 + .../lib/sugar-web/graphics/activitypalette.js | 62 + .../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 | 587 +++++ .../lib/sugar-web/graphics/grid.js | 57 + .../lib/sugar-web/graphics/icon.js | 89 + .../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 | 53 + .../lib/sugar-web/graphics/palette.js | 182 ++ .../sugar-web/graphics/radiobuttonsgroup.js | 62 + .../lib/sugar-web/graphics/xocolor.js | 731 ++++++ .../lib/sugar-web/package.json | 10 + .../lib/sugar-web/presence.js | 254 +++ .../test/functional/datastoreSpec.js | 253 +++ .../test/functional/toolkitContractSpec.js | 31 + .../lib/sugar-web/test/graphics/iconSpec.js | 59 + .../test/graphics/menupaletteSpec.js | 78 + .../sugar-web/test/graphics/paletteSpec.js | 36 + .../test/graphics/radiobuttonsgroupSpec.js | 97 + .../lib/sugar-web/test/karma-shared.conf.js | 75 + .../lib/sugar-web/test/karma-unit.conf.js | 19 + .../lib/sugar-web/test/karma.conf.js | 16 + .../lib/sugar-web/test/loader.js | 21 + .../lib/sugar-web/test/unit/busSpec.js | 156 ++ .../lib/sugar-web/test/unit/datastoreSpec.js | 32 + .../lib/sugar-web/test/unit/dictstoreSpec.js | 54 + .../lib/sugar-web/test/unit/envSpec.js | 137 ++ activities/Cordova.activity/lib/text.js | 386 ++++ activities/Cordova.activity/lib/webL10n.js | 1029 +++++++++ activities/Cordova.activity/package.json | 13 + activities/Cordova.activity/setup.py | 5 + activities/Cordova.activity/toolbar.html | 27 + 98 files changed, 12488 insertions(+) create mode 100644 activities/Cordova.activity/LICENSE create mode 100644 activities/Cordova.activity/activity/accelerometer.svg create mode 100644 activities/Cordova.activity/activity/activity-icon.svg create mode 100644 activities/Cordova.activity/activity/activity-icon1.svg create mode 100644 activities/Cordova.activity/activity/activity.info create mode 100644 activities/Cordova.activity/activity/camera.svg create mode 100644 activities/Cordova.activity/activity/device.svg create mode 100644 activities/Cordova.activity/activity/dialog.svg create mode 100644 activities/Cordova.activity/activity/globalization.svg create mode 100644 activities/Cordova.activity/activity/network.svg create mode 100644 activities/Cordova.activity/cordova.js create mode 100644 activities/Cordova.activity/cordova_plugins.js create mode 100644 activities/Cordova.activity/css/activity.css create mode 100644 activities/Cordova.activity/css/iframe.css create mode 100644 activities/Cordova.activity/css/index.css create mode 100644 activities/Cordova.activity/img/accelerometer.svg create mode 100644 activities/Cordova.activity/img/camera.svg create mode 100644 activities/Cordova.activity/img/device.svg create mode 100644 activities/Cordova.activity/img/dialog.svg create mode 100644 activities/Cordova.activity/img/globalization.svg create mode 100644 activities/Cordova.activity/img/logo.png create mode 100644 activities/Cordova.activity/img/network.svg create mode 100644 activities/Cordova.activity/index.html create mode 100644 activities/Cordova.activity/js/activity.js create mode 100644 activities/Cordova.activity/js/index.js create mode 100644 activities/Cordova.activity/js/loader.js create mode 100644 activities/Cordova.activity/lib/domReady.js create mode 100644 activities/Cordova.activity/lib/require.js create mode 100644 activities/Cordova.activity/lib/sugar-web/LICENSE create mode 100644 activities/Cordova.activity/lib/sugar-web/README.md create mode 100644 activities/Cordova.activity/lib/sugar-web/activity/activity.js create mode 100644 activities/Cordova.activity/lib/sugar-web/activity/shortcut.js create mode 100644 activities/Cordova.activity/lib/sugar-web/bus.js create mode 100644 activities/Cordova.activity/lib/sugar-web/bus/sugarizer.js create mode 100644 activities/Cordova.activity/lib/sugar-web/bus/sugaros.js create mode 100644 activities/Cordova.activity/lib/sugar-web/datastore.js create mode 100644 activities/Cordova.activity/lib/sugar-web/datastore/sugarizer.js create mode 100644 activities/Cordova.activity/lib/sugar-web/datastore/sugaros.js create mode 100644 activities/Cordova.activity/lib/sugar-web/dictstore.js create mode 100644 activities/Cordova.activity/lib/sugar-web/env.js create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/README.md create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/activitypalette.html create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/activitypalette.js create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-200dpi.css create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-200dpi.less create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-96dpi.css create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-96dpi.less create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/css/sugar.less create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/grid.js create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icon.js create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/menupalette.html create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/menupalette.js create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/palette.js create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/radiobuttonsgroup.js create mode 100644 activities/Cordova.activity/lib/sugar-web/graphics/xocolor.js create mode 100644 activities/Cordova.activity/lib/sugar-web/package.json create mode 100644 activities/Cordova.activity/lib/sugar-web/presence.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/functional/datastoreSpec.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/functional/toolkitContractSpec.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/graphics/iconSpec.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/graphics/menupaletteSpec.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/graphics/paletteSpec.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/graphics/radiobuttonsgroupSpec.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/karma-shared.conf.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/karma-unit.conf.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/karma.conf.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/loader.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/unit/busSpec.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/unit/datastoreSpec.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/unit/dictstoreSpec.js create mode 100644 activities/Cordova.activity/lib/sugar-web/test/unit/envSpec.js create mode 100644 activities/Cordova.activity/lib/text.js create mode 100644 activities/Cordova.activity/lib/webL10n.js create mode 100644 activities/Cordova.activity/package.json create mode 100644 activities/Cordova.activity/setup.py create mode 100644 activities/Cordova.activity/toolbar.html diff --git a/activities.json b/activities.json index 0efd0107a..d3c5ba82d 100644 --- a/activities.json +++ b/activities.json @@ -3,6 +3,7 @@ {"id": "org.sugarlabs.ChatPrototype", "name": "ChatPrototype", "version": 1, "directory": "activities/ChatPrototype.activity", "icon": "activity/activity-icon.svg", "favorite": false, "activityId": null}, {"id": "org.sugarlabs.Clock", "name": "Clock Web", "version": 1, "directory": "activities/Clock.activity", "icon": "activity/activity-clock.svg", "favorite": true, "activityId": null}, {"id": "org.sugarlabs.ConnectTheDots", "name": "Connect the Dots", "version": 1, "directory": "activities/ConnecttheDots.activity", "icon": "activity/activity-icon.svg", "favorite": false, "activityId": null}, + {"id": "io.cordova.all_in_one_plugin_sample", "name": "Cordova", "version": 1, "directory": "activities/Cordova.activity", "icon": "activity/activity-icon.svg", "favorite": false, "activityId": null}, {"id": "org.olpcfrance.FoodChain", "name": "FoodChain", "version": 4, "directory": "activities/FoodChain.activity", "icon": "activity/activity-icon.svg", "favorite": true, "activityId": null}, {"id": "org.sugarlabs.GearsActivity", "name": "Gears", "version": 6, "directory": "activities/Gears.activity", "icon": "activity/activity-icon.svg", "favorite": true, "activityId": null}, {"id": "org.sugarlabs.GTDActivity", "name": "Get Things Done", "version": 1, "directory": "activities/GetThingsDone.activity", "icon": "activity/activity-icon.svg", "favorite": true, "activityId": null}, diff --git a/activities/Cordova.activity/LICENSE b/activities/Cordova.activity/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/activities/Cordova.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/Cordova.activity/activity/accelerometer.svg b/activities/Cordova.activity/activity/accelerometer.svg new file mode 100644 index 000000000..66df4e2b2 --- /dev/null +++ b/activities/Cordova.activity/activity/accelerometer.svg @@ -0,0 +1,34 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + diff --git a/activities/Cordova.activity/activity/activity-icon.svg b/activities/Cordova.activity/activity/activity-icon.svg new file mode 100644 index 000000000..1605a49e4 --- /dev/null +++ b/activities/Cordova.activity/activity/activity-icon.svg @@ -0,0 +1,11 @@ + + + +]> + + + + + + diff --git a/activities/Cordova.activity/activity/activity-icon1.svg b/activities/Cordova.activity/activity/activity-icon1.svg new file mode 100644 index 000000000..8da7c63ba --- /dev/null +++ b/activities/Cordova.activity/activity/activity-icon1.svg @@ -0,0 +1,26 @@ + + +]> + + + + + + + + + + + + + + diff --git a/activities/Cordova.activity/activity/activity.info b/activities/Cordova.activity/activity/activity.info new file mode 100644 index 000000000..73b002059 --- /dev/null +++ b/activities/Cordova.activity/activity/activity.info @@ -0,0 +1,6 @@ +[Activity] +name = Cordova +activity_version = 1 +bundle_id = io.cordova.all_in_one_plugin_sample +exec = sugar-activity-web +icon = activity-icon diff --git a/activities/Cordova.activity/activity/camera.svg b/activities/Cordova.activity/activity/camera.svg new file mode 100644 index 000000000..13f5932b3 --- /dev/null +++ b/activities/Cordova.activity/activity/camera.svg @@ -0,0 +1,27 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + diff --git a/activities/Cordova.activity/activity/device.svg b/activities/Cordova.activity/activity/device.svg new file mode 100644 index 000000000..bc7dec5c8 --- /dev/null +++ b/activities/Cordova.activity/activity/device.svg @@ -0,0 +1,19 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/activities/Cordova.activity/activity/dialog.svg b/activities/Cordova.activity/activity/dialog.svg new file mode 100644 index 000000000..66311bd58 --- /dev/null +++ b/activities/Cordova.activity/activity/dialog.svg @@ -0,0 +1,19 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/activities/Cordova.activity/activity/globalization.svg b/activities/Cordova.activity/activity/globalization.svg new file mode 100644 index 000000000..cfa1aba8a --- /dev/null +++ b/activities/Cordova.activity/activity/globalization.svg @@ -0,0 +1,50 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + diff --git a/activities/Cordova.activity/activity/network.svg b/activities/Cordova.activity/activity/network.svg new file mode 100644 index 000000000..6015a5949 --- /dev/null +++ b/activities/Cordova.activity/activity/network.svg @@ -0,0 +1,53 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + diff --git a/activities/Cordova.activity/cordova.js b/activities/Cordova.activity/cordova.js new file mode 100644 index 000000000..65750642e --- /dev/null +++ b/activities/Cordova.activity/cordova.js @@ -0,0 +1,1729 @@ +// Platform: sugar +// 3.4.0 +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +*/ +;(function() { +var CORDOVA_JS_BUILD_LABEL = '3.4.0'; +// file: src/scripts/require.js + +/*jshint -W079 */ +/*jshint -W020 */ + +var require, + define; + +(function () { + var modules = {}, + // Stack of moduleIds currently being built. + requireStack = [], + // Map of module ID -> index into requireStack of modules currently being built. + inProgressModules = {}, + SEPARATOR = "."; + + + + function build(module) { + console.log("module="+JSON.stringify(module)); + var factory = module.factory, + localRequire = function (id) { + console.log("id="+JSON.stringify(id)); + var resultantId = id; + //Its a relative path, so lop off the last portion and add the id (minus "./") + if (id.charAt(0) === ".") { + resultantId = module.id.slice(0, module.id.lastIndexOf(SEPARATOR)) + SEPARATOR + id.slice(2); + } + return require(resultantId); + }; + module.exports = {}; + delete module.factory; + factory(localRequire, module.exports, module); + return module.exports; + } + + require = function (id) { + if (!modules[id]) { + throw "module " + id + " not found"; + } else if (id in inProgressModules) { + var cycle = requireStack.slice(inProgressModules[id]).join('->') + '->' + id; + throw "Cycle in require graph: " + cycle; + } + if (modules[id].factory) { + try { + inProgressModules[id] = requireStack.length; + requireStack.push(id); + return build(modules[id]); + } finally { + delete inProgressModules[id]; + requireStack.pop(); + } + } + return modules[id].exports; + }; + + define = function (id, factory) { + if (modules[id]) { + throw "module " + id + " already defined"; + } + + modules[id] = { + id: id, + factory: factory + }; + }; + + define.remove = function (id) { + delete modules[id]; + }; + + define.moduleMap = modules; +})(); + +//Export for use in node +if (typeof module === "object" && typeof require === "function") { + module.exports.require = require; + module.exports.define = define; +} + +// file: src/cordova.js +define("cordova", function(require, exports, module) { + + +var channel = require('cordova/channel'); +var platform = require('cordova/platform'); + +/** + * Intercept calls to addEventListener + removeEventListener and handle deviceready, + * resume, and pause events. + */ +var m_document_addEventListener = document.addEventListener; +var m_document_removeEventListener = document.removeEventListener; +var m_window_addEventListener = window.addEventListener; +var m_window_removeEventListener = window.removeEventListener; + +/** + * Houses custom event handlers to intercept on document + window event listeners. + */ +var documentEventHandlers = {}, + windowEventHandlers = {}; + +document.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof documentEventHandlers[e] != 'undefined') { + documentEventHandlers[e].subscribe(handler); + } else { + m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +window.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof windowEventHandlers[e] != 'undefined') { + windowEventHandlers[e].subscribe(handler); + } else { + m_window_addEventListener.call(window, evt, handler, capture); + } +}; + +document.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof documentEventHandlers[e] != "undefined") { + documentEventHandlers[e].unsubscribe(handler); + } else { + m_document_removeEventListener.call(document, evt, handler, capture); + } +}; + +window.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof windowEventHandlers[e] != "undefined") { + windowEventHandlers[e].unsubscribe(handler); + } else { + m_window_removeEventListener.call(window, evt, handler, capture); + } +}; + +function createEvent(type, data) { + var event = document.createEvent('Events'); + event.initEvent(type, false, false); + if (data) { + for (var i in data) { + if (data.hasOwnProperty(i)) { + event[i] = data[i]; + } + } + } + return event; +} + + +var cordova = { + define:define, + require:require, + version:CORDOVA_JS_BUILD_LABEL, + platformId:platform.id, + /** + * Methods to add/remove your own addEventListener hijacking on document + window. + */ + addWindowEventHandler:function(event) { + return (windowEventHandlers[event] = channel.create(event)); + }, + addStickyDocumentEventHandler:function(event) { + return (documentEventHandlers[event] = channel.createSticky(event)); + }, + addDocumentEventHandler:function(event) { + return (documentEventHandlers[event] = channel.create(event)); + }, + removeWindowEventHandler:function(event) { + delete windowEventHandlers[event]; + }, + removeDocumentEventHandler:function(event) { + delete documentEventHandlers[event]; + }, + /** + * Retrieve original event handlers that were replaced by Cordova + * + * @return object + */ + getOriginalHandlers: function() { + return {'document': {'addEventListener': m_document_addEventListener, 'removeEventListener': m_document_removeEventListener}, + 'window': {'addEventListener': m_window_addEventListener, 'removeEventListener': m_window_removeEventListener}}; + }, + /** + * Method to fire event from native code + * bNoDetach is required for events which cause an exception which needs to be caught in native code + */ + fireDocumentEvent: function(type, data, bNoDetach) { + var evt = createEvent(type, data); + if (typeof documentEventHandlers[type] != 'undefined') { + if( bNoDetach ) { + documentEventHandlers[type].fire(evt); + } + else { + setTimeout(function() { + // Fire deviceready on listeners that were registered before cordova.js was loaded. + if (type == 'deviceready') { + document.dispatchEvent(evt); + } + documentEventHandlers[type].fire(evt); + }, 0); + } + } else { + document.dispatchEvent(evt); + } + }, + fireWindowEvent: function(type, data) { + var evt = createEvent(type,data); + if (typeof windowEventHandlers[type] != 'undefined') { + setTimeout(function() { + windowEventHandlers[type].fire(evt); + }, 0); + } else { + window.dispatchEvent(evt); + } + }, + + /** + * Plugin callback mechanism. + */ + // Randomize the starting callbackId to avoid collisions after refreshing or navigating. + // This way, it's very unlikely that any new callback would get the same callbackId as an old callback. + callbackId: Math.floor(Math.random() * 2000000000), + callbacks: {}, + callbackStatus: { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }, + + /** + * Called by native code when returning successful result from an action. + */ + +//------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------NOTE------------------------------------------------ +//-----------------------------------------add this to the activity.js in lib folder--------------------------- +//-------------------------------------------------------line : 144-------------------------------------------- +//------------------------------------------------------------------------------------------------------------- + + callbackSuccess: function(callbackId, args) { + console.log("Reached callbackSuccess"); + console.log("callbackId : "+callbackId); + console.log("args.status : "+args.status); + console.log("[args.message] : "+[args.message]); + console.log("args.keepCallback : "+args.keepCallback); + try { + cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback); + } catch (e) { + console.log("Error in error callback: " + callbackId + " = "+e); + } + }, + + /** + * Called by native code when returning error result from an action. + */ + +//------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------NOTE------------------------------------------------ +//-----------------------------------------add this to the activity.js in lib folder--------------------------- +//-----------------------------------------------------line : 142---------------------------------------------- +//------------------------------------------------------------------------------------------------------------- + + callbackError: function(callbackId, args) { + // TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative. + // Derive success from status. + try { + cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback); + } catch (e) { + console.log("Error in error callback: " + callbackId + " = "+e); + } + }, + + /** + * Called by native code when returning the result from an action. + */ + callbackFromNative: function(callbackId, success, status, args, keepCallback) { + console.log("Reached callbackFromNative"); + console.log("success : "+success); + console.log("status : "+status); + console.log("args : " + args); + console.log("keepCallback : "+keepCallback); + var callback = cordova.callbacks[callbackId]; + //console.log(callback); + if (callback) { + if (success && status == cordova.callbackStatus.OK) { + callback.success && callback.success.apply(null, args); + } else if (!success) { + callback.fail && callback.fail.apply(null, args); + } + + // Clear callback if not expecting any more results + if (!keepCallback) { + delete cordova.callbacks[callbackId]; + } + } + }, + + addConstructor: function(func) { + channel.onCordovaReady.subscribe(function() { + try { + func(); + } catch(e) { + console.log("Failed to run constructor: " + e); + } + }); + } +}; + + +module.exports = cordova; + +}); + + +// file: src/common/argscheck.js +define("cordova/argscheck", function(require, exports, module) { + +var exec = require('cordova/exec'); +var utils = require('cordova/utils'); + +var moduleExports = module.exports; + +var typeMap = { + 'A': 'Array', + 'D': 'Date', + 'N': 'Number', + 'S': 'String', + 'F': 'Function', + 'O': 'Object' +}; + +function extractParamName(callee, argIndex) { + return (/.*?\((.*?)\)/).exec(callee)[1].split(', ')[argIndex]; +} + +function checkArgs(spec, functionName, args, opt_callee) { + if (!moduleExports.enableChecks) { + return; + } + var errMsg = null; + var typeName; + for (var i = 0; i < spec.length; ++i) { + var c = spec.charAt(i), + cUpper = c.toUpperCase(), + arg = args[i]; + // Asterix means allow anything. + if (c == '*') { + continue; + } + typeName = utils.typeName(arg); + if ((arg === null || arg === undefined) && c == cUpper) { + continue; + } + if (typeName != typeMap[cUpper]) { + errMsg = 'Expected ' + typeMap[cUpper]; + break; + } + } + if (errMsg) { + errMsg += ', but got ' + typeName + '.'; + errMsg = 'Wrong type for parameter "' + extractParamName(opt_callee || args.callee, i) + '" of ' + functionName + ': ' + errMsg; + // Don't log when running unit tests. + if (typeof jasmine == 'undefined') { + console.error(errMsg); + } + throw TypeError(errMsg); + } +} + +function getValue(value, defaultValue) { + return value === undefined ? defaultValue : value; +} + +moduleExports.checkArgs = checkArgs; +moduleExports.getValue = getValue; +moduleExports.enableChecks = true; + + +}); + +// file: src/common/base64.js +define("cordova/base64", function(require, exports, module) { + +var base64 = exports; + +base64.fromArrayBuffer = function(arrayBuffer) { + var array = new Uint8Array(arrayBuffer); + return uint8ToBase64(array); +}; + +//------------------------------------------------------------------------------ + +/* This code is based on the performance tests at http://jsperf.com/b64tests + * This 12-bit-at-a-time algorithm was the best performing version on all + * platforms tested. + */ + +var b64_6bit = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +var b64_12bit; + +var b64_12bitTable = function() { + b64_12bit = []; + for (var i=0; i<64; i++) { + for (var j=0; j<64; j++) { + b64_12bit[i*64+j] = b64_6bit[i] + b64_6bit[j]; + } + } + b64_12bitTable = function() { return b64_12bit; }; + return b64_12bit; +}; + +function uint8ToBase64(rawData) { + var numBytes = rawData.byteLength; + var output=""; + var segment; + var table = b64_12bitTable(); + for (var i=0;i> 12]; + output += table[segment & 0xfff]; + } + if (numBytes - i == 2) { + segment = (rawData[i] << 16) + (rawData[i+1] << 8); + output += table[segment >> 12]; + output += b64_6bit[(segment & 0xfff) >> 6]; + output += '='; + } else if (numBytes - i == 1) { + segment = (rawData[i] << 16); + output += table[segment >> 12]; + output += '=='; + } + return output; +} + +}); + +// file: src/common/builder.js +define("cordova/builder", function(require, exports, module) { + +var utils = require('cordova/utils'); + +function each(objects, func, context) { + for (var prop in objects) { + if (objects.hasOwnProperty(prop)) { + func.apply(context, [objects[prop], prop]); + } + } +} + +function clobber(obj, key, value) { + exports.replaceHookForTesting(obj, key); + obj[key] = value; + // Getters can only be overridden by getters. + if (obj[key] !== value) { + utils.defineGetter(obj, key, function() { + return value; + }); + } +} + +function assignOrWrapInDeprecateGetter(obj, key, value, message) { + if (message) { + utils.defineGetter(obj, key, function() { + console.log(message); + delete obj[key]; + clobber(obj, key, value); + return value; + }); + } else { + clobber(obj, key, value); + } +} + +function include(parent, objects, clobber, merge) { + each(objects, function (obj, key) { + try { + var result = obj.path ? require(obj.path) : {}; + + if (clobber) { + // Clobber if it doesn't exist. + if (typeof parent[key] === 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else if (typeof obj.path !== 'undefined') { + // If merging, merge properties onto parent, otherwise, clobber. + if (merge) { + recursiveMerge(parent[key], result); + } else { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } + } + result = parent[key]; + } else { + // Overwrite if not currently defined. + if (typeof parent[key] == 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else { + // Set result to what already exists, so we can build children into it if they exist. + result = parent[key]; + } + } + + if (obj.children) { + include(result, obj.children, clobber, merge); + } + } catch(e) { + utils.alert('Exception building Cordova JS globals: ' + e + ' for key "' + key + '"'); + } + }); +} + +/** + * Merge properties from one object onto another recursively. Properties from + * the src object will overwrite existing target property. + * + * @param target Object to merge properties into. + * @param src Object to merge properties from. + */ +function recursiveMerge(target, src) { + for (var prop in src) { + if (src.hasOwnProperty(prop)) { + if (target.prototype && target.prototype.constructor === target) { + // If the target object is a constructor override off prototype. + clobber(target.prototype, prop, src[prop]); + } else { + if (typeof src[prop] === 'object' && typeof target[prop] === 'object') { + recursiveMerge(target[prop], src[prop]); + } else { + clobber(target, prop, src[prop]); + } + } + } + } +} + +exports.buildIntoButDoNotClobber = function(objects, target) { + include(target, objects, false, false); +}; +exports.buildIntoAndClobber = function(objects, target) { + include(target, objects, true, false); +}; +exports.buildIntoAndMerge = function(objects, target) { + include(target, objects, true, true); +}; +exports.recursiveMerge = recursiveMerge; +exports.assignOrWrapInDeprecateGetter = assignOrWrapInDeprecateGetter; +exports.replaceHookForTesting = function() {}; + +}); + +// file: src/common/channel.js +define("cordova/channel", function(require, exports, module) { + +var utils = require('cordova/utils'), + nextGuid = 1; + +/** + * Custom pub-sub "channel" that can have functions subscribed to it + * This object is used to define and control firing of events for + * cordova initialization, as well as for custom events thereafter. + * + * The order of events during page load and Cordova startup is as follows: + * + * onDOMContentLoaded* Internal event that is received when the web page is loaded and parsed. + * onNativeReady* Internal event that indicates the Cordova native side is ready. + * onCordovaReady* Internal event fired when all Cordova JavaScript objects have been created. + * onDeviceReady* User event fired to indicate that Cordova is ready + * onResume User event fired to indicate a start/resume lifecycle event + * onPause User event fired to indicate a pause lifecycle event + * onDestroy* Internal event fired when app is being destroyed (User should use window.onunload event, not this one). + * + * The events marked with an * are sticky. Once they have fired, they will stay in the fired state. + * All listeners that subscribe after the event is fired will be executed right away. + * + * The only Cordova events that user code should register for are: + * deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript + * pause App has moved to background + * resume App has returned to foreground + * + * Listeners can be registered as: + * document.addEventListener("deviceready", myDeviceReadyListener, false); + * document.addEventListener("resume", myResumeListener, false); + * document.addEventListener("pause", myPauseListener, false); + * + * The DOM lifecycle events should be used for saving and restoring state + * window.onload + * window.onunload + * + */ + +/** + * Channel + * @constructor + * @param type String the channel name + */ +var Channel = function(type, sticky) { + this.type = type; + // Map of guid -> function. + this.handlers = {}; + // 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired. + this.state = sticky ? 1 : 0; + // Used in sticky mode to remember args passed to fire(). + this.fireArgs = null; + // Used by onHasSubscribersChange to know if there are any listeners. + this.numHandlers = 0; + // Function that is called when the first listener is subscribed, or when + // the last listener is unsubscribed. + this.onHasSubscribersChange = null; +}, + channel = { + /** + * Calls the provided function only after all of the channels specified + * have been fired. All channels must be sticky channels. + */ + join: function(h, c) { + var len = c.length, + i = len, + f = function() { + if (!(--i)) h(); + }; + for (var j=0; j 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); +console.log("Reached in websocket client send"); + } 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 new 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) { + console.log("INSIDE BUS.SENDMESSAGE"); + var message = { + "method": method, + "id": lastId, + "params": params, + "jsonrpc": "2.0" + }; + + if (callback) { + callbacks[lastId] = callback; + } + console.log("MESSAGE INSIDE BUS.SENDMESSAGE : "+JSON.stringify(message)); + client.send(JSON.stringify(message)); + + lastId++; + }; + + 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; + }; + + module.exports = bus; +}); + + + +// file: src/sugar/exec.js +define("cordova/exec", function(require, exports, module) { + +//var sugar = require('cordova/platform'); +var cordova = require('cordova'); +var execProxy = require('cordova/exec/proxy'); + +module.exports = function(success, fail, service, action, args) { + var proxy = execProxy.get(service,action); + if(proxy) { + var callbackId = service + cordova.callbackId++; + //console.log("EXEC:" + service + " : " + action); + if (typeof success == "function" || typeof fail == "function") { + cordova.callbacks[callbackId] = {success:success, fail:fail}; + } + try { + proxy(success, fail, args); + } + catch(e) { + console.log("Exception calling native with command :: " + service + " :: " + action + " ::exception=" + e); + } + } + else { + fail && fail("Missing Command Error"); + } +}; + +}); + + +// file: src/common/exec/proxy.js +define("cordova/exec/proxy", function(require, exports, module) { + + +// internal map of proxy function +var CommandProxyMap = {}; + +module.exports = { + + // example: cordova.commandProxy.add("Accelerometer",{getCurrentAcceleration: function(successCallback, errorCallback, options) {...},...); + add:function(id,proxyObj) { + console.log("adding proxy for " + id); + CommandProxyMap[id] = proxyObj; + return proxyObj; + }, + + // cordova.commandProxy.remove("Accelerometer"); + remove:function(id) { + var proxy = CommandProxyMap[id]; + delete CommandProxyMap[id]; + CommandProxyMap[id] = null; + return proxy; + }, + + get:function(service,action) { + return ( CommandProxyMap[service] ? CommandProxyMap[service][action] : null ); + } +}; +}); + + +// file: src/sugar/sugar/commandProxy.js +define("cordova/sugar/commandProxy", function(require, exports, module) { + +console.log('WARNING: please require cordova/exec/proxy instead'); +module.exports = require('cordova/exec/proxy'); + +}); + + +// file: src/common/init.js +define("cordova/init", function(require, exports, module) { + +var channel = require('cordova/channel'); +var cordova = require('cordova'); +var modulemapper = require('cordova/modulemapper'); +var platform = require('cordova/platform'); +var pluginloader = require('cordova/pluginloader'); + +var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady]; + +function logUnfiredChannels(arr) { + for (var i = 0; i < arr.length; ++i) { + if (arr[i].state != 2) { + console.log('Channel not fired: ' + arr[i].type); + } + } +} + +window.setTimeout(function() { + if (channel.onDeviceReady.state != 2) { + console.log('deviceready has not fired after 5 seconds.'); + logUnfiredChannels(platformInitChannelsArray); + logUnfiredChannels(channel.deviceReadyChannelsArray); + } +}, 5000); + +// Replace navigator before any modules are required(), to ensure it happens as soon as possible. +// We replace it so that properties that can't be clobbered can instead be overridden. +function replaceNavigator(origNavigator) { + var CordovaNavigator = function() {}; + CordovaNavigator.prototype = origNavigator; + var newNavigator = new CordovaNavigator(); + // This work-around really only applies to new APIs that are newer than Function.bind. + // Without it, APIs such as getGamepads() break. + if (CordovaNavigator.bind) { + for (var key in origNavigator) { + if (typeof origNavigator[key] == 'function') { + newNavigator[key] = origNavigator[key].bind(origNavigator); + } + } + } + return newNavigator; +} +if (window.navigator) { + window.navigator = replaceNavigator(window.navigator); +} + +if (!window.console) { + window.console = { + log: function(){} + }; +} +if (!window.console.warn) { + window.console.warn = function(msg) { + this.log("warn: " + msg); + }; +} + +// Register pause, resume and deviceready channels as events on document. +channel.onPause = cordova.addDocumentEventHandler('pause'); +channel.onResume = cordova.addDocumentEventHandler('resume'); +channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready'); + +// Listen for DOMContentLoaded and notify our channel subscribers. +if (document.readyState == 'complete' || document.readyState == 'interactive') { + channel.onDOMContentLoaded.fire(); +console.log("document.readyState"); +console.log(document.readyState); +} else { + document.addEventListener('DOMContentLoaded', function() { + channel.onDOMContentLoaded.fire(); + }, false); +} + +// _nativeReady is global variable that the native side can set +// to signify that the native code is ready. It is a global since +// it may be called before any cordova JS is ready. +if (window._nativeReady) { + channel.onNativeReady.fire(); +} + +modulemapper.clobbers('cordova', 'cordova'); +modulemapper.clobbers('cordova/exec', 'cordova.exec'); +modulemapper.clobbers('cordova/exec', 'Cordova.exec'); + +// Call the platform-specific initialization. +platform.bootstrap && platform.bootstrap(); + +pluginloader.load(function() { + channel.onPluginsReady.fire(); +}); + +/** + * Create all cordova objects once native side is ready. + */ +channel.join(function() { + modulemapper.mapModules(window); + + platform.initialize && platform.initialize(); + + // Fire event to notify that all objects are created + channel.onCordovaReady.fire(); + + // Fire onDeviceReady event once page has fully loaded, all + // constructors have run and cordova info has been received from native + // side. + channel.join(function() { + require('cordova').fireDocumentEvent('deviceready'); + }, channel.deviceReadyChannelsArray); + +}, platformInitChannelsArray); + + +}); + +// file: src/common/modulemapper.js +define("cordova/modulemapper", function(require, exports, module) { + +var builder = require('cordova/builder'), + moduleMap = define.moduleMap, + symbolList, + deprecationMap; + +exports.reset = function() { + symbolList = []; + deprecationMap = {}; +}; + +function addEntry(strategy, moduleName, symbolPath, opt_deprecationMessage) { + if (!(moduleName in moduleMap)) { + throw new Error('Module ' + moduleName + ' does not exist.'); + } + symbolList.push(strategy, moduleName, symbolPath); + if (opt_deprecationMessage) { + deprecationMap[symbolPath] = opt_deprecationMessage; + } +} + +exports.clobbers = function(moduleName, symbolPath, opt_deprecationMessage) { + addEntry('c', moduleName, symbolPath, opt_deprecationMessage); +}; + +exports.merges = function(moduleName, symbolPath, opt_deprecationMessage) { + addEntry('m', moduleName, symbolPath, opt_deprecationMessage); +}; + +exports.defaults = function(moduleName, symbolPath, opt_deprecationMessage) { + addEntry('d', moduleName, symbolPath, opt_deprecationMessage); +}; + +exports.runs = function(moduleName) { + addEntry('r', moduleName, null); +}; + +function prepareNamespace(symbolPath, context) { + if (!symbolPath) { + return context; + } + var parts = symbolPath.split('.'); + var cur = context; + for (var i = 0, part; part = parts[i]; ++i) { + cur = cur[part] = cur[part] || {}; + } + return cur; +} + +exports.mapModules = function(context) { + var origSymbols = {}; + context.CDV_origSymbols = origSymbols; + for (var i = 0, len = symbolList.length; i < len; i += 3) { + var strategy = symbolList[i]; + var moduleName = symbolList[i + 1]; + var module = require(moduleName); + // + if (strategy == 'r') { + continue; + } + var symbolPath = symbolList[i + 2]; + var lastDot = symbolPath.lastIndexOf('.'); + var namespace = symbolPath.substr(0, lastDot); + var lastName = symbolPath.substr(lastDot + 1); + + var deprecationMsg = symbolPath in deprecationMap ? 'Access made to deprecated symbol: ' + symbolPath + '. ' + deprecationMsg : null; + var parentObj = prepareNamespace(namespace, context); + var target = parentObj[lastName]; + + if (strategy == 'm' && target) { + builder.recursiveMerge(target, module); + } else if ((strategy == 'd' && !target) || (strategy != 'd')) { + if (!(symbolPath in origSymbols)) { + origSymbols[symbolPath] = target; + } + builder.assignOrWrapInDeprecateGetter(parentObj, lastName, module, deprecationMsg); + } + } +}; + +exports.getOriginalSymbol = function(context, symbolPath) { + var origSymbols = context.CDV_origSymbols; + if (origSymbols && (symbolPath in origSymbols)) { + return origSymbols[symbolPath]; + } + var parts = symbolPath.split('.'); + var obj = context; + for (var i = 0; i < parts.length; ++i) { + obj = obj && obj[parts[i]]; + } + return obj; +}; + +exports.reset(); + + +}); + +// file: src/sugar/platform.js +define("cordova/platform", function(require, exports, module) { +module.exports = { + id: 'sugar', + cordovaVersion: '3.5.0', + bootstrap: function() { + var cordova = require('cordova'), + exec = require('cordova/exec'), + channel = require("cordova/channel"), + modulemapper = require('cordova/modulemapper'); + + require('cordova/modulemapper').clobbers('cordova/exec/proxy', 'cordova.commandProxy'); + require('cordova/channel').onNativeReady.fire(); + + + + + } +}; + +}); + +// file: src/common/pluginloader.js +define("cordova/pluginloader", function(require, exports, module) { + +var modulemapper = require('cordova/modulemapper'); +var urlutil = require('cordova/urlutil'); + +// Helper function to inject a + + + +
+ + + + + + + + + + + + + +
+ +
+
+

Apache Cordova

+ +
+ +
+

ACCELEROMETER



+ navigator.accelerometer.getCurrentAcceleration - Get the current acceleration along the _x_, _y_, and _z_ axes. + +

+ + navigator.accelerometer.watchAcceleration - Retrieves the device's current `Acceleration` at a regular interval, executing + the `accelerometerSuccess` callback function each time. +
+
+ +

+ + navigator.accelerometer.clearWatch - Stop watching the `Acceleration` referenced by the `watchID` parameter. + +

+
+ + +
+

CAMERA



+ + get picture from journal + +

+ + get the picture from camera + +
+ + +

+
+ + +
+

DIALOG PLUGIN



+ + navigator.notification.alert(message, alertCallback, [title], [buttonName]) :
message: Dialog message (String), alertCallback : Callback to invoke when alert dialog is dismissed (Function),
title: Dialog title(String) (Optional, defaults to `Alert`),
buttonName: Button name (String) (Optional, defaults to `OK`) + +

+ + navigator.notification.confirm(message, confirmCallback, [title], [buttonLabels]) -
message: Dialog message (String),
confirmCallback: Callback to invoke with index of button pressed (1, 2, or 3) or when the dialog is dismissed without a button press (0) (Function),
title: Dialog title (String)(Optional, defaults to `Confirm`)
buttonLabels: Array of strings specifying button labels(Array)(Optional, defaults to [`OK,Cancel`]) + +

+ + navigator.notification.prompt - Displays a native dialog box that is more customizable than the browser's `prompt` function - navigator.notification.prompt(message, promptCallback, [title], [buttonLabels], [defaultText]) :: message:
Dialog message.(String) ;
promptCallback: Callback to invoke with index of button pressed (1, 2, or 3) or when the dialog is dismissed without a button press (0). (Function);
title: Dialog title(String) (Optional, defaults to `Prompt`) ;
buttonLabels: Array of strings specifying button labels (Array) (Optional, defaults to `["OK","Cancel"]`);
defaultText: Default textbox input value (`String`) (Optional, Default: empty string) + +

+
+ + + + +
+

DEVICE



+ +

Loading device properties...


+ + It defines a global object device with the following properties : +
device.model : Get the hardware model from the gio ssettings +
device.cordova: the version of cordova +
device.platform: returns 'sugar' +
device.uuid:returns the serial number +
device.version:version of sugar +

+
+ +
+

NETWORK CONNECTION



+ + The `connection` object, exposed via `navigator.connection`, provides information about the device's cellular and wifi connection. + +

+
+ + + +
+

GLOBALIZATION



+ + navigator.globalization.getPreferredLanguage. Get the BCP 47 language tag for the client's current language. + +

+ + navigator.globalization.getLocaleName. Returns the BCP 47 compliant tag for the client's current locale setting. + +

+
+
+ + + + + diff --git a/activities/Cordova.activity/js/activity.js b/activities/Cordova.activity/js/activity.js new file mode 100644 index 000000000..ff8197175 --- /dev/null +++ b/activities/Cordova.activity/js/activity.js @@ -0,0 +1,12 @@ +define(function (require) { + var activity = require("sugar-web/activity/activity"); + + // Manipulate the DOM only when it is ready. + require(['domReady!'], function (doc) { + + // Initialize the activity. + activity.setup(); + + }); + +}); diff --git a/activities/Cordova.activity/js/index.js b/activities/Cordova.activity/js/index.js new file mode 100644 index 000000000..ddfb0eb9a --- /dev/null +++ b/activities/Cordova.activity/js/index.js @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +var app = { + compliant: true, + + // Application Constructor + initialize: function() { + var useragent = navigator.userAgent.toLowerCase(); + if (useragent.indexOf('android') == -1 && useragent.indexOf('ios') == -1 && useragent.indexOf('mozilla/5.0 (mobile') == -1) { + var parentElement = document.getElementById('deviceready'); + var listeningElement = parentElement.querySelector('.listening'); + var notcompatibleElement = parentElement.querySelector('.notcompatible'); + + listeningElement.setAttribute('style', 'display:none;'); + notcompatibleElement.setAttribute('style', 'display:block;'); + this.compliant = false; + return; + } + this.bindEvents(); + }, + // Bind Event Listeners + // + // Bind any events that are required on startup. Common events are: + // 'load', 'deviceready', 'offline', and 'online'. + bindEvents: function() { + document.addEventListener('deviceready', this.onDeviceReady, false); + }, + // deviceready Event Handler + // + // The scope of 'this' is the event. In order to call the 'receivedEvent' + // function, we must explicitly call 'app.receivedEvent(...);' + onDeviceReady: function() { + app.receivedEvent('deviceready'); + var element = document.getElementById('deviceProperties'); + element.innerHTML = 'Device Model: ' + device.model + '\n' + + 'Device Cordova: ' + device.cordova + '\n' + + 'Device Platform: ' + device.platform + '\n' + + 'Device UUID: ' + device.uuid + '\n' + + 'Device Version: ' + device.version + '\n'; + }, + // Update DOM on a Received Event + receivedEvent: function(id) { + var parentElement = document.getElementById(id); + var listeningElement = parentElement.querySelector('.listening'); + var receivedElement = parentElement.querySelector('.received'); + + listeningElement.setAttribute('style', 'display:none;'); + receivedElement.setAttribute('style', 'display:block;'); + + console.log('Received Event: ' + id); + } +}; diff --git a/activities/Cordova.activity/js/loader.js b/activities/Cordova.activity/js/loader.js new file mode 100644 index 000000000..01021e981 --- /dev/null +++ b/activities/Cordova.activity/js/loader.js @@ -0,0 +1,8 @@ +requirejs.config({ + baseUrl: "lib", + paths: { + activity: "../js" + } +}); + +requirejs(["activity/activity"]); diff --git a/activities/Cordova.activity/lib/domReady.js b/activities/Cordova.activity/lib/domReady.js new file mode 100644 index 000000000..2b5412209 --- /dev/null +++ b/activities/Cordova.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/Cordova.activity/lib/require.js b/activities/Cordova.activity/lib/require.js new file mode 100644 index 000000000..5b2687543 --- /dev/null +++ b/activities/Cordova.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/Cordova.activity/lib/sugar-web/LICENSE b/activities/Cordova.activity/lib/sugar-web/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/activities/Cordova.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/Cordova.activity/lib/sugar-web/README.md b/activities/Cordova.activity/lib/sugar-web/README.md new file mode 100644 index 000000000..de87a7250 --- /dev/null +++ b/activities/Cordova.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/Cordova.activity/lib/sugar-web/activity/activity.js b/activities/Cordova.activity/lib/sugar-web/activity/activity.js new file mode 100644 index 000000000..2b9690024 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/activity/activity.js @@ -0,0 +1,133 @@ +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) { + + 'use strict'; + + var datastoreObject = null; + + var activity = {}; + + activity.setup = function () { + bus.listen(); + + l10n.start(); + + function sendPauseEvent() { + var pauseEvent = document.createEvent("CustomEvent"); + pauseEvent.initCustomEvent('activityPause', false, false, { + 'cancelable': true + }); + window.dispatchEvent(pauseEvent); + } + bus.onNotification("activity.pause", sendPauseEvent); + + // An activity that handles 'activityStop' can also call + // event.preventDefault() to prevent the close, and explicitly + // call activity.close() after storing. + + function sendStopEvent() { + var stopEvent = document.createEvent("CustomEvent"); + stopEvent.initCustomEvent('activityStop', false, false, { + 'cancelable': true + }); + var result = window.dispatchEvent(stopEvent); + if (result) { + activity.close(); + } + } + bus.onNotification("activity.stop", sendStopEvent); + + 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); + var 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) { + sendStopEvent(); + }); + + 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); + }; + + activity.close = function (callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null); + } else { + callback(error, null); + } + } + + bus.sendMessage("activity.close", [], onResponseReceived); + }; + + activity.showObjectChooser = function (callback) { + function onResponseReceived(error, result) { + if (error === null) { + callback(null, result[0]); + } else { + callback(error, null); + } + } + + bus.sendMessage("activity.show_object_chooser", [], onResponseReceived); + }; + + return activity; +}); diff --git a/activities/Cordova.activity/lib/sugar-web/activity/shortcut.js b/activities/Cordova.activity/lib/sugar-web/activity/shortcut.js new file mode 100644 index 000000000..508e92ab1 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/activity/shortcut.js @@ -0,0 +1,60 @@ +define(function () { + + 'use strict'; + + 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 (var 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/Cordova.activity/lib/sugar-web/bus.js b/activities/Cordova.activity/lib/sugar-web/bus.js new file mode 100644 index 000000000..2cdfa4ac0 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/bus.js @@ -0,0 +1,14 @@ +define(["sugar-web/env", "sugar-web/bus/sugarizer", "sugar-web/bus/sugaros"], function(env, sugarizer, sugaros) { + + 'use strict'; + + var bus; + + if (env.isSugarizer()) { + bus = sugarizer; + } else { + bus = sugaros; + } + + return bus; +}); diff --git a/activities/Cordova.activity/lib/sugar-web/bus/sugarizer.js b/activities/Cordova.activity/lib/sugar-web/bus/sugarizer.js new file mode 100644 index 000000000..cd1b6a8aa --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/bus/sugarizer.js @@ -0,0 +1,77 @@ +define(["sugar-web/env"], function (env) { + + 'use strict'; + + var bus = {}; + + function WebSocketClient(environment) { + } + + WebSocketClient.prototype.send = function (data) { + }; + + WebSocketClient.prototype.close = function () { + }; + + function InputStream() { + } + + InputStream.prototype.open = function (callback) { + }; + + InputStream.prototype.read = function (count, callback) { + }; + + InputStream.prototype.gotData = function (buffer) { + }; + + InputStream.prototype.close = function (callback) { + }; + + function OutputStream() { + } + + OutputStream.prototype.open = function (callback) { + }; + + OutputStream.prototype.write = function (data) { + }; + + OutputStream.prototype.close = function (callback) { + }; + + bus.createInputStream = function (callback) { + }; + + bus.createOutputStream = function (callback) { + }; + + bus.sendMessage = function (method, params, callback) { + if (method == "activity.close") { + window.location = "../../index.html"; + } else if (method == "activity.get_xo_color") { + var color = {stroke: "#FF2B34", fill: "#005FE4"}; + if (typeof(Storage)!=="undefined" && typeof(window.localStorage)!=="undefined") { + try { + color = JSON.parse(window.localStorage.getItem("sugar_settings")).colorvalue; + } catch(err) {} + } + callback(null, [[color.fill, color.stroke]]); + } + return; + }; + + bus.onNotification = function (method, callback) { + }; + + bus.sendBinary = function (buffer, callback) { + }; + + bus.listen = function (customClient) { + }; + + bus.close = function () { + }; + + return bus; +}); diff --git a/activities/Cordova.activity/lib/sugar-web/bus/sugaros.js b/activities/Cordova.activity/lib/sugar-web/bus/sugaros.js new file mode 100644 index 000000000..52f385779 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/bus/sugaros.js @@ -0,0 +1,223 @@ +define(["sugar-web/env"], function (env) { + + 'use strict'; + + 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 new 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, + "jsonrpc": "2.0" + }; + + if (callback) { + callbacks[lastId] = callback; + } + + client.send(JSON.stringify(message)); + + lastId++; + }; + + 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/Cordova.activity/lib/sugar-web/datastore.js b/activities/Cordova.activity/lib/sugar-web/datastore.js new file mode 100644 index 000000000..c1a396f8f --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/datastore.js @@ -0,0 +1,14 @@ +define(["sugar-web/env", "sugar-web/datastore/sugarizer", "sugar-web/datastore/sugaros"], function(env, sugarizer, sugaros) { + + 'use strict'; + + var datastore ; + + if (env.isSugarizer()) { + datastore = sugarizer; + } else { + datastore = sugaros; + } + + return datastore; +}); diff --git a/activities/Cordova.activity/lib/sugar-web/datastore/sugarizer.js b/activities/Cordova.activity/lib/sugar-web/datastore/sugarizer.js new file mode 100644 index 000000000..733ab4c8e --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/datastore/sugarizer.js @@ -0,0 +1,213 @@ +define(["sugar-web/bus", "sugar-web/env"], function(bus, env) { + + 'use strict'; + + var datastore = {}; + + var html5storage = {}; + var datastorePrefix = 'sugar_datastore_'; + var initialized = false; + + //- Utility function + + // Get parameter from query string + datastore.getUrlParameter = function(name) { + var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search); + return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); + }; + + // Create a uuid + datastore.createUUID = function() { + var s = []; + var hexDigits = "0123456789abcdef"; + for (var i = 0; i < 36; i++) { + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); + } + s[14] = "4"; + s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); + s[8] = s[13] = s[18] = s[23] = "-"; + + var uuid = s.join(""); + return uuid; + }; + + // Callback checker + datastore.callbackChecker = function(callback) { + if (callback === undefined || callback === null) { + callback = function() {}; + } + return callback; + }; + + + //- Static Datastore methods + + // Create a new datastore entry + datastore.create = function(metadata, callback, text) { + var callback_c = datastore.callbackChecker(callback); + var objectId = datastore.createUUID(); + html5storage.setValue(datastorePrefix + objectId, { + metadata: metadata, + text: (text === undefined) ? null : text + }); + callback_c(null, objectId); + } + + // Find entries matching an activity type + datastore.find = function(id) { + var results = []; + if (!html5storage.test()) + return results; + for (var key in localStorage) { + if (key.substr(0, datastorePrefix.length) == datastorePrefix) { + var entry = html5storage.getValue(key); + entry.objectId = key.substr(datastorePrefix.length); + if (id === undefined || entry.metadata.activity == id) { + results.push(entry); + } + } + } + + return results; + } + + // Remove an entry in the datastore + datastore.remove = function(objectId) { + html5storage.removeValue(datastorePrefix + objectId); + } + + //- Instance datastore methods + function DatastoreObject(objectId) { + this.objectId = objectId; + this.newMetadata = {}; + this.newDataAsText = null; + this.toload = false; + + // Init environment from query string values + if (!initialized) { + env.getEnvironment(function() { + initialized = true; + }); + } + + // Init or create objectId if need + var that = this; + if (this.objectId === undefined) { + var env_objectId = window.top.sugar.environment.objectId; + if (env_objectId != null) { + this.objectId = env_objectId; + this.toload = true; + } + } + } + + // Load metadata + DatastoreObject.prototype.getMetadata = function(callback) { + var callback_c = datastore.callbackChecker(callback); + var result = html5storage.getValue(datastorePrefix + this.objectId); + if (result != null) { + this.setMetadata(result.metadata); + this.setDataAsText(result.text); + this.toload = false; + callback_c(null, result.metadata); + } + }; + + // Load text + DatastoreObject.prototype.loadAsText = function(callback) { + var callback_c = datastore.callbackChecker(callback); + var result = html5storage.getValue(datastorePrefix + this.objectId); + if (result != null) { + this.setMetadata(result.metadata); + this.setDataAsText(result.text); + this.toload = false; + callback_c(null, result.metadata, result.text); + } + }; + + // Set metadata + DatastoreObject.prototype.setMetadata = function(metadata) { + for (var key in metadata) { + this.newMetadata[key] = metadata[key]; + } + }; + + // Set text + DatastoreObject.prototype.setDataAsText = function(text) { + this.newDataAsText = text; + }; + + // Save data + DatastoreObject.prototype.save = function(callback) { + if (this.objectId === undefined) { + var that = this; + this.newMetadata["timestamp"] = this.newMetadata["creation_time"] = new Date().getTime(); + this.newMetadata["file_size"] = 0; + datastore.create(this.newMetadata, function(error, oid) { + if (error == null) { + that.objectId = oid; + } + }); + } else { + if (this.toload) { + this.getMetadata(null); + this.toload = false; + } + } + var callback_c = datastore.callbackChecker(callback); + this.newMetadata["timestamp"] = new Date().getTime(); + var sugar_settings = html5storage.getValue("sugar_settings"); + if (sugar_settings) { + this.newMetadata["buddy_name"] = sugar_settings.name; + this.newMetadata["buddy_color"] = sugar_settings.colorvalue; + } + html5storage.setValue(datastorePrefix + this.objectId, { + metadata: this.newMetadata, + text: this.newDataAsText + }); + callback_c(null, this.newMetadata); + }; + + datastore.DatastoreObject = DatastoreObject; + datastore.localStorage = html5storage; + + + // -- HTML5 local storage handling + + // Test if HTML5 storage is available + html5storage.test = function() { + return (typeof(Storage) !== "undefined" && typeof(window.localStorage) !== "undefined"); + }; + + // Set a value in the storage + html5storage.setValue = function(key, value) { + if (this.test()) { + try { + window.localStorage.setItem(key, JSON.stringify(value)); + } catch (err) {} + } + }; + + // Get a value in the storage + html5storage.getValue = function(key) { + if (this.test()) { + try { + return JSON.parse(window.localStorage.getItem(key)); + } catch (err) { + return null; + } + } + return null; + }; + + // Remove a value in the storage + html5storage.removeValue = function(key) { + if (this.test()) { + try { + window.localStorage.removeItem(key); + } catch (err) {} + } + }; + + return datastore; +}); diff --git a/activities/Cordova.activity/lib/sugar-web/datastore/sugaros.js b/activities/Cordova.activity/lib/sugar-web/datastore/sugaros.js new file mode 100644 index 000000000..ebb3f19f3 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/datastore/sugaros.js @@ -0,0 +1,224 @@ +define(["sugar-web/bus", "sugar-web/env"], function (bus, env) { + + 'use strict'; + + var datastore = {}; + + function DatastoreObject(objectId) { + this.objectId = objectId; + this.newMetadata = {}; + + this.ensureObjectId = function (callback) { + var that = this; + + env.getObjectId(function (objectId) { + if (objectId !== null && that.objectId === undefined) { + that.objectId = 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) { + var 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) { + var 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/Cordova.activity/lib/sugar-web/dictstore.js b/activities/Cordova.activity/lib/sugar-web/dictstore.js new file mode 100644 index 000000000..99bfc3328 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/dictstore.js @@ -0,0 +1,68 @@ +define(["sugar-web/activity/activity", "sugar-web/env"], function (activity, env) { + + 'use strict'; + + // This is a helper module that allows to persist key/value data + // using the standard localStorage object. + // + // Usage: + // ------ + // + // // 1. Setup: + // + // dictstore.init(onReadyCallback); + // + // // 2. Use localStorage directly, and then call save(): + // + // var value = localStorage['key']; + // localStorage['key'] = newValue; + // dictstore.save(onSavedCallback); + // + var dictstore = {}; + + dictstore.init = function (callback) { + + if (env.isStandalone()) { + // In standalone mode, use localStorage as is. + callback(); + + } else { + // In Sugar, set localStorage from the datastore. + localStorage.clear(); + + var onLoaded = function (error, metadata, jsonData) { + var data = JSON.parse(jsonData); + for (var i in data) { + localStorage[i] = data[i]; + } + + callback(); + + }; + activity.getDatastoreObject().loadAsText(onLoaded); + } + }; + + // Internally, the key/values are stored as text in the Sugar + // datastore, using the JSON format. + dictstore.save = function (callback) { + if (callback === undefined) { + callback = function () {}; + } + + if (env.isStandalone()) { + // In standalone mode, use localStorage as is. + callback(); + } else { + var datastoreObject = activity.getDatastoreObject(); + var jsonData = JSON.stringify(localStorage); + datastoreObject.setDataAsText(jsonData); + datastoreObject.save(function (error) { + callback(error); + }); + } + }; + + return dictstore; + +}); diff --git a/activities/Cordova.activity/lib/sugar-web/env.js b/activities/Cordova.activity/lib/sugar-web/env.js new file mode 100644 index 000000000..6fe972088 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/env.js @@ -0,0 +1,82 @@ +define(function () { + + 'use strict'; + + var env = {}; + + env.getEnvironment = function (callback) { + // FIXME: we assume this code runs on the same thread as the + // javascript executed from sugar-toolkit-gtk3 (python) + + if (env.isSugarizer()) { + var getUrlParameter = function(name) { + var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search); + return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); + }; + window.top.sugar = {}; + window.top.sugar.environment = { + activityId: getUrlParameter("aid"), + activityName: getUrlParameter("n"), + bundleId: getUrlParameter("a"), + objectId: getUrlParameter("o"), + sharedId: getUrlParameter("s") + }; + setTimeout(function () { + callback(null, window.top.sugar.environment); + }, 0); + } else if (env.isStandalone()) { + setTimeout(function () { + callback(null, {}); + }, 0); + } else { + var environmentCallback = function () { + callback(null, window.top.sugar.environment); + }; + + if (window.top.sugar) { + setTimeout(function () { + environmentCallback(); + }, 0); + } else { + window.top.sugar = {}; + window.top.sugar.onEnvironmentSet = function () { + environmentCallback(); + }; + } + } + }; + + env.getObjectId = function (callback) { + env.getEnvironment(function (error, environment) { + callback(environment.objectId); + }); + }; + + env.getURLScheme = function () { + return window.location.protocol; + }; + + env.getHost = function() { + return window.location.hostname; + }; + + env.isStandalone = function () { + var webActivityHost = "0.0.0.0"; + var currentHost = env.getHost(); + + return currentHost !== webActivityHost; + }; + + env.isSugarizer = function() { + if (typeof(Storage)!=="undefined" && typeof(window.localStorage)!=="undefined") { + try { + return (window.localStorage.getItem('sugar_settings') !== null); + } catch(err) { + return false; + } + } + return false; + }; + + return env; +}); diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/README.md b/activities/Cordova.activity/lib/sugar-web/graphics/README.md new file mode 100644 index 000000000..3a8206215 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/README.md @@ -0,0 +1,40 @@ +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 files. +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. + +To compile the CSS files do: + + lessc graphics/css/sugar-96dpi.less graphics/css/sugar-96dpi.css + lessc graphics/css/sugar-200dpi.less graphics/css/sugar-200dpi.css + +Be sure to compile them 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/Cordova.activity/lib/sugar-web/graphics/activitypalette.html b/activities/Cordova.activity/lib/sugar-web/graphics/activitypalette.html new file mode 100644 index 000000000..9ef0da124 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/activitypalette.html @@ -0,0 +1,9 @@ +
+ +
+
+ +
+
+ +
diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/activitypalette.js b/activities/Cordova.activity/lib/sugar-web/graphics/activitypalette.js new file mode 100644 index 000000000..e41ccf0ee --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/activitypalette.js @@ -0,0 +1,62 @@ +define(["sugar-web/graphics/palette", + "text!sugar-web/graphics/activitypalette.html"], function (palette, template) { + + 'use strict'; + + var activitypalette = {}; + + activitypalette.ActivityPalette = function (activityButton, + datastoreObject) { + + palette.Palette.call(this, activityButton); + + var activityTitle; + var descriptionLabel; + var descriptionBox; + + this.getPalette().id = "activity-palette"; + + var containerElem = document.createElement('div'); + containerElem.innerHTML = 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/Cordova.activity/lib/sugar-web/graphics/css/sugar-200dpi.css b/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-200dpi.css new file mode 100644 index 000000000..4255cdc88 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-200dpi.css @@ -0,0 +1,454 @@ +html { + height: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + position: relative; + height: 100%; + margin: 0; + font-family: sans-serif; + font-size: 10pt; + background-color: #c0c0c0; +} +.unselectable { + -webkit-user-select: none; + -moz-user-select: none; +} +.pull-right { + float: right; +} +a { + color: #0076c3; + text-decoration: none; +} +/* Toolbar */ +.toolbar { + height: 75px; + padding: 0 77px; + color: white; + background-color: #282828; + -webkit-user-select: none; + -moz-user-select: none; +} +/* Toolbar separator */ +.toolbar hr { + display: inline-block; + height: 45px; + margin: 4.5px; + margin-bottom: -21px; + border: 1px solid #808080; +} +/* Toolbar toolbutton */ +.toolbar .toolbutton { + position: relative; + width: 67px; + height: 67px; + margin: 4px 2px; + color: white; + color: transparent; + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + border: 0; + border-radius: 7.5px; +} +.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 { + position: absolute; + right: -4px; + bottom: -4px; + display: block; + width: 75px; + height: 15px; + background-color: transparent; + content: ""; +} +.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 { + position: absolute; + top: 75px; + bottom: 0; + width: 100%; + overflow-y: auto; + color: black; + background-color: #c0c0c0; +} +/* Button */ +button { + padding: 2px 4px; + line-height: 30px; + color: white; + background-color: #808080; + border: 2px solid transparent; + border-radius: 30px; + -webkit-user-select: none; + -moz-user-select: none; +} +button:hover { + background-color: #a2a2a2; + border-color: #a2a2a2; +} +button:active { + color: black; + background-color: white; + border-color: #808080; +} +button:focus { + border-color: white; +} +.toolbar button { + margin-top: 18.5px; +} +/* Button with icon */ +button.icon { + position: relative; + padding-left: 34px; +} +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 { + position: absolute; + display: inline-block; + width: 30px; + height: 30px; + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + background-size: 30px 30px; +} +button.icon span { + top: 2px; + left: 2px; +} +/* One line text input */ +input[type='text'] { + width: 225px; + padding: 4px; + line-height: 30px; + background-color: #e5e5e5; + border: 2px solid #e5e5e5; + border-radius: 30px; + outline: 0; +} +input[type='text']:focus { + background-color: white; +} +input[type='text']:disabled { + background-color: #808080; + border-color: #808080; +} +.toolbar input[type='text'], +.palette .row input[type='text'] { + margin-top: 16.5px; +} +.palette .row input[type='text']:nth-last-child(1) { + margin-top: 14.5px; +} +input[type='text'].expand { + width: calc(100% - 12px); +} +/* One line text input with buttons inside */ +.icon-input { + position: relative; + display: inline-block; +} +.icon-input input[type='text'] { + width: 187px; + padding-right: 42px; +} +.icon-input.expand { + width: 100%; +} +.icon-input.expand input[type='text'] { + width: calc(100% - 50px); +} +.icon-input button { + position: absolute; + width: 42px; + height: 42px; + padding: 0; + background-size: 30px 30px; +} +.icon-input button.right { + right: 0; + margin: 0 0 0 -42px; + border-radius: 0 30px 30px 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: 16.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'] { + height: 15px; + cursor: pointer; + background-color: #808080; + border-radius: 30px; + -webkit-appearance: none !important; +} +input[type='range']::-webkit-slider-thumb { + width: 30px; + height: 30px; + background-color: #c0c0c0; + border: 4px solid #808080; + border-radius: 15px; + -webkit-appearance: none !important; +} +input[type='range']::-webkit-slider-thumb:hover { + background-color: #e2e2e2; + border-color: #a2a2a2; +} +.toolbar input[type='range'] { + margin-top: 30px; +} +/* Label */ +label { + -webkit-user-select: none; + -moz-user-select: none; +} +/* Palette */ +.palette { + position: absolute; + color: white; + pointer-events: none; + background-color: transparent; +} +.palette-invoker { + width: 71px; + height: 73px; + background-color: black; + background-position: 2px 2px; + background-repeat: no-repeat; + background-size: 67px; + border: 2px solid #808080; + border-bottom: 0; +} +.palette-invoker:after { + position: absolute; + top: 60px; + left: 0; + display: block; + width: 75px; + height: 15px; + background-color: transparent; + background-image: url('../icons/emblems/arrow-up.svg'); + content: ""; +} +.palette-invoker:before { + position: absolute; + top: 75px; + left: 2px; + display: block; + width: 71px; + height: 2px; + background-color: black; + content: ""; +} +.palette .wrapper { + max-width: 371px; + min-width: 221px; + min-height: 71px; + pointer-events: auto; + background-color: black; + border: 2px solid #808080; +} +.palette .header { + height: 71px; + margin: 0 7.5px; + font-weight: bold; + line-height: 71px; + -webkit-user-select: none; + -moz-user-select: none; +} +.palette hr { + border: 1px solid #808080; +} +.palette hr.header-separator { + margin-top: 0; +} +.palette .row { + height: 75px; + margin: 0 7.5px; +} +.palette .row:nth-last-child(1) { + height: 71px; +} +.palette .row.small { + height: 30px; +} +.palette .row.expand { + height: auto; +} +/* Palette menu */ +.palette .menu { + padding: 0; + margin-top: 15px; + margin-bottom: 15px; + list-style-type: none; +} +.palette .menu button { + width: 100%; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; + line-height: 45px; + text-align: left; + background-color: transparent; + border: 0; + border-radius: 0; +} +.palette .menu button:hover { + color: white; + background-color: #808080; +} +.palette .menu button.icon { + padding-left: 49px; +} +.palette .menu button.icon span { + top: 0; + left: 0; + width: 45px; + height: 45px; +} +/* Scrollbar */ +::-webkit-scrollbar { + width: 15px; + background-color: #808080; +} +::-webkit-scrollbar-thumb { + background-color: white; + border: 2px solid #dddddd; + border-radius: 15px; +} +/* Grid for visual debugging and layout */ +.grid { + position: absolute; + top: 0; + left: 0; + background-color: transparent; +} +/* Checkbox and radio */ +input[type='checkbox'], +input[type='radio'] { + width: 30px; + height: 30px; + margin: 2px 2px 4px 2px; + vertical-align: middle; + background-position: center; + background-size: contain; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +.toolbar input[type='checkbox'], +.toolbar input[type='radio'] { + margin-top: 20.5px; + margin-bottom: 24.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 { + margin: 2px; + border: 2px solid #808080; +} +textarea.expand { + width: calc(100% - 12px); +} +/* Lists */ +ul.flat-list { + padding: 0; + margin: 0; + list-style-type: none; +} +ul.flat-list li { + height: 43px; + line-height: 43px; + background-color: white; + border-bottom: 2px dotted #c0c0c0; +} +ul.flat-list li:nth-last-child(1) { + border-bottom: none; +} +ul.flat-list.big li { + height: 58px; + line-height: 58px; +} +ul.flat-list.striped li:nth-child(odd) { + background-color: #e5e5e5; +} +/* ActivityPalette */ +#activity-palette .wrapper { + width: 371px; +} diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-200dpi.less b/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-200dpi.less new file mode 100644 index 000000000..614c5ed16 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-200dpi.less @@ -0,0 +1,2 @@ +@subcell-size: 15px; +@import "sugar.less"; diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-96dpi.css b/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-96dpi.css new file mode 100644 index 000000000..a8144b8e6 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-96dpi.css @@ -0,0 +1,454 @@ +html { + height: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + position: relative; + height: 100%; + margin: 0; + font-family: sans-serif; + font-size: 10pt; + background-color: #c0c0c0; +} +.unselectable { + -webkit-user-select: none; + -moz-user-select: none; +} +.pull-right { + float: right; +} +a { + color: #0076c3; + text-decoration: none; +} +/* Toolbar */ +.toolbar { + height: 55px; + padding: 0 57px; + color: white; + background-color: #282828; + -webkit-user-select: none; + -moz-user-select: none; +} +/* Toolbar separator */ +.toolbar hr { + display: inline-block; + height: 33px; + margin: 2.5px; + margin-bottom: -17px; + border: 1px solid #808080; +} +/* Toolbar toolbutton */ +.toolbar .toolbutton { + position: relative; + width: 47px; + height: 47px; + margin: 4px 2px; + color: white; + color: transparent; + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + border: 0; + border-radius: 5.5px; +} +.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 { + position: absolute; + right: -4px; + bottom: -4px; + display: block; + width: 55px; + height: 11px; + background-color: transparent; + content: ""; +} +.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 { + position: absolute; + top: 55px; + bottom: 0; + width: 100%; + overflow-y: auto; + color: black; + background-color: #c0c0c0; +} +/* Button */ +button { + padding: 2px 4px; + line-height: 22px; + color: white; + background-color: #808080; + border: 2px solid transparent; + border-radius: 22px; + -webkit-user-select: none; + -moz-user-select: none; +} +button:hover { + background-color: #a2a2a2; + border-color: #a2a2a2; +} +button:active { + color: black; + background-color: white; + 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 { + position: absolute; + display: inline-block; + width: 22px; + height: 22px; + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + background-size: 22px 22px; +} +button.icon span { + top: 2px; + left: 2px; +} +/* One line text input */ +input[type='text'] { + width: 165px; + padding: 4px; + line-height: 22px; + background-color: #e5e5e5; + border: 2px solid #e5e5e5; + border-radius: 22px; + outline: 0; +} +input[type='text']:focus { + background-color: white; +} +input[type='text']:disabled { + background-color: #808080; + border-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 { + position: relative; + display: inline-block; +} +.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 { + position: absolute; + width: 34px; + height: 34px; + padding: 0; + background-size: 22px 22px; +} +.icon-input button.right { + right: 0; + margin: 0 0 0 -34px; + border-radius: 0 22px 22px 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'] { + height: 11px; + cursor: pointer; + background-color: #808080; + border-radius: 22px; + -webkit-appearance: none !important; +} +input[type='range']::-webkit-slider-thumb { + width: 22px; + height: 22px; + background-color: #c0c0c0; + border: 4px solid #808080; + border-radius: 11px; + -webkit-appearance: none !important; +} +input[type='range']::-webkit-slider-thumb:hover { + background-color: #e2e2e2; + border-color: #a2a2a2; +} +.toolbar input[type='range'] { + margin-top: 22px; +} +/* Label */ +label { + -webkit-user-select: none; + -moz-user-select: none; +} +/* Palette */ +.palette { + position: absolute; + color: white; + pointer-events: none; + background-color: transparent; +} +.palette-invoker { + width: 51px; + height: 53px; + background-color: black; + background-position: 2px 2px; + background-repeat: no-repeat; + background-size: 47px; + border: 2px solid #808080; + border-bottom: 0; +} +.palette-invoker:after { + position: absolute; + top: 44px; + left: 0; + display: block; + width: 55px; + height: 11px; + background-color: transparent; + background-image: url('../icons/emblems/arrow-up.svg'); + content: ""; +} +.palette-invoker:before { + position: absolute; + top: 55px; + left: 2px; + display: block; + width: 51px; + height: 2px; + background-color: black; + content: ""; +} +.palette .wrapper { + max-width: 271px; + min-width: 161px; + min-height: 51px; + pointer-events: auto; + background-color: black; + border: 2px solid #808080; +} +.palette .header { + height: 51px; + margin: 0 5.5px; + font-weight: bold; + line-height: 51px; + -webkit-user-select: none; + -moz-user-select: none; +} +.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 { + padding: 0; + margin-top: 11px; + margin-bottom: 11px; + list-style-type: none; +} +.palette .menu button { + width: 100%; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; + line-height: 33px; + text-align: left; + background-color: transparent; + border: 0; + border-radius: 0; +} +.palette .menu button:hover { + color: white; + background-color: #808080; +} +.palette .menu button.icon { + padding-left: 37px; +} +.palette .menu button.icon span { + top: 0; + left: 0; + width: 33px; + height: 33px; +} +/* Scrollbar */ +::-webkit-scrollbar { + width: 11px; + background-color: #808080; +} +::-webkit-scrollbar-thumb { + background-color: white; + border: 2px solid #dddddd; + border-radius: 11px; +} +/* Grid for visual debugging and layout */ +.grid { + position: absolute; + top: 0; + left: 0; + background-color: transparent; +} +/* Checkbox and radio */ +input[type='checkbox'], +input[type='radio'] { + width: 22px; + height: 22px; + margin: 2px 2px 4px 2px; + vertical-align: middle; + background-position: center; + background-size: contain; + -webkit-appearance: none; + -moz-appearance: none; + 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 { + margin: 2px; + border: 2px solid #808080; +} +textarea.expand { + width: calc(100% - 12px); +} +/* Lists */ +ul.flat-list { + padding: 0; + margin: 0; + list-style-type: none; +} +ul.flat-list li { + height: 31px; + line-height: 31px; + background-color: white; + border-bottom: 2px dotted #c0c0c0; +} +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/Cordova.activity/lib/sugar-web/graphics/css/sugar-96dpi.less b/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-96dpi.less new file mode 100644 index 000000000..ea91f4920 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar-96dpi.less @@ -0,0 +1,2 @@ +@subcell-size: 11px; +@import "sugar.less"; diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar.less b/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar.less new file mode 100644 index 000000000..8378b2c3f --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/css/sugar.less @@ -0,0 +1,587 @@ +// recess: ignore + +@toolbar-grey: #282828; +@button-grey: #808080; +@panel-grey: #C0C0C0; +@text-field-grey: #E5E5E5; +@link-blue: #0076C3; + +@line-width: 2px; +@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); + +html { + height: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body { + position: relative; + height: 100%; + margin: 0; + font-family: sans-serif; + font-size: @font-size; + background-color: @panel-grey; +} + +.unselectable { + -webkit-user-select: none; + -moz-user-select: none; +} + +.pull-right { + float: right; +} + +a { + color: @link-blue; + text-decoration: none; +} + +/* Toolbar */ + +.toolbar { + height: @toolbar-height; + padding: 0 @toolbutton-size + (5 * @line-width); + color: white; + background-color: @toolbar-grey; + .unselectable; +} + +/* Toolbar separator */ + +.toolbar hr { + display: inline-block; + height: 3 * @subcell-size; + margin: (@subcell-size - (@line-width * 3)) / 2; + margin-bottom: -1 * (@subcell-size + (@line-width * 3)); + border: (@line-width / 2) solid @button-grey; +} + +/* Toolbar toolbutton */ + +.toolbar .toolbutton { + position: relative; + width: @toolbutton-size; + height: @toolbutton-size; + margin: (2 * @line-width) @line-width; + color: white; + color: transparent; + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + border: 0; + border-radius: @subcell-size / 2; +} + +.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 { + position: absolute; + right: -2 * @line-width; + bottom: -2 * @line-width; + display: block; + width: @cell-size; + height: @subcell-size; + background-color: transparent; + content: ""; +} + +.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 { + position: absolute; + top: @toolbar-height; + bottom: 0; + width: 100%; + overflow-y: auto; + color: black; + background-color: @panel-grey; +} + +/* Button */ + +button { + padding: @line-width (2 * @line-width); + line-height: @icon-small-size; + color: white; + background-color: @button-grey; + border: @line-width solid transparent; + border-radius: 2 * @subcell-size; + .unselectable; +} + +button:hover { + background-color: @button-grey + #222; + border-color: @button-grey + #222; +} + +button:active { + color: black; + background-color: white; + 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 { + position: absolute; + display: inline-block; + width: @icon-small-size; + height: @icon-small-size; + background-color: transparent; + background-position: center; + background-repeat: no-repeat; + background-size: @icon-small-size @icon-small-size; +} + +button.icon span { + top: @line-width; + left: @line-width; +} + +/* One line text input */ + +input[type='text'] { + width: @input-text-width; + padding: @input-text-padding; + line-height: @input-text-line-height; + background-color: @text-field-grey; + border: @line-width solid @text-field-grey; + border-radius: 2 * @subcell-size; + outline: 0; +} + +input[type='text']:focus { + background-color: white; +} + +input[type='text']:disabled { + background-color: @button-grey; + border-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 { + position: relative; + display: inline-block; +} + +.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 { + position: absolute; + width: @button-small-size; + height: @button-small-size; + padding: 0; + background-size: @icon-small-size @icon-small-size; +} + +.icon-input button.right { + right: 0; + margin: 0 0 0 -@button-small-size; + border-radius: 0 (2 * @subcell-size) (2 * @subcell-size) 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'] { + height: @subcell-size; + cursor: pointer; + background-color: @button-grey; + border-radius: 2 * @subcell-size; + -webkit-appearance: none !important; +} + +input[type='range']::-webkit-slider-thumb { + width: @icon-small-size; + height: @icon-small-size; + background-color: @panel-grey; + border: (2 * @line-width) solid @button-grey; + border-radius: @subcell-size; + -webkit-appearance: none !important; +} + +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 { + position: absolute; + color: white; + pointer-events: none; + background-color: transparent; +} + +.palette-invoker { + width: @cell-size - 2 * @line-width; + height: @cell-size - @line-width; + background-color: black; + background-position: @line-width @line-width; + background-repeat: no-repeat; + background-size: @toolbutton-size; + border: @line-width solid @button-grey; + border-bottom: 0; +} + +.palette-invoker:after { + position: absolute; + top: 4 * @subcell-size; + left: 0; + display: block; + width: @cell-size; + height: @subcell-size; + background-color: transparent; + background-image: url('../icons/emblems/arrow-up.svg'); + content: ""; +} + +.palette-invoker:before { + position: absolute; + top: @cell-size; + left: @line-width; + display: block; + width: @cell-size - 2 * @line-width; + height: @line-width; + background-color: black; + content: ""; +} + +.palette .wrapper { + 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; + background-color: black; + border: @line-width solid @button-grey; +} + +.palette .header { + height: @cell-size - 2 * @line-width; + margin: 0 (@subcell-size / 2); + font-weight: bold; + line-height: @cell-size - 2 * @line-width; + .unselectable; +} + +.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 { + padding: 0; + margin-top: @subcell-size; + margin-bottom: @subcell-size; + list-style-type: none; +} + +.palette .menu button { + width: 100%; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; + line-height: 3 * @subcell-size; + text-align: left; + background-color: transparent; + border: 0; + border-radius: 0; +} + +.palette .menu button:hover { + color: white; + background-color: @button-grey; +} + +.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 { + width: @subcell-size; + background-color: @button-grey; +} + +::-webkit-scrollbar-thumb { + background-color: white; + border: @line-width solid #ddd; + border-radius: @subcell-size; +} + +/* Grid for visual debugging and layout */ + +.grid { + position: absolute; + top: 0; + left: 0; + background-color: transparent; +} + +.appearance(@value) { + -webkit-appearance: @value; + -moz-appearance: @value; + appearance: @value; +} + +/* Checkbox and radio */ + +input[type='checkbox'], +input[type='radio'] { + width: @icon-small-size; + height: @icon-small-size; + margin: @line-width @line-width (2 * @line-width) @line-width; + vertical-align: middle; + background-position: center; + background-size: contain; + .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 { + margin: @line-width; + border: @line-width solid @button-grey; +} + +textarea.expand { + width: calc(~"100%" - (6 * (@line-width))); +} + +/* Lists */ + +ul.flat-list { + padding: 0; + margin: 0; + list-style-type: none; +} + +ul.flat-list li { + height: 3 * @subcell-size - @line-width; + line-height: 3 * @subcell-size - @line-width; + background-color: white; + border-bottom: @line-width dotted @panel-grey; +} + +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; +} diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/grid.js b/activities/Cordova.activity/lib/sugar-web/graphics/grid.js new file mode 100644 index 000000000..3322bb8c5 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/grid.js @@ -0,0 +1,57 @@ +define(function () { + + 'use strict'; + + 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/Cordova.activity/lib/sugar-web/graphics/icon.js b/activities/Cordova.activity/lib/sugar-web/graphics/icon.js new file mode 100644 index 000000000..18b87bd65 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icon.js @@ -0,0 +1,89 @@ +define(function () { + + 'use strict'; + + 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 + var res = style.backgroundImage.slice(4, -1); + var last = res.length-1; + if (res[0] == '"' && res[last] == '"') { + res = res.slice(1, last); + } + return res; + } + + 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/Cordova.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg new file mode 100644 index 000000000..11b82e817 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/activity-stop.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg new file mode 100644 index 000000000..8ec1223a7 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked-selected.svg @@ -0,0 +1,27 @@ + + + +]> + + + + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg new file mode 100644 index 000000000..3cfce18f6 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-checked.svg @@ -0,0 +1,27 @@ + + + +]> + + + + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg new file mode 100644 index 000000000..2263279eb --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked-selected.svg @@ -0,0 +1,22 @@ + + +]> + + + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg new file mode 100644 index 000000000..e1587823e --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/checkbox-unchecked.svg @@ -0,0 +1,22 @@ + + +]> + + + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg new file mode 100644 index 000000000..dc0d688f6 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel-active.svg @@ -0,0 +1,6 @@ + + +]> + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg new file mode 100644 index 000000000..dab4ae2d9 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-cancel.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg new file mode 100644 index 000000000..45de8401a --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-ok-active.svg @@ -0,0 +1,6 @@ + + +]> + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg new file mode 100644 index 000000000..69e5a2a13 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/dialog-ok.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg new file mode 100644 index 000000000..467509e75 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-active.svg @@ -0,0 +1,23 @@ + + + +]> + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg new file mode 100644 index 000000000..55b4cdb87 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel-disabled.svg @@ -0,0 +1,21 @@ + + + +]> + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg new file mode 100644 index 000000000..5339a7e94 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/entry-cancel.svg @@ -0,0 +1,21 @@ + + + +]> + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg new file mode 100644 index 000000000..c1d1085a0 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-active-selected.svg @@ -0,0 +1,31 @@ + + + +]> + + + + + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg new file mode 100644 index 000000000..a5fe59156 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-active.svg @@ -0,0 +1,31 @@ + + + +]> + + + + + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg new file mode 100644 index 000000000..a24b97e65 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio-selected.svg @@ -0,0 +1,26 @@ + + + +]> + + + + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio.svg new file mode 100644 index 000000000..d25028624 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/radio.svg @@ -0,0 +1,26 @@ + + + +]> + + + + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg new file mode 100644 index 000000000..b88462ff5 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-groups.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg new file mode 100644 index 000000000..5578fecbf --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-home.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg new file mode 100644 index 000000000..8d3f8d134 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/actions/zoom-neighborhood.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg new file mode 100644 index 000000000..2de1a9e34 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/emblems/arrow-down.svg @@ -0,0 +1,20 @@ + + + +]> + + + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg b/activities/Cordova.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg new file mode 100644 index 000000000..a977f4a86 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/icons/emblems/arrow-up.svg @@ -0,0 +1,20 @@ + + + +]> + + + + + diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/menupalette.html b/activities/Cordova.activity/lib/sugar-web/graphics/menupalette.html new file mode 100644 index 000000000..daf5fd579 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/menupalette.html @@ -0,0 +1,8 @@ +{{#.}} +
  • + +
  • +{{/.}} diff --git a/activities/Cordova.activity/lib/sugar-web/graphics/menupalette.js b/activities/Cordova.activity/lib/sugar-web/graphics/menupalette.js new file mode 100644 index 000000000..27fe8edf8 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/menupalette.js @@ -0,0 +1,53 @@ +define(["sugar-web/graphics/palette", + "text!sugar-web/graphics/menupalette.html", "mustache"], function (palette, template, mustache) { + + 'use strict'; + + var menupalette = {}; + + menupalette.MenuPalette = function (invoker, primaryText, menuData) { + palette.Palette.call(this, invoker, primaryText); + + this.selectItemEvent = document.createEvent("CustomEvent"); + this.selectItemEvent.initCustomEvent('selectItem', true, true, { + 'item': undefined + }); + + var menuElem = document.createElement('ul'); + menuElem.className = "menu"; + menuElem.innerHTML = mustache.render(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/Cordova.activity/lib/sugar-web/graphics/palette.js b/activities/Cordova.activity/lib/sugar-web/graphics/palette.js new file mode 100644 index 000000000..be5cf4bfd --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/palette.js @@ -0,0 +1,182 @@ +define(function () { + + 'use strict'; + + 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")) { + var 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++) { + var 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/Cordova.activity/lib/sugar-web/graphics/radiobuttonsgroup.js b/activities/Cordova.activity/lib/sugar-web/graphics/radiobuttonsgroup.js new file mode 100644 index 000000000..782a17636 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/radiobuttonsgroup.js @@ -0,0 +1,62 @@ +define(function () { + + 'use strict'; + + 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 (var 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/Cordova.activity/lib/sugar-web/graphics/xocolor.js b/activities/Cordova.activity/lib/sugar-web/graphics/xocolor.js new file mode 100644 index 000000000..96d9a63b1 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/graphics/xocolor.js @@ -0,0 +1,731 @@ +define(function () { + + 'use strict'; + + 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/Cordova.activity/lib/sugar-web/package.json b/activities/Cordova.activity/lib/sugar-web/package.json new file mode 100644 index 000000000..f2e3555b2 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/package.json @@ -0,0 +1,10 @@ +{ + "volo": { + "baseUrl": "lib", + "dependencies": { + "webL10n": "github:sugarlabs/webL10n", + "mustache": "github:janl/mustache.js/0.7.2", + "text": "github:requirejs/text" + } + } +} diff --git a/activities/Cordova.activity/lib/sugar-web/presence.js b/activities/Cordova.activity/lib/sugar-web/presence.js new file mode 100644 index 000000000..7cce449f6 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/presence.js @@ -0,0 +1,254 @@ +define(function (require) { + // Message type constants + var msgInit = 0; + var msgListUsers = 1; + var msgCreateSharedActivity = 2; + var msgListSharedActivities = 3; + var msgJoinSharedActivity = 4; + var msgLeaveSharedActivity = 5; + var msgOnConnectionClosed = 6; + var msgOnSharedActivityUserChanged = 7; + var msgSendMessage = 8; + + // Array for callbacks on each type + var callbackArray = []; + + // User and shared info storage + var userInfo = null; + var sharedInfo = null; + + // Connection object + function SugarPresence() { + // Init callbacks + var emptyCallback = function() {}; + var listUsersCallback = emptyCallback; + var createSharedActivityCallback = emptyCallback; + var listSharedActivityCallback = emptyCallback; + var joinSharedActivity = emptyCallback; + var leaveSharedActivity = emptyCallback; + var onConnectionClosed = emptyCallback; + var onSharedActivityUserChanged = emptyCallback; + var receivedDataCallback = emptyCallback; + callbackArray = [emptyCallback, listUsersCallback, createSharedActivityCallback, + listSharedActivityCallback, joinSharedActivity, leaveSharedActivity, + onConnectionClosed, onSharedActivityUserChanged, receivedDataCallback + ]; + this.socket = null; + + // Handle message received from server + this.registerMessageHandler = function() { + // Get message content + this.socket.onmessage = function(event) { + // Convert message to JSON + var edata = event.data; + try { + var json = JSON.parse(edata); + } catch (e) { + console.log('Presence API error, this doesn\'t look like a valid JSON: ', edata); + return; + } + + // Call the matching callback + if (json.type < callbackArray.length) + callbackArray[json.type](json.data); + else + console.log('Presence API error, unknown callback type:'+json.type); + }; + } + + // Register user to the server + this.registerUser = function() { + this.socket.send(JSON.stringify(this.userInfo)); + } + + } + + // Create presence object + var presence = new SugarPresence(); + + // Test if connected to network + SugarPresence.prototype.isConnected = function() { + return (this.socket != null); + } + + // Get user info + SugarPresence.prototype.getUserInfo = function() { + return this.userInfo; + } + + // Get shared activity info + SugarPresence.prototype.getSharedInfo = function() { + return this.sharedInfo; + } + + // Join network function + SugarPresence.prototype.joinNetwork = function(callback) { + // Check WebSocket support + if (!window.WebSocket){ + console.log('WebSocket not supported'); + callback({code: -1}, presence); + } + + // Get server name + var server = location.hostname; + if (localStorage.sugar_settings) { + var sugarSettings = JSON.parse(localStorage.sugar_settings); + if (sugarSettings.server) { + server = sugarSettings.server; + var endName = server.indexOf(':') + if (endName == -1) endName = server.indexOf('/'); + if (endName == -1) endName = server.length; + server = server.substring(0, endName); + } + } + + // Connect to server + this.socket = new WebSocket('ws://'+server+':8039'); + this.socket.onerror = function(error) { + console.log('WebSocket Error: ' + error); + callback(error, presence); + this.socket = null; + }; + + // When connection open, send user info + var that = this; + this.socket.onopen = function(event) { + var sugarSettings = JSON.parse(localStorage.sugar_settings); + that.userInfo = { + name: sugarSettings.name, + networkId: sugarSettings.networkId, + colorvalue: sugarSettings.colorvalue + }; + that.registerMessageHandler(); + that.registerUser(); + callback(null, presence); + }; + + // When connection closed, call closed callback + this.socket.onclose = function(event) { + callbackArray[msgOnConnectionClosed](event); + }; + } + + // Leave network + SugarPresence.prototype.leaveNetwork = function() { + if (!this.isConnected()) + return; + this.socket.close(); + } + + // List all users. Will receive an array of users. + SugarPresence.prototype.listUsers = function(callback) { + if (!this.isConnected()) + return; + + // Register call back + callbackArray[msgListUsers] = callback; + + // Send list user message + var sjson = JSON.stringify({ + type: msgListUsers + }); + this.socket.send(sjson); + } + + // Create a shared activity. Will receive a unique group id. + SugarPresence.prototype.createSharedActivity = function(activityId, callback) { + if (!this.isConnected()) + return; + + // Register call back + var that = this; + callbackArray[msgCreateSharedActivity] = function(data) { + that.sharedInfo = { id: data }; + callback(data); + } + + // Send create shared activity message + var sjson = JSON.stringify({ + type: msgCreateSharedActivity, + activityId: activityId + }); + this.socket.send(sjson); + } + + // List all shared activities. Will receive an array of each shared activities and users connected + SugarPresence.prototype.listSharedActivities = function(callback) { + if (!this.isConnected()) + return; + + // Register call back + callbackArray[msgListSharedActivities] = callback; + + // Send list shared activities message + var sjson = JSON.stringify({ + type: msgListSharedActivities + }); + this.socket.send(sjson); + } + + // Join a shared activity. Will receive group properties or null + SugarPresence.prototype.joinSharedActivity = function(group, callback) { + if (!this.isConnected()) + return; + + // Register call back + var that = this; + callbackArray[msgJoinSharedActivity] = function(data) { + that.sharedInfo = data; + callback(data); + } + + // Send join shared activity message + var sjson = JSON.stringify({ + type: msgJoinSharedActivity, + group: group + }); + this.socket.send(sjson); + } + + // Leave shared activities + SugarPresence.prototype.leaveSharedActivity = function(group, callback) { + if (!this.isConnected()) + return; + + // Register call back + callbackArray[msgLeaveSharedActivity] = callback; + + // Send leave shared activity message + var sjson = JSON.stringify({ + type: msgLeaveSharedActivity, + group: group + }); + this.socket.send(sjson); + } + + // Register connection closed event + SugarPresence.prototype.onConnectionClosed = function(callback) { + callbackArray[msgOnConnectionClosed] = callback; + } + + // Register shared activity user changed event + SugarPresence.prototype.onSharedActivityUserChanged = function(callback) { + callbackArray[msgOnSharedActivityUserChanged] = callback; + } + + // Send message to a group + SugarPresence.prototype.sendMessage = function(group, data) {; + if (!this.isConnected()) + return; + var sjson = JSON.stringify({ + type: msgSendMessage, + group: group, + data: data + }); + this.socket.send(sjson); + } + + // Register data received message + SugarPresence.prototype.onDataReceived = function(callback) { + callbackArray[msgSendMessage] = callback; + } + + return presence; +}); diff --git a/activities/Cordova.activity/lib/sugar-web/test/functional/datastoreSpec.js b/activities/Cordova.activity/lib/sugar-web/test/functional/datastoreSpec.js new file mode 100644 index 000000000..530d3f01c --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/functional/datastoreSpec.js @@ -0,0 +1,253 @@ +define(["sugar-web/bus", "sugar-web/env", "sugar-web/datastore"], function (bus, env, datastore) { + + 'use strict'; + + var defaultTimeoutInterval = jasmine.getEnv().defaultTimeoutInterval; + + describe("datastore object", function () { + + beforeEach(function () { + // FIXME: due to db initialization, + // the very first save() call may take a while + jasmine.getEnv().defaultTimeoutInterval = 10000; + spyOn(env, 'isStandalone').andReturn(false); + bus.listen(); + }); + + afterEach(function () { + jasmine.getEnv().defaultTimeoutInterval = defaultTimeoutInterval; + bus.close(); + }); + + it("should be able to set and get metadata", function () { + var saved; + var gotMetadata; + var datastoreObject; + var objectId; + var testTitle = "hello"; + + runs(function () { + saved = false; + + datastoreObject = new datastore.DatastoreObject(); + datastoreObject.setMetadata({ + title: testTitle + }); + + datastoreObject.save(function () { + saved = true; + objectId = datastoreObject.objectId; + }); + }); + + waitsFor(function () { + return saved; + }, "should have saved the object"); + + runs(function () { + gotMetadata = false; + + datastoreObject = new datastore.DatastoreObject(objectId); + datastoreObject.getMetadata(function (error, metadata) { + expect(metadata.title).toEqual(testTitle); + gotMetadata = true; + }); + }); + + waitsFor(function () { + return gotMetadata; + }, "should have got the object metadata"); + }); + + it("should be able to save and load text", function () { + var saved; + var gotMetadata; + var datastoreObject; + var objectId; + var testText = "hello"; + + runs(function () { + saved = false; + + datastoreObject = new datastore.DatastoreObject(); + datastoreObject.setDataAsText(testText); + + datastoreObject.save(function () { + saved = true; + objectId = datastoreObject.objectId; + }); + }); + + waitsFor(function () { + return saved; + }, "should have saved the object"); + + runs(function () { + gotMetadata = false; + + function onLoaded(error, metadata, text) { + expect(text).toEqual(testText); + gotMetadata = true; + } + + datastoreObject = new datastore.DatastoreObject(objectId); + datastoreObject.loadAsText(onLoaded); + }); + + waitsFor(function () { + return gotMetadata; + }, "should have got the object metadata"); + }); + + }); + + describe("datastore", function () { + + beforeEach(function () { + // FIXME: due to db initialization, + // the very first save() call may take a while + jasmine.getEnv().defaultTimeoutInterval = 10000; + spyOn(env, 'isStandalone').andReturn(false); + bus.listen(); + }); + + afterEach(function () { + jasmine.getEnv().defaultTimeoutInterval = defaultTimeoutInterval; + bus.close(); + }); + + it("should be able to create an object", function () { + var wasCreated; + + runs(function () { + wasCreated = false; + + function onCreated(error, objectId) { + expect(objectId).toEqual(jasmine.any(String)); + wasCreated = true; + } + + datastore.create({}, onCreated); + }); + + waitsFor(function () { + return wasCreated; + }, "the object should be created"); + }); + + it("should be able to set object metadata", function () { + var metadataSet; + var gotMetadata; + var objectId; + var testTitle = "hello"; + + runs(function () { + function onMetadataSet(error) { + expect(error).toBeNull(); + metadataSet = true; + } + + function onCreated(error, createdObjectId) { + objectId = createdObjectId; + + var metadata = { + title: testTitle + }; + datastore.setMetadata(objectId, metadata, onMetadataSet); + } + + metadataSet = false; + + datastore.create({}, onCreated); + }); + + waitsFor(function () { + return metadataSet; + }, "metadata should be set"); + + runs(function () { + function onGotMetadata(error, metadata) { + expect(metadata.title).toEqual(testTitle); + gotMetadata = true; + } + + gotMetadata = false; + + datastore.getMetadata(objectId, onGotMetadata); + }); + + waitsFor(function () { + return gotMetadata; + }, "should have got object metadata"); + }); + + it("should be able to get object metadata", function () { + var gotMetadata = false; + var testTitle = "hello"; + + runs(function () { + function onGotMetadata(error, metadata) { + expect(metadata.title).toEqual(testTitle); + gotMetadata = true; + } + + function onCreated(error, objectId) { + datastore.getMetadata(objectId, onGotMetadata); + } + + datastore.create({ + title: testTitle + }, onCreated); + }); + + waitsFor(function () { + return gotMetadata; + }, "should have got object metadata"); + }); + + it("should be able to load an object", function () { + var wasLoaded = false; + var objectId = null; + var inputStream = null; + var objectData = null; + var testData = new Uint8Array([1, 2, 3, 4]); + + runs(function () { + function onStreamClose(error) { + expect(objectData).toEqual(testData.buffer); + wasLoaded = true; + } + + function onStreamRead(error, data) { + objectData = data; + } + + function onLoaded(error, metadata, loadedInputStream) { + inputStream = loadedInputStream; + inputStream.read(8192, onStreamRead); + inputStream.close(onStreamClose); + } + + function onClosed(error) { + datastore.load(objectId, onLoaded); + } + + function onSaved(error, outputStream) { + outputStream.write(testData); + outputStream.close(onClosed); + } + + function onCreated(error, createdObjectId) { + objectId = createdObjectId; + datastore.save(objectId, {}, onSaved); + } + + datastore.create({}, onCreated); + }); + + waitsFor(function () { + return wasLoaded; + }, "the object should be loaded"); + }); + }); +}); diff --git a/activities/Cordova.activity/lib/sugar-web/test/functional/toolkitContractSpec.js b/activities/Cordova.activity/lib/sugar-web/test/functional/toolkitContractSpec.js new file mode 100644 index 000000000..09b83b151 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/functional/toolkitContractSpec.js @@ -0,0 +1,31 @@ +define(["sugar-web/env"], function (env) { + + 'use strict'; + + describe("Environment object", function () { + + it("should have valid properties", function () { + //FIXME: we shouldn't stub this here. + //current implementation of isStandalone fails with sugar-web-test + spyOn(env, 'isStandalone').andReturn(false); + + var expectedEnv; + + runs(function () { + env.getEnvironment(function (error, environment) { + expectedEnv = environment; + }); + }); + + waitsFor(function () { + return expectedEnv !== undefined; + }, "should get sugar environment"); + + runs(function () { + expect(expectedEnv.bundleId).not.toBeUndefined(); + expect(expectedEnv.activityId).not.toBeUndefined(); + expect(expectedEnv.activityName).not.toBeUndefined(); + }); + }); + }); +}); diff --git a/activities/Cordova.activity/lib/sugar-web/test/graphics/iconSpec.js b/activities/Cordova.activity/lib/sugar-web/test/graphics/iconSpec.js new file mode 100644 index 000000000..18fb353b8 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/graphics/iconSpec.js @@ -0,0 +1,59 @@ +define(["sugar-web/graphics/icon"], function (icon) { + + 'use strict'; + + describe("icon", function () { + var wasLoaded; + var iconUrlResult; + + it("should be able to change icon more than once", function () { + var elem = document.createElement('div'); + var iconUrl; + + function callback(url) { + iconUrlResult = url; + wasLoaded = true; + } + + runs(function () { + wasLoaded = false; + iconUrl = "/base/graphics/icons/actions/dialog-ok-active.svg"; + var iconInfo = { + "uri": iconUrl, + "strokeColor": '#B20008', + "fillColor": '#FF2B34' + }; + icon.load(iconInfo, callback); + }); + + waitsFor(function () { + return wasLoaded; + }, "icon loaded"); + + runs(function () { + expect(iconUrlResult).not.toBe(iconUrl); + }); + + runs(function () { + wasLoaded = false; + iconUrl = iconUrlResult; + var iconInfo = { + "uri": iconUrl, + "strokeColor": '#FF2B34', + "fillColor": '#B20008' + }; + icon.load(iconInfo, callback); + }); + + waitsFor(function () { + return wasLoaded; + }, "icon loaded"); + + runs(function () { + expect(iconUrlResult).not.toBe(iconUrl); + }); + + }); + }); + +}); diff --git a/activities/Cordova.activity/lib/sugar-web/test/graphics/menupaletteSpec.js b/activities/Cordova.activity/lib/sugar-web/test/graphics/menupaletteSpec.js new file mode 100644 index 000000000..3f4758bb8 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/graphics/menupaletteSpec.js @@ -0,0 +1,78 @@ +define(["sugar-web/graphics/menupalette", "sugar-web/graphics/palette"], function (menupalette, palette) { + + 'use strict'; + + describe("menupalette", function () { + + var invoker; + var menuData; + var menuPalette; + + beforeEach(function () { + invoker = document.createElement('button'); + + menuData = [ + { + label: "One", + id: "one-button", + icon: true + }, + { + label: "Two", + id: "two-button", + icon: true + }, + { + label: "Three", + id: "three-button", + icon: true + } + ]; + + menuPalette = new menupalette.MenuPalette(invoker, undefined, + menuData); + }); + + it("should have a fixed number of clickable items", function () { + var buttons = menuPalette.getPalette(). + querySelectorAll('.container button'); + expect(buttons.length).toBe(menuData.length); + }); + + it("should emit a signal with the clicked item", function () { + var button; + var buttonSelected; + + function onItemSelected(event) { + button = event.detail.target; + buttonSelected = true; + } + + runs(function () { + buttonSelected = false; + menuPalette.addEventListener('selectItem', onItemSelected); + + var buttons = menuPalette.getPalette(). + querySelectorAll('.container button'); + buttons[1].click(); + }); + + waitsFor(function () { + return buttonSelected; + }, "should have selected a button"); + + runs(function () { + expect(button.id).toBe("two-button"); + }); + }); + + it("should be an instance of the child class", function () { + expect(menuPalette instanceof menupalette.MenuPalette).toBe(true); + }); + + it("should be an instance of the parent class", function () { + expect(menuPalette instanceof palette.Palette).toBe(true); + }); + + }); +}); diff --git a/activities/Cordova.activity/lib/sugar-web/test/graphics/paletteSpec.js b/activities/Cordova.activity/lib/sugar-web/test/graphics/paletteSpec.js new file mode 100644 index 000000000..a50fd32bb --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/graphics/paletteSpec.js @@ -0,0 +1,36 @@ +define(["sugar-web/graphics/palette"], function (palette) { + + 'use strict'; + + describe("palette", function () { + it("should start down", function () { + var invoker = document.createElement('button'); + var myPalette = new palette.Palette(invoker); + expect(myPalette.isDown()).toBe(true); + }); + + it("should toggle", function () { + var invoker = document.createElement('button'); + var myPalette = new palette.Palette(invoker); + myPalette.toggle(); + expect(myPalette.isDown()).toBe(false); + myPalette.toggle(); + expect(myPalette.isDown()).toBe(true); + }); + + it("if one palette in a group popups, the others popdown", function () { + var invokerA = document.createElement('button'); + var invokerB = document.createElement('button'); + var myPaletteA = new palette.Palette(invokerA); + var myPaletteB = new palette.Palette(invokerB); + myPaletteA.toggle(); + expect(myPaletteA.isDown()).toBe(false); + expect(myPaletteB.isDown()).toBe(true); + myPaletteB.toggle(); + expect(myPaletteA.isDown()).toBe(true); + expect(myPaletteB.isDown()).toBe(false); + }); + + }); + +}); diff --git a/activities/Cordova.activity/lib/sugar-web/test/graphics/radiobuttonsgroupSpec.js b/activities/Cordova.activity/lib/sugar-web/test/graphics/radiobuttonsgroupSpec.js new file mode 100644 index 000000000..2c63775cd --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/graphics/radiobuttonsgroupSpec.js @@ -0,0 +1,97 @@ +define(["sugar-web/graphics/radiobuttonsgroup"], function ( + radioButtonsGroup) { + + 'use strict'; + + var elem1; + var elem2; + var elem3; + + beforeEach(function () { + elem1 = document.createElement('button'); + elem2 = document.createElement('button'); + elem3 = document.createElement('button'); + }); + + describe("radioToolButton", function () { + var wasClicked; + + it("should construct", function () { + var radio = new radioButtonsGroup.RadioButtonsGroup( + [elem1, elem2, elem3]); + expect(radio.elems.length).toBe(3); + }); + + it("should start active the first by default", function () { + var radio = new radioButtonsGroup.RadioButtonsGroup( + [elem1, elem2, elem3]); + expect(radio.getActive()).toBe(elem1); + }); + + it("should start active the first with 'active' class", function () { + elem2.className = "active red"; + elem3.className = "active blue"; + var radio = new radioButtonsGroup.RadioButtonsGroup( + [elem1, elem2, elem3]); + expect(radio.getActive()).toBe(elem2); + }); + + it("should add 'active' class to the selected item", function () { + var radio = new radioButtonsGroup.RadioButtonsGroup( + [elem1, elem2, elem3]); + var elem = radio.getActive(); + expect(elem.classList.contains('active')).toBe(true); + }); + + it("should change the active one on click", function () { + var radio = new radioButtonsGroup.RadioButtonsGroup( + [elem1, elem2, elem3]); + + // let's click elem2 + + runs(function () { + wasClicked = false; + + elem2.onclick = function () { + wasClicked = true; + }; + + elem2.click(); + }); + + waitsFor(function () { + return wasClicked; + }, "the element should be clicked"); + + runs(function () { + var elem = radio.getActive(); + expect(elem).toBe(elem2); + expect(elem.classList.contains('active')).toBe(true); + }); + + // now let's click elem1 + + runs(function () { + wasClicked = false; + + elem1.onclick = function () { + wasClicked = true; + }; + + elem1.click(); + }); + + waitsFor(function () { + return wasClicked; + }, "the element should be clicked"); + + runs(function () { + var elem = radio.getActive(); + expect(elem).toBe(elem1); + expect(elem.classList.contains('active')).toBe(true); + }); + }); + + }); + +}); diff --git a/activities/Cordova.activity/lib/sugar-web/test/karma-shared.conf.js b/activities/Cordova.activity/lib/sugar-web/test/karma-shared.conf.js new file mode 100644 index 000000000..9ff061f0e --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/karma-shared.conf.js @@ -0,0 +1,75 @@ +// Karma configuration +// Generated on Mon May 13 2013 10:01:17 GMT-0300 (ART) + + +// list of files / patterns to load in the browser +module.exports = function (config) { + config.set({ + frameworks: ['jasmine', 'requirejs'], + + + // base path, that will be used to resolve files and exclude + basePath: '..', + + + files: [ + 'test/loader.js', + { + pattern: 'lib/**/*.js', + included: false + }, { + pattern: '*.js', + included: false + }, { + pattern: 'activity/**/*.js', + included: false + }, { + pattern: 'graphics/**/*', + included: false + } + ], + + + // list of files to exclude + exclude: [], + + + // test results reporter to use + // possible values: 'dots', 'progress', 'junit' + reporters: ['progress'], + + + // web server port + port: 9876, + + + // cli runner port + runnerPort: 9100, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || + // LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file + // changes + autoWatch: true, + + + // If browser does not capture in given timeout [ms], kill it + captureTimeout: 60000, + + + // Continuous Integration mode + // if true, it capture browsers, run tests and exit + singleRun: false, + + preprocessors: {} + }); +}; diff --git a/activities/Cordova.activity/lib/sugar-web/test/karma-unit.conf.js b/activities/Cordova.activity/lib/sugar-web/test/karma-unit.conf.js new file mode 100644 index 000000000..889c20698 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/karma-unit.conf.js @@ -0,0 +1,19 @@ +// Karma configuration for unit tests + +sharedConfig = require("./karma-shared.conf.js"); + +module.exports = function (config) { + var testFiles = [ + { + pattern: 'test/unit/*Spec.js', + included: false + }, { + pattern: 'test/graphics/*Spec.js', + included: false + }, + ]; + + sharedConfig(config); + + config.files = config.files.concat(testFiles); +}; diff --git a/activities/Cordova.activity/lib/sugar-web/test/karma.conf.js b/activities/Cordova.activity/lib/sugar-web/test/karma.conf.js new file mode 100644 index 000000000..9c2d69ed2 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/karma.conf.js @@ -0,0 +1,16 @@ +// Karma configuration for all the tests + +sharedConfig = require("./karma-shared.conf.js"); + +module.exports = function (config) { + var testFiles = [ + { + pattern: 'test/**/*Spec.js', + included: false + } + ]; + + sharedConfig(config); + + config.files = config.files.concat(testFiles); +}; diff --git a/activities/Cordova.activity/lib/sugar-web/test/loader.js b/activities/Cordova.activity/lib/sugar-web/test/loader.js new file mode 100644 index 000000000..aec983753 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/loader.js @@ -0,0 +1,21 @@ +var tests = Object.keys(window.__karma__.files).filter(function (file) { + return (/Spec\.js$/).test(file); +}); + +requirejs.config({ + // Karma serves files from '/base' + baseUrl: "/base", + + paths: { + "sugar-web": ".", + "mustache": "lib/mustache", + "text": "lib/text", + "webL10n": "lib/webL10n" + }, + + // ask Require.js to load these files (all our tests) + deps: tests, + + // start test run, once Require.js is done + callback: window.__karma__.start +}); diff --git a/activities/Cordova.activity/lib/sugar-web/test/unit/busSpec.js b/activities/Cordova.activity/lib/sugar-web/test/unit/busSpec.js new file mode 100644 index 000000000..2a0ecbec2 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/unit/busSpec.js @@ -0,0 +1,156 @@ +define(["sugar-web/bus"], function (bus) { + + 'use strict'; + + describe("bus requests", function () { + var client; + + function MockClient() { + this.result = []; + this.error = null; + } + + MockClient.prototype.send = function (data) { + var that = this; + setTimeout(function () { + var parsed = JSON.parse(data); + + var message = { + data: JSON.stringify({ + result: that.result, + error: that.error, + id: parsed.id + }) + }; + + that.onMessage(message); + }, 0); + }; + + MockClient.prototype.close = function () {}; + + beforeEach(function () { + client = new MockClient(); + bus.listen(client); + }); + + afterEach(function () { + bus.close(); + client = null; + }); + + it("should receive a response", function () { + var responseReceived; + + runs(function () { + responseReceived = false; + + function onResponseReceived(error, result) { + expect(error).toBeNull(); + expect(result).toEqual(["hello"]); + responseReceived = true; + } + + client.result = ["hello"]; + + bus.sendMessage("hello", [], onResponseReceived); + }); + + waitsFor(function () { + return responseReceived; + }, "a response should be received"); + }); + + it("should receive an error", function () { + var errorReceived; + + runs(function () { + errorReceived = false; + + function onResponseReceived(error, result) { + expect(error).toEqual(jasmine.any(Error)); + expect(result).toBeNull(); + + errorReceived = true; + } + + client.result = null; + client.error = new Error("error"); + + bus.sendMessage("hello", [], onResponseReceived); + }); + + waitsFor(function () { + return errorReceived; + }, "an error should be received"); + }); + + }); + + describe("bus notifications", function () { + var client; + + function MockClient() { + this.params = null; + } + + MockClient.prototype.send_notification = function (method, params) { + var that = this; + + setTimeout(function () { + var message = { + data: JSON.stringify({ + method: method, + params: that.params + }) + }; + + that.onMessage(message); + }, 0); + }; + + MockClient.prototype.close = function () {}; + + beforeEach(function () { + client = new MockClient(); + bus.listen(client); + }); + + afterEach(function () { + bus.close(); + client = null; + }); + + it("should receive a notification", function () { + var notificationReceived; + var notificationParams; + var originalParams = { + param1: true, + param2: "foo" + }; + + runs(function () { + notificationReceived = false; + notificationParams = null; + + function onNotificationReceived(params) { + notificationReceived = true; + notificationParams = params; + } + + bus.onNotification("hey.there", onNotificationReceived); + + client.params = originalParams; + client.send_notification("hey.there"); + }); + + waitsFor(function () { + return notificationReceived; + }, "a notification should be received"); + + runs(function () { + expect(notificationParams).toEqual(originalParams); + }); + }); + }); +}); diff --git a/activities/Cordova.activity/lib/sugar-web/test/unit/datastoreSpec.js b/activities/Cordova.activity/lib/sugar-web/test/unit/datastoreSpec.js new file mode 100644 index 000000000..b166c82a4 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/unit/datastoreSpec.js @@ -0,0 +1,32 @@ +define(["sugar-web/env", "sugar-web/datastore"], function (env, datastore) { + + 'use strict'; + + describe("Ensure the datastore object has an objectId", function () { + + // FIXME does not work in standalone mode + it("should have objectId", function () { + var objectId = "objectId"; + spyOn(env, "getObjectId").andCallFake(function (callback) { + setTimeout(function () { + callback(objectId); + }, 0); + }); + var callback = jasmine.createSpy(); + + var datastoreObject = new datastore.DatastoreObject(); + + runs(function () { + datastoreObject.ensureObjectId(callback); + }); + + waitsFor(function () { + return datastoreObject.objectId !== undefined; + }, "should have objectId received from the environment"); + + runs(function () { + expect(callback).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/activities/Cordova.activity/lib/sugar-web/test/unit/dictstoreSpec.js b/activities/Cordova.activity/lib/sugar-web/test/unit/dictstoreSpec.js new file mode 100644 index 000000000..38528df32 --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/unit/dictstoreSpec.js @@ -0,0 +1,54 @@ +define(["sugar-web/dictstore", "sugar-web/env"], function (dictstore, env) { + + 'use strict'; + + describe("dictstore on standalone mode", function () { + + beforeEach(function () { + spyOn(env, 'isStandalone').andReturn(true); + }); + + describe("init method", function () { + + it("should execute callback", function () { + var callback = jasmine.createSpy(); + + dictstore.init(callback); + expect(callback).toHaveBeenCalled(); + }); + + it("should maintain localStorage", function () { + localStorage.testKey = "test"; + + dictstore.init(function () {}); + expect(localStorage.testKey).toBe("test"); + }); + }); + + describe("save method", function () { + + it("should just execute the callback", function () { + var callbackExecuted; + + localStorage.test_key = "test"; + + runs(function () { + callbackExecuted = false; + + dictstore.save(function () { + callbackExecuted = true; + }); + }); + + waitsFor(function () { + return callbackExecuted === true; + }, "The callback should executed"); + + runs(function () { + expect(localStorage.test_key).toBe("test"); + }); + }); + }); + + }); +}); diff --git a/activities/Cordova.activity/lib/sugar-web/test/unit/envSpec.js b/activities/Cordova.activity/lib/sugar-web/test/unit/envSpec.js new file mode 100644 index 000000000..3ed8c593c --- /dev/null +++ b/activities/Cordova.activity/lib/sugar-web/test/unit/envSpec.js @@ -0,0 +1,137 @@ +define(["sugar-web/env"], function (env) { + + 'use strict'; + + describe("getObjectId", function () { + + it("should return objectId from the sugar's environment", function () { + var environment = { + objectId: "objectId" + }; + spyOn(env, "getEnvironment").andCallFake(function (callback) { + setTimeout(function () { + callback(null, environment); + }, 0); + }); + var expected_objectId; + + runs(function () { + env.getObjectId(function (objectId) { + expected_objectId = objectId; + }); + }); + + waitsFor(function () { + return expected_objectId !== undefined; + }, "should return objectId"); + }); + }); + + describe("standalone mode", function () { + + it("should return true if in standalone mode", function () { + var noWebActivityURLScheme = "http:"; + spyOn(env, 'getURLScheme').andReturn(noWebActivityURLScheme); + + var isStandaloneMode = env.isStandalone(); + expect(isStandaloneMode).toBe(true); + }); + + it("should return false if not in standalone mode", function () { + var webActivityURLScheme = "activity:"; + spyOn(env, 'getURLScheme').andReturn(webActivityURLScheme); + + var isStandaloneMode = env.isStandalone(); + expect(isStandaloneMode).toBe(false); + }); + }); + + describe("getEnvironment", function () { + var sugarOrig; + + beforeEach(function () { + sugarOrig = JSON.parse(JSON.stringify(window.top.sugar)); + }); + + afterEach(function () { + window.top.sugar = sugarOrig; + }); + + describe("in sugar mode", function () { + + beforeEach(function () { + spyOn(env, 'isStandalone').andReturn(false); + }); + + describe("when env was already set", function () { + + it("should run callback with null error and env", function () { + var environment = {}; + window.top.sugar = { + environment: environment + }; + var callback = jasmine.createSpy(); + + runs(function () { + env.getEnvironment(callback); + }); + + waitsFor(function () { + return callback.wasCalled; + }, "callback should be executed"); + + runs(function () { + expect(callback).toHaveBeenCalledWith( + null, environment); + }); + }); + }); + + describe("when env was not set, yet", function () { + + beforeEach(function () { + window.top.sugar = undefined; + }); + + it("should set onEnvironmentSet handler", function () { + var sugar; + env.getEnvironment(function () {}); + sugar = window.top.sugar; + expect(sugar.onEnvironmentSet).not.toBeUndefined(); + }); + + it("should run callback on EnvironmentSet event", function () { + var callback = jasmine.createSpy(); + var expectedEnv = "env"; + + env.getEnvironment(callback); + window.top.sugar.environment = expectedEnv; + window.top.sugar.onEnvironmentSet(); + + expect(callback).toHaveBeenCalledWith(null, expectedEnv); + }); + }); + }); + + it("should return {} in standalone mode", function () { + window.top.sugar = undefined; + spyOn(env, 'isStandalone').andReturn(true); + var actualEnv; + + runs(function () { + env.getEnvironment(function (error, environment) { + actualEnv = environment; + }); + }); + + waitsFor(function () { + return actualEnv !== undefined; + }, "environment not to be undefined"); + + runs(function () { + expect(actualEnv).toEqual({}); + }); + + }); + }); +}); diff --git a/activities/Cordova.activity/lib/text.js b/activities/Cordova.activity/lib/text.js new file mode 100644 index 000000000..1e4fc9661 --- /dev/null +++ b/activities/Cordova.activity/lib/text.js @@ -0,0 +1,386 @@ +/** + * @license RequireJS text 2.0.10 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/text for details + */ +/*jslint regexp: true */ +/*global require, XMLHttpRequest, ActiveXObject, + define, window, process, Packages, + java, location, Components, FileUtils */ + +define(['module'], function (module) { + 'use strict'; + + var text, fs, Cc, Ci, xpcIsWindows, + progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], + xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, + bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im, + hasLocation = typeof location !== 'undefined' && location.href, + defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''), + defaultHostName = hasLocation && location.hostname, + defaultPort = hasLocation && (location.port || undefined), + buildMap = {}, + masterConfig = (module.config && module.config()) || {}; + + text = { + version: '2.0.10', + + strip: function (content) { + //Strips declarations so that external SVG and XML + //documents can be added to a document without worry. Also, if the string + //is an HTML document, only the part inside the body tag is returned. + if (content) { + content = content.replace(xmlRegExp, ""); + var matches = content.match(bodyRegExp); + if (matches) { + content = matches[1]; + } + } else { + content = ""; + } + return content; + }, + + jsEscape: function (content) { + return content.replace(/(['\\])/g, '\\$1') + .replace(/[\f]/g, "\\f") + .replace(/[\b]/g, "\\b") + .replace(/[\n]/g, "\\n") + .replace(/[\t]/g, "\\t") + .replace(/[\r]/g, "\\r") + .replace(/[\u2028]/g, "\\u2028") + .replace(/[\u2029]/g, "\\u2029"); + }, + + createXhr: masterConfig.createXhr || function () { + //Would love to dump the ActiveX crap in here. Need IE 6 to die first. + var xhr, i, progId; + if (typeof XMLHttpRequest !== "undefined") { + return new XMLHttpRequest(); + } else if (typeof ActiveXObject !== "undefined") { + for (i = 0; i < 3; i += 1) { + progId = progIds[i]; + try { + xhr = new ActiveXObject(progId); + } catch (e) {} + + if (xhr) { + progIds = [progId]; // so faster next time + break; + } + } + } + + return xhr; + }, + + /** + * Parses a resource name into its component parts. Resource names + * look like: module/name.ext!strip, where the !strip part is + * optional. + * @param {String} name the resource name + * @returns {Object} with properties "moduleName", "ext" and "strip" + * where strip is a boolean. + */ + parseName: function (name) { + var modName, ext, temp, + strip = false, + index = name.indexOf("."), + isRelative = name.indexOf('./') === 0 || + name.indexOf('../') === 0; + + if (index !== -1 && (!isRelative || index > 1)) { + modName = name.substring(0, index); + ext = name.substring(index + 1, name.length); + } else { + modName = name; + } + + temp = ext || modName; + index = temp.indexOf("!"); + if (index !== -1) { + //Pull off the strip arg. + strip = temp.substring(index + 1) === "strip"; + temp = temp.substring(0, index); + if (ext) { + ext = temp; + } else { + modName = temp; + } + } + + return { + moduleName: modName, + ext: ext, + strip: strip + }; + }, + + xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/, + + /** + * Is an URL on another domain. Only works for browser use, returns + * false in non-browser environments. Only used to know if an + * optimized .js version of a text resource should be loaded + * instead. + * @param {String} url + * @returns Boolean + */ + useXhr: function (url, protocol, hostname, port) { + var uProtocol, uHostName, uPort, + match = text.xdRegExp.exec(url); + if (!match) { + return true; + } + uProtocol = match[2]; + uHostName = match[3]; + + uHostName = uHostName.split(':'); + uPort = uHostName[1]; + uHostName = uHostName[0]; + + return (!uProtocol || uProtocol === protocol) && + (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && + ((!uPort && !uHostName) || uPort === port); + }, + + finishLoad: function (name, strip, content, onLoad) { + content = strip ? text.strip(content) : content; + if (masterConfig.isBuild) { + buildMap[name] = content; + } + onLoad(content); + }, + + load: function (name, req, onLoad, config) { + //Name has format: some.module.filext!strip + //The strip part is optional. + //if strip is present, then that means only get the string contents + //inside a body tag in an HTML string. For XML/SVG content it means + //removing the declarations so the content can be inserted + //into the current doc without problems. + + // Do not bother with the work if a build and text will + // not be inlined. + if (config.isBuild && !config.inlineText) { + onLoad(); + return; + } + + masterConfig.isBuild = config.isBuild; + + var parsed = text.parseName(name), + nonStripName = parsed.moduleName + + (parsed.ext ? '.' + parsed.ext : ''), + url = req.toUrl(nonStripName), + useXhr = (masterConfig.useXhr) || + text.useXhr; + + // Do not load if it is an empty: url + if (url.indexOf('empty:') === 0) { + onLoad(); + return; + } + + //Load the text. Use XHR if possible and in a browser. + if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) { + text.get(url, function (content) { + text.finishLoad(name, parsed.strip, content, onLoad); + }, function (err) { + if (onLoad.error) { + onLoad.error(err); + } + }); + } else { + //Need to fetch the resource across domains. Assume + //the resource has been optimized into a JS module. Fetch + //by the module name + extension, but do not include the + //!strip part to avoid file system issues. + req([nonStripName], function (content) { + text.finishLoad(parsed.moduleName + '.' + parsed.ext, + parsed.strip, content, onLoad); + }); + } + }, + + write: function (pluginName, moduleName, write, config) { + if (buildMap.hasOwnProperty(moduleName)) { + var content = text.jsEscape(buildMap[moduleName]); + write.asModule(pluginName + "!" + moduleName, + "define(function () { return '" + + content + + "';});\n"); + } + }, + + writeFile: function (pluginName, moduleName, req, write, config) { + var parsed = text.parseName(moduleName), + extPart = parsed.ext ? '.' + parsed.ext : '', + nonStripName = parsed.moduleName + extPart, + //Use a '.js' file name so that it indicates it is a + //script that can be loaded across domains. + fileName = req.toUrl(parsed.moduleName + extPart) + '.js'; + + //Leverage own load() method to load plugin value, but only + //write out values that do not have the strip argument, + //to avoid any potential issues with ! in file names. + text.load(nonStripName, req, function (value) { + //Use own write() method to construct full module value. + //But need to create shell that translates writeFile's + //write() to the right interface. + var textWrite = function (contents) { + return write(fileName, contents); + }; + textWrite.asModule = function (moduleName, contents) { + return write.asModule(moduleName, fileName, contents); + }; + + text.write(pluginName, nonStripName, textWrite, config); + }, config); + } + }; + + if (masterConfig.env === 'node' || (!masterConfig.env && + typeof process !== "undefined" && + process.versions && + !!process.versions.node && + !process.versions['node-webkit'])) { + //Using special require.nodeRequire, something added by r.js. + fs = require.nodeRequire('fs'); + + text.get = function (url, callback, errback) { + try { + var file = fs.readFileSync(url, 'utf8'); + //Remove BOM (Byte Mark Order) from utf8 files if it is there. + if (file.indexOf('\uFEFF') === 0) { + file = file.substring(1); + } + callback(file); + } catch (e) { + errback(e); + } + }; + } else if (masterConfig.env === 'xhr' || (!masterConfig.env && + text.createXhr())) { + text.get = function (url, callback, errback, headers) { + var xhr = text.createXhr(), header; + xhr.open('GET', url, true); + + //Allow plugins direct access to xhr headers + if (headers) { + for (header in headers) { + if (headers.hasOwnProperty(header)) { + xhr.setRequestHeader(header.toLowerCase(), headers[header]); + } + } + } + + //Allow overrides specified in config + if (masterConfig.onXhr) { + masterConfig.onXhr(xhr, url); + } + + xhr.onreadystatechange = function (evt) { + var status, err; + //Do not explicitly handle errors, those should be + //visible via console output in the browser. + if (xhr.readyState === 4) { + status = xhr.status; + if (status > 399 && status < 600) { + //An http 4xx or 5xx error. Signal an error. + err = new Error(url + ' HTTP status: ' + status); + err.xhr = xhr; + errback(err); + } else { + callback(xhr.responseText); + } + + if (masterConfig.onXhrComplete) { + masterConfig.onXhrComplete(xhr, url); + } + } + }; + xhr.send(null); + }; + } else if (masterConfig.env === 'rhino' || (!masterConfig.env && + typeof Packages !== 'undefined' && typeof java !== 'undefined')) { + //Why Java, why is this so awkward? + text.get = function (url, callback) { + var stringBuffer, line, + encoding = "utf-8", + file = new java.io.File(url), + lineSeparator = java.lang.System.getProperty("line.separator"), + input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), + content = ''; + try { + stringBuffer = new java.lang.StringBuffer(); + line = input.readLine(); + + // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 + // http://www.unicode.org/faq/utf_bom.html + + // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 + if (line && line.length() && line.charAt(0) === 0xfeff) { + // Eat the BOM, since we've already found the encoding on this file, + // and we plan to concatenating this buffer with others; the BOM should + // only appear at the top of a file. + line = line.substring(1); + } + + if (line !== null) { + stringBuffer.append(line); + } + + while ((line = input.readLine()) !== null) { + stringBuffer.append(lineSeparator); + stringBuffer.append(line); + } + //Make sure we return a JavaScript string and not a Java string. + content = String(stringBuffer.toString()); //String + } finally { + input.close(); + } + callback(content); + }; + } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env && + typeof Components !== 'undefined' && Components.classes && + Components.interfaces)) { + //Avert your gaze! + Cc = Components.classes, + Ci = Components.interfaces; + Components.utils['import']('resource://gre/modules/FileUtils.jsm'); + xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc); + + text.get = function (url, callback) { + var inStream, convertStream, fileObj, + readData = {}; + + if (xpcIsWindows) { + url = url.replace(/\//g, '\\'); + } + + fileObj = new FileUtils.File(url); + + //XPCOM, you so crazy + try { + inStream = Cc['@mozilla.org/network/file-input-stream;1'] + .createInstance(Ci.nsIFileInputStream); + inStream.init(fileObj, 1, 0, false); + + convertStream = Cc['@mozilla.org/intl/converter-input-stream;1'] + .createInstance(Ci.nsIConverterInputStream); + convertStream.init(inStream, "utf-8", inStream.available(), + Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + + convertStream.readString(inStream.available(), readData); + convertStream.close(); + inStream.close(); + callback(readData.value); + } catch (e) { + throw new Error((fileObj && fileObj.path || '') + ': ' + e); + } + }; + } + return text; +}); diff --git a/activities/Cordova.activity/lib/webL10n.js b/activities/Cordova.activity/lib/webL10n.js new file mode 100644 index 000000000..f76d66a5e --- /dev/null +++ b/activities/Cordova.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/Cordova.activity/package.json b/activities/Cordova.activity/package.json new file mode 100644 index 000000000..a0f61a0ea --- /dev/null +++ b/activities/Cordova.activity/package.json @@ -0,0 +1,13 @@ +{ + "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", + "text": "github:requirejs/text/2.0.12" + } + } +} diff --git a/activities/Cordova.activity/setup.py b/activities/Cordova.activity/setup.py new file mode 100644 index 000000000..ad218b212 --- /dev/null +++ b/activities/Cordova.activity/setup.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +from sugar3.activity import bundlebuilder + +bundlebuilder.start() diff --git a/activities/Cordova.activity/toolbar.html b/activities/Cordova.activity/toolbar.html new file mode 100644 index 000000000..a3e5f413d --- /dev/null +++ b/activities/Cordova.activity/toolbar.html @@ -0,0 +1,27 @@ + + + + + +My Activity + + + + + + + +
    + + + + + + +
    + + + +