Vojtech Szocs has uploaded a new change for review.

Change subject: Introducing space-shooter plugin
......................................................................

Introducing space-shooter plugin

Welcome, space traveler!

This plugin is part of UI Plugins Crash Course:

  http://www.ovirt.org/Tutorial/UIPlugins/CrashCourse

This plugin contains Alien Invasion, sample HTML5 game
developed by Pascal Rettig and released under both GPL
and MIT license:

  https://github.com/cykod/AlienInvasion

Refer to Crash Course wiki for step-by-step tutorial.

Check out README file for details on installation.

Change-Id: I347b18ca57b7b41e4cdf42e528cf5a7aff60c583
Signed-off-by: Vojtech Szocs <[email protected]>
---
A space-shooter-plugin/.gitignore
A space-shooter-plugin/README
A space-shooter-plugin/space-shooter-resources/dc-score.html
A space-shooter-plugin/space-shooter-resources/game/GPL-LICENSE.txt
A space-shooter-plugin/space-shooter-resources/game/MIT-LICENSE.txt
A space-shooter-plugin/space-shooter-resources/game/README.md
A space-shooter-plugin/space-shooter-resources/game/base.css
A space-shooter-plugin/space-shooter-resources/game/engine.js
A space-shooter-plugin/space-shooter-resources/game/game.js
A space-shooter-plugin/space-shooter-resources/game/game.manifest
A space-shooter-plugin/space-shooter-resources/game/images/sprites.png
A space-shooter-plugin/space-shooter-resources/game/index.html
A space-shooter-plugin/space-shooter-resources/modernizr.custom.js
A space-shooter-plugin/space-shooter-resources/plugin.html
A space-shooter-plugin/space-shooter.json
15 files changed, 1,490 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/samples-uiplugins 
refs/changes/13/23813/1

diff --git a/space-shooter-plugin/.gitignore b/space-shooter-plugin/.gitignore
new file mode 100644
index 0000000..517b035
--- /dev/null
+++ b/space-shooter-plugin/.gitignore
@@ -0,0 +1,5 @@
+# space-shooter project ignore list
+
+# IDE files
+.idea
+*.iml
diff --git a/space-shooter-plugin/README b/space-shooter-plugin/README
new file mode 100644
index 0000000..09c6558
--- /dev/null
+++ b/space-shooter-plugin/README
@@ -0,0 +1,34 @@
+oVirt Space Shooter
+-------------------
+
+Welcome, space traveler!
+
+This plugin is part of UI Plugins Crash Course:
+
+  http://www.ovirt.org/Tutorial/UIPlugins/CrashCourse
+
+This plugin contains Alien Invasion, sample HTML5 game
+developed by Pascal Rettig and released under both GPL
+and MIT license:
+
+  https://github.com/cykod/AlienInvasion
+
+Refer to Crash Course wiki for step-by-step tutorial.
+
+
+INSTALLING
+
+Configure oVirt Engine HTTP(S) origin(s):
+
+  $ ENGINE_HTTP='http://engine-domain-example:8080'
+  $ ENGINE_HTTPS='https://engine-domain-example:8443'
+  $ PLUGIN_USER_CONFIG='{ "config": { "allowedOrigins": 
["$ENGINE_HTTP","$ENGINE_HTTPS"] } }'
+  $ echo $PLUGIN_USER_CONFIG > $ENGINE_ETC/ui-plugins/space-shooter-config.json
+
+Alternatively, simply edit $PLUGIN_HOME/space-shooter.json file directly.
+
+Expose plugin metadata to oVirt Engine
+  $ ln -s $PLUGIN_HOME/space-shooter.json 
$ENGINE_USR/ui-plugins/space-shooter.json
+
+Expose plugin static files to oVirt Engine
+  $ ln -s $PLUGIN_HOME/space-shooter-resources 
$ENGINE_USR/ui-plugins/space-shooter-resources
diff --git a/space-shooter-plugin/space-shooter-resources/dc-score.html 
b/space-shooter-plugin/space-shooter-resources/dc-score.html
new file mode 100644
index 0000000..dcea2eb
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/dc-score.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<!--
+       oVirt Space Shooter: Data Center 'Score' sub tab
+       displays current user score for given Data Center entity
+-->
+<html>
+<head>
+    <meta charset="utf-8">
+</head>
+<body>
+
+<p>
+    Score for Data Center <span id='dataCenterName'>?</span>:
+    <span id='score'>?</span> (<span id='rank'>?</span>)
+</p>
+
+<script type='text/javascript'>
+
+       // Ask the plugin for current user score
+       parent.postMessage('GetDataCenterScore', '*');
+
+       // The plugin should call this function in response to 
GetDataCenterScore message
+       var update = function(dataCenterName, score, rank, rankColor) {
+               document.getElementById('dataCenterName').innerHTML = '<em>' + 
dataCenterName + '</em>';
+               document.getElementById('score').innerHTML = '<b>' + score + 
'</b>';
+               document.getElementById('rank').innerHTML = '<b>' + rank + 
'</b>';
+               document.getElementById('rank').style.color = rankColor;
+       };
+
+</script>
+
+</body>
+</html>
diff --git a/space-shooter-plugin/space-shooter-resources/game/GPL-LICENSE.txt 
b/space-shooter-plugin/space-shooter-resources/game/GPL-LICENSE.txt
new file mode 100644
index 0000000..11dddd0
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/game/GPL-LICENSE.txt
@@ -0,0 +1,278 @@
+        GNU GENERAL PUBLIC LICENSE
+           Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+        GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+          NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
diff --git a/space-shooter-plugin/space-shooter-resources/game/MIT-LICENSE.txt 
b/space-shooter-plugin/space-shooter-resources/game/MIT-LICENSE.txt
new file mode 100644
index 0000000..72997a4
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/game/MIT-LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2011 Cykod LLC, http://coderdeck.com/
+
+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.
diff --git a/space-shooter-plugin/space-shooter-resources/game/README.md 
b/space-shooter-plugin/space-shooter-resources/game/README.md
new file mode 100644
index 0000000..b023030
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/game/README.md
@@ -0,0 +1,26 @@
+Alien Invasion
+==============
+This is the sample game that is built in the first three Chapter of
+mobile HTML5 Game Development.
+
+It is released under both the GPL and MIT license to do with what you will.
+
+Playable Version: 
+http://cykod.github.com/AlienInvasion/
+
+Bit.ly link for mobile: 
+http://bit.ly/html5-invasion
+
+
+If you make an interesting for fork or enhancement of the game, let me know 
and it'll get
+linked to here. This original repo will stay matching the code in the book.
+
+For more  [HTML5 Game Development](http://www.html5gamedevelopment.org) 
resources, see:
+
+* [HTML5 Game Demos](http://www.html5gamedevelopment.org/html5-demos)
+* [HTML5 GameDev 
Tutorials](http://www.html5gamedevelopment.org/html5-game-tutorials)
+* [HTML5 Game Development News](http://www.html5gamedevelopment.org/html5-news)
+* [HTML5 Game Engines](http://www.html5gamedevelopment.org/html5-engines)
+
+
+
diff --git a/space-shooter-plugin/space-shooter-resources/game/base.css 
b/space-shooter-plugin/space-shooter-resources/game/base.css
new file mode 100644
index 0000000..779e2a8
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/game/base.css
@@ -0,0 +1,62 @@
+/* http://meyerweb.com/eric/tools/css/reset/ 
+   v2.0 | 20110126
+   License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed, 
+figure, figcaption, footer, header, hgroup, 
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+       margin: 0;
+       padding: 0;
+       border: 0;
+       font-size: 100%;
+       font: inherit;
+       vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure, 
+footer, header, hgroup, menu, nav, section {
+       display: block;
+}
+body {
+       line-height: 1;
+}
+ol, ul {
+       list-style: none;
+}
+blockquote, q {
+       quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+       content: '';
+       content: none;
+}
+table {
+       border-collapse: collapse;
+       border-spacing: 0;
+}
+
+
+/* Center the container */
+#container {
+  /*padding-top:50px;*/
+  margin:0 auto;
+  /*width:480px;*/
+}
+
+/* Give canvas a background */
+canvas {
+  background-color:black;
+
+}
diff --git a/space-shooter-plugin/space-shooter-resources/game/engine.js 
b/space-shooter-plugin/space-shooter-resources/game/engine.js
new file mode 100644
index 0000000..deff7d3
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/game/engine.js
@@ -0,0 +1,452 @@
+(function() {
+    var lastTime = 0;
+    var vendors = ['ms', 'moz', 'webkit', 'o'];
+    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+        window.requestAnimationFrame = 
window[vendors[x]+'RequestAnimationFrame'];
+        window.cancelAnimationFrame = 
+          window[vendors[x]+'CancelAnimationFrame'] || 
window[vendors[x]+'CancelRequestAnimationFrame'];
+    }
+ 
+    if (!window.requestAnimationFrame)
+        window.requestAnimationFrame = function(callback, element) {
+            var currTime = new Date().getTime();
+            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+            var id = window.setTimeout(function() { callback(currTime + 
timeToCall); }, 
+              timeToCall);
+            lastTime = currTime + timeToCall;
+            return id;
+        };
+ 
+    if (!window.cancelAnimationFrame)
+        window.cancelAnimationFrame = function(id) {
+            clearTimeout(id);
+        };
+}());
+  
+
+var Game = new function() {                                                    
              
+  var boards = [];
+
+  // Game Initialization
+  this.initialize = function(canvasElementId,sprite_data,callback) {
+    this.canvas = document.getElementById(canvasElementId);
+
+    this.playerOffset = 10;
+    this.canvasMultiplier= 1;
+    this.setupMobile();
+
+    this.width = this.canvas.width;
+    this.height= this.canvas.height;
+
+    this.ctx = this.canvas.getContext && this.canvas.getContext('2d');
+    if(!this.ctx) { return alert("Please upgrade your browser to play"); }
+
+    this.setupInput();
+
+    this.loop(); 
+
+    if(this.mobile) {
+      this.setBoard(4,new TouchControls());
+    }
+
+    SpriteSheet.load(sprite_data,callback);
+  };
+  
+
+  // Handle Input
+  var KEY_CODES = { 37:'left', 39:'right', 32 :'fire' };
+  this.keys = {};
+
+  this.setupInput = function() {
+    window.addEventListener('keydown',function(e) {
+      if(KEY_CODES[e.keyCode]) {
+       Game.keys[KEY_CODES[e.keyCode]] = true;
+       e.preventDefault();
+      }
+    },false);
+
+    window.addEventListener('keyup',function(e) {
+      if(KEY_CODES[e.keyCode]) {
+       Game.keys[KEY_CODES[e.keyCode]] = false; 
+       e.preventDefault();
+      }
+    },false);
+  };
+
+
+  var lastTime = new Date().getTime();
+  var maxTime = 1/30;
+  // Game Loop
+  this.loop = function() { 
+    var curTime = new Date().getTime();
+    requestAnimationFrame(Game.loop);
+    var dt = (curTime - lastTime)/1000;
+    if(dt > maxTime) { dt = maxTime; }
+
+    for(var i=0,len = boards.length;i<len;i++) {
+      if(boards[i]) { 
+        boards[i].step(dt);
+        boards[i].draw(Game.ctx);
+      }
+    }
+    lastTime = curTime;
+  };
+  
+  // Change an active game board
+  this.setBoard = function(num,board) { boards[num] = board; };
+
+
+  this.setupMobile = function() {
+    var container = document.getElementById("container"),
+        hasTouch =  !!('ontouchstart' in window),
+        w = window.innerWidth, h = window.innerHeight;
+      
+    if(hasTouch) { this.mobile = true; }
+
+    if(screen.width >= 1280 || !hasTouch) { return false; }
+
+    if(w > h) {
+      alert("Please rotate the device and then click OK");
+      w = window.innerWidth; h = window.innerHeight;
+    }
+
+    container.style.height = h*2 + "px";
+    window.scrollTo(0,1);
+
+    h = window.innerHeight + 2;
+    container.style.height = h + "px";
+    container.style.width = w + "px";
+    container.style.padding = 0;
+
+    if(h >= this.canvas.height * 1.75 || w >= this.canvas.height * 1.75) {
+      this.canvasMultiplier = 2;
+      this.canvas.width = w / 2;
+      this.canvas.height = h / 2;
+      this.canvas.style.width = w + "px";
+      this.canvas.style.height = h + "px";
+    } else {
+      this.canvas.width = w;
+      this.canvas.height = h;
+    }
+
+    this.canvas.style.position='absolute';
+    this.canvas.style.left="0px";
+    this.canvas.style.top="0px";
+
+  };
+
+};
+
+
+var SpriteSheet = new function() {
+  this.map = { }; 
+
+  this.load = function(spriteData,callback) { 
+    this.map = spriteData;
+    this.image = new Image();
+    this.image.onload = callback;
+    this.image.src = 'images/sprites.png';
+  };
+
+  this.draw = function(ctx,sprite,x,y,frame) {
+    var s = this.map[sprite];
+    if(!frame) frame = 0;
+    ctx.drawImage(this.image,
+                     s.sx + frame * s.w, 
+                     s.sy, 
+                     s.w, s.h, 
+                     Math.floor(x), Math.floor(y),
+                     s.w, s.h);
+  };
+
+  return this;
+};
+
+var TitleScreen = function TitleScreen(title,subtitle,callback) {
+  var up = false;
+  this.step = function(dt) {
+    if(!Game.keys['fire']) up = true;
+    if(up && Game.keys['fire'] && callback) callback();
+  };
+
+  this.draw = function(ctx) {
+    ctx.fillStyle = "#FFFFFF";
+
+    ctx.font = "bold 40px bangers";
+    var measure = ctx.measureText(title);  
+    ctx.fillText(title,Game.width/2 - measure.width/2,Game.height/2);
+
+    ctx.font = "bold 20px bangers";
+    var measure2 = ctx.measureText(subtitle);
+    ctx.fillText(subtitle,Game.width/2 - measure2.width/2,Game.height/2 + 40);
+  };
+};
+
+
+var GameBoard = function() {
+  var board = this;
+
+  // The current list of objects
+  this.objects = [];
+  this.cnt = {};
+
+  // Add a new object to the object list
+  this.add = function(obj) { 
+    obj.board=this; 
+    this.objects.push(obj); 
+    this.cnt[obj.type] = (this.cnt[obj.type] || 0) + 1;
+    return obj; 
+  };
+
+  // Mark an object for removal
+  this.remove = function(obj) { 
+    var idx = this.removed.indexOf(obj);
+    if(idx == -1) {
+      this.removed.push(obj); 
+      return true;
+    } else {
+      return false;
+    }
+  };
+
+  // Reset the list of removed objects
+  this.resetRemoved = function() { this.removed = []; };
+
+  // Removed an objects marked for removal from the list
+  this.finalizeRemoved = function() {
+    for(var i=0,len=this.removed.length;i<len;i++) {
+      var idx = this.objects.indexOf(this.removed[i]);
+      if(idx != -1) {
+        this.cnt[this.removed[i].type]--;
+        this.objects.splice(idx,1);
+      }
+    }
+  };
+
+  // Call the same method on all current objects 
+  this.iterate = function(funcName) {
+     var args = Array.prototype.slice.call(arguments,1);
+     for(var i=0,len=this.objects.length;i<len;i++) {
+       var obj = this.objects[i];
+       obj[funcName].apply(obj,args);
+     }
+  };
+
+  // Find the first object for which func is true
+  this.detect = function(func) {
+    for(var i = 0,val=null, len=this.objects.length; i < len; i++) {
+      if(func.call(this.objects[i])) return this.objects[i];
+    }
+    return false;
+  };
+
+  // Call step on all objects and them delete
+  // any object that have been marked for removal
+  this.step = function(dt) { 
+    this.resetRemoved();
+    this.iterate('step',dt);
+    this.finalizeRemoved();
+  };
+
+  // Draw all the objects
+  this.draw= function(ctx) {
+    this.iterate('draw',ctx);
+  };
+
+  // Check for a collision between the 
+  // bounding rects of two objects
+  this.overlap = function(o1,o2) {
+    return !((o1.y+o1.h-1<o2.y) || (o1.y>o2.y+o2.h-1) ||
+             (o1.x+o1.w-1<o2.x) || (o1.x>o2.x+o2.w-1));
+  };
+
+  // Find the first object that collides with obj
+  // match against an optional type
+  this.collide = function(obj,type) {
+    return this.detect(function() {
+      if(obj != this) {
+       var col = (!type || this.type & type) && board.overlap(obj,this);
+       return col ? this : false;
+      }
+    });
+  };
+
+
+};
+
+var Sprite = function() { };
+
+Sprite.prototype.setup = function(sprite,props) {
+  this.sprite = sprite;
+  this.merge(props);
+  this.frame = this.frame || 0;
+  this.w =  SpriteSheet.map[sprite].w;
+  this.h =  SpriteSheet.map[sprite].h;
+};
+
+Sprite.prototype.merge = function(props) {
+  if(props) {
+    for (var prop in props) {
+      this[prop] = props[prop];
+    }
+  }
+};
+
+Sprite.prototype.draw = function(ctx) {
+  SpriteSheet.draw(ctx,this.sprite,this.x,this.y,this.frame);
+};
+
+Sprite.prototype.hit = function(damage) {
+  this.board.remove(this);
+};
+
+
+var Level = function(levelData,callback) {
+  this.levelData = [];
+  for(var i =0; i<levelData.length; i++) {
+    this.levelData.push(Object.create(levelData[i]));
+  }
+  this.t = 0;
+  this.callback = callback;
+};
+
+Level.prototype.step = function(dt) {
+  var idx = 0, remove = [], curShip = null;
+
+  // Update the current time offset
+  this.t += dt * 1000;
+
+  //   Start, End,  Gap, Type,   Override
+  // [ 0,     4000, 500, 'step', { x: 100 } ]
+  while((curShip = this.levelData[idx]) && 
+        (curShip[0] < this.t + 2000)) {
+    // Check if we've passed the end time 
+    if(this.t > curShip[1]) {
+      remove.push(curShip);
+    } else if(curShip[0] < this.t) {
+      // Get the enemy definition blueprint
+      var enemy = enemies[curShip[3]],
+          override = curShip[4];
+
+      // Add a new enemy with the blueprint and override
+      this.board.add(new Enemy(enemy,override));
+
+      // Increment the start time by the gap
+      curShip[0] += curShip[2];
+    }
+    idx++;
+  }
+
+  // Remove any objects from the levelData that have passed
+  for(var i=0,len=remove.length;i<len;i++) {
+    var remIdx = this.levelData.indexOf(remove[i]);
+    if(remIdx != -1) this.levelData.splice(remIdx,1);
+  }
+
+  // If there are no more enemies on the board or in 
+  // levelData, this level is done
+  if(this.levelData.length === 0 && this.board.cnt[OBJECT_ENEMY] === 0) {
+    if(this.callback) this.callback();
+  }
+
+};
+
+Level.prototype.draw = function(ctx) { };
+
+
+var TouchControls = function() {
+
+  var gutterWidth = 10;
+  var unitWidth = Game.width/5;
+  var blockWidth = unitWidth-gutterWidth;
+
+  this.drawSquare = function(ctx,x,y,txt,on) {
+    ctx.globalAlpha = on ? 0.9 : 0.6;
+    ctx.fillStyle =  "#CCC";
+    ctx.fillRect(x,y,blockWidth,blockWidth);
+
+    ctx.fillStyle = "#FFF";
+    ctx.globalAlpha = 1.0;
+    ctx.font = "bold " + (3*unitWidth/4) + "px arial";
+
+    var txtSize = ctx.measureText(txt);
+
+    ctx.fillText(txt, 
+                 x+blockWidth/2-txtSize.width/2, 
+                 y+3*blockWidth/4+5);
+  };
+
+  this.draw = function(ctx) {
+    ctx.save();
+
+    var yLoc = Game.height - unitWidth;
+    this.drawSquare(ctx,gutterWidth,yLoc,"\u25C0", Game.keys['left']);
+    this.drawSquare(ctx,unitWidth + gutterWidth,yLoc,"\u25B6", 
Game.keys['right']);
+    this.drawSquare(ctx,4*unitWidth,yLoc,"A",Game.keys['fire']);
+
+    ctx.restore();
+  };
+
+  this.step = function(dt) { };
+
+  this.trackTouch = function(e) {
+    var touch, x;
+
+    e.preventDefault();
+    Game.keys['left'] = false;
+    Game.keys['right'] = false;
+    for(var i=0;i<e.targetTouches.length;i++) {
+      touch = e.targetTouches[i];
+      x = touch.pageX / Game.canvasMultiplier - Game.canvas.offsetLeft;
+      if(x < unitWidth) {
+        Game.keys['left'] = true;
+      } 
+      if(x > unitWidth && x < 2*unitWidth) {
+        Game.keys['right'] = true;
+      } 
+    }
+
+    if(e.type == 'touchstart' || e.type == 'touchend') {
+      for(i=0;i<e.changedTouches.length;i++) {
+        touch = e.changedTouches[i];
+        x = touch.pageX / Game.canvasMultiplier - Game.canvas.offsetLeft;
+        if(x > 4 * unitWidth) {
+          Game.keys['fire'] = (e.type == 'touchstart');
+        }
+      }
+    }
+  };
+
+  Game.canvas.addEventListener('touchstart',this.trackTouch,true);
+  Game.canvas.addEventListener('touchmove',this.trackTouch,true);
+  Game.canvas.addEventListener('touchend',this.trackTouch,true);
+
+  // For Android
+  Game.canvas.addEventListener('dblclick',function(e) { e.preventDefault(); 
},true);
+  Game.canvas.addEventListener('click',function(e) { e.preventDefault(); 
},true);
+
+  Game.playerOffset = unitWidth + 20;
+};
+
+
+var GamePoints = function() {
+  Game.points = 0;
+
+  var pointsLength = 8;
+
+  this.draw = function(ctx) {
+    ctx.save();
+    ctx.font = "bold 18px arial";
+    ctx.fillStyle= "#FFFFFF";
+
+    var txt = "" + Game.points;
+    var i = pointsLength - txt.length, zeros = "";
+    while(i-- > 0) { zeros += "0"; }
+
+    ctx.fillText(zeros + txt,10,20);
+    ctx.restore();
+
+  };
+
+  this.step = function(dt) { };
+};
diff --git a/space-shooter-plugin/space-shooter-resources/game/game.js 
b/space-shooter-plugin/space-shooter-resources/game/game.js
new file mode 100644
index 0000000..7317f35
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/game/game.js
@@ -0,0 +1,305 @@
+var sprites = {
+ ship: { sx: 0, sy: 0, w: 37, h: 42, frames: 1 },
+ missile: { sx: 0, sy: 30, w: 2, h: 10, frames: 1 },
+ enemy_purple: { sx: 37, sy: 0, w: 42, h: 43, frames: 1 },
+ enemy_bee: { sx: 79, sy: 0, w: 37, h: 43, frames: 1 },
+ enemy_ship: { sx: 116, sy: 0, w: 42, h: 43, frames: 1 },
+ enemy_circle: { sx: 158, sy: 0, w: 32, h: 33, frames: 1 },
+ explosion: { sx: 0, sy: 64, w: 64, h: 64, frames: 12 },
+ enemy_missile: { sx: 9, sy: 42, w: 3, h: 20, frame: 1, }
+};
+
+var enemies = {
+  straight: { x: 0,   y: -50, sprite: 'enemy_ship', health: 10, 
+              E: 100 },
+  ltr:      { x: 0,   y: -100, sprite: 'enemy_purple', health: 10, 
+              B: 75, C: 1, E: 100, missiles: 2  },
+  circle:   { x: 250,   y: -50, sprite: 'enemy_circle', health: 10, 
+              A: 0,  B: -100, C: 1, E: 20, F: 100, G: 1, H: Math.PI/2 },
+  wiggle:   { x: 100, y: -50, sprite: 'enemy_bee', health: 20, 
+              B: 50, C: 4, E: 100, firePercentage: 0.001, missiles: 2 },
+  step:     { x: 0,   y: -50, sprite: 'enemy_circle', health: 10,
+              B: 150, C: 1.2, E: 75 }
+};
+
+var OBJECT_PLAYER = 1,
+    OBJECT_PLAYER_PROJECTILE = 2,
+    OBJECT_ENEMY = 4,
+    OBJECT_ENEMY_PROJECTILE = 8,
+    OBJECT_POWERUP = 16;
+
+var startGame = function() {
+  var ua = navigator.userAgent.toLowerCase();
+
+  // Only 1 row of stars
+  if(ua.match(/android/)) {
+    Game.setBoard(0,new Starfield(50,0.6,100,true));
+  } else {
+    Game.setBoard(0,new Starfield(20,0.4,100,true));
+    Game.setBoard(1,new Starfield(50,0.6,100));
+    Game.setBoard(2,new Starfield(100,1.0,50));
+  }  
+  Game.setBoard(3,new TitleScreen("Alien Invasion", 
+                                  "Press fire to start playing",
+                                  playGame));
+};
+
+var level1 = [
+ // Start,   End, Gap,  Type,   Override
+  [ 0,      4000,  500, 'step' ],
+  [ 6000,   13000, 800, 'ltr' ],
+  [ 10000,  16000, 400, 'circle' ],
+  [ 17800,  20000, 500, 'straight', { x: 50 } ],
+  [ 18200,  20000, 500, 'straight', { x: 90 } ],
+  [ 18200,  20000, 500, 'straight', { x: 10 } ],
+  [ 22000,  25000, 400, 'wiggle', { x: 150 }],
+  [ 22000,  25000, 400, 'wiggle', { x: 100 }]
+];
+
+
+
+var playGame = function() {
+  var board = new GameBoard();
+  board.add(new PlayerShip());
+  board.add(new Level(level1,winGame));
+  Game.setBoard(3,board);
+  Game.setBoard(5,new GamePoints(0));
+};
+
+var winGame = function() {
+  Game.setBoard(3,new TitleScreen("You win!", 
+                                  "Press fire to play again",
+                                  playGame));
+  parent.postMessage('GameWin', '*');
+};
+
+var loseGame = function() {
+  Game.setBoard(3,new TitleScreen("You lose!", 
+                                  "Press fire to play again",
+                                  playGame));
+};
+
+var Starfield = function(speed,opacity,numStars,clear) {
+
+  // Set up the offscreen canvas
+  var stars = document.createElement("canvas");
+  stars.width = Game.width; 
+  stars.height = Game.height;
+  var starCtx = stars.getContext("2d");
+
+  var offset = 0;
+
+  // If the clear option is set, 
+  // make the background black instead of transparent
+  if(clear) {
+    starCtx.fillStyle = "#000";
+    starCtx.fillRect(0,0,stars.width,stars.height);
+  }
+
+  // Now draw a bunch of random 2 pixel
+  // rectangles onto the offscreen canvas
+  starCtx.fillStyle = "#FFF";
+  starCtx.globalAlpha = opacity;
+  for(var i=0;i<numStars;i++) {
+    starCtx.fillRect(Math.floor(Math.random()*stars.width),
+                     Math.floor(Math.random()*stars.height),
+                     2,
+                     2);
+  }
+
+  // This method is called every frame
+  // to draw the starfield onto the canvas
+  this.draw = function(ctx) {
+    var intOffset = Math.floor(offset);
+    var remaining = stars.height - intOffset;
+
+    // Draw the top half of the starfield
+    if(intOffset > 0) {
+      ctx.drawImage(stars,
+                0, remaining,
+                stars.width, intOffset,
+                0, 0,
+                stars.width, intOffset);
+    }
+
+    // Draw the bottom half of the starfield
+    if(remaining > 0) {
+      ctx.drawImage(stars,
+              0, 0,
+              stars.width, remaining,
+              0, intOffset,
+              stars.width, remaining);
+    }
+  };
+
+  // This method is called to update
+  // the starfield
+  this.step = function(dt) {
+    offset += dt * speed;
+    offset = offset % stars.height;
+  };
+};
+
+var PlayerShip = function() { 
+  this.setup('ship', { vx: 0, reloadTime: 0.25, maxVel: 200 });
+
+  this.reload = this.reloadTime;
+  this.x = Game.width/2 - this.w / 2;
+  this.y = Game.height - Game.playerOffset - this.h;
+
+  this.step = function(dt) {
+    if(Game.keys['left']) { this.vx = -this.maxVel; }
+    else if(Game.keys['right']) { this.vx = this.maxVel; }
+    else { this.vx = 0; }
+
+    this.x += this.vx * dt;
+
+    if(this.x < 0) { this.x = 0; }
+    else if(this.x > Game.width - this.w) { 
+      this.x = Game.width - this.w;
+    }
+
+    this.reload-=dt;
+    if(Game.keys['fire'] && this.reload < 0) {
+      Game.keys['fire'] = false;
+      this.reload = this.reloadTime;
+
+      this.board.add(new PlayerMissile(this.x,this.y+this.h/2));
+      this.board.add(new PlayerMissile(this.x+this.w,this.y+this.h/2));
+    }
+  };
+};
+
+PlayerShip.prototype = new Sprite();
+PlayerShip.prototype.type = OBJECT_PLAYER;
+
+PlayerShip.prototype.hit = function(damage) {
+  if(this.board.remove(this)) {
+    loseGame();
+  }
+};
+
+
+var PlayerMissile = function(x,y) {
+  this.setup('missile',{ vy: -700, damage: 10 });
+  this.x = x - this.w/2;
+  this.y = y - this.h; 
+};
+
+PlayerMissile.prototype = new Sprite();
+PlayerMissile.prototype.type = OBJECT_PLAYER_PROJECTILE;
+
+PlayerMissile.prototype.step = function(dt)  {
+  this.y += this.vy * dt;
+  var collision = this.board.collide(this,OBJECT_ENEMY);
+  if(collision) {
+    collision.hit(this.damage);
+    this.board.remove(this);
+  } else if(this.y < -this.h) { 
+      this.board.remove(this); 
+  }
+};
+
+
+var Enemy = function(blueprint,override) {
+  this.merge(this.baseParameters);
+  this.setup(blueprint.sprite,blueprint);
+  this.merge(override);
+};
+
+Enemy.prototype = new Sprite();
+Enemy.prototype.type = OBJECT_ENEMY;
+
+Enemy.prototype.baseParameters = { A: 0, B: 0, C: 0, D: 0, 
+                                   E: 0, F: 0, G: 0, H: 0,
+                                   t: 0, reloadTime: 0.75, 
+                                   reload: 0 };
+
+Enemy.prototype.step = function(dt) {
+  this.t += dt;
+
+  this.vx = this.A + this.B * Math.sin(this.C * this.t + this.D);
+  this.vy = this.E + this.F * Math.sin(this.G * this.t + this.H);
+
+  this.x += this.vx * dt;
+  this.y += this.vy * dt;
+
+  var collision = this.board.collide(this,OBJECT_PLAYER);
+  if(collision) {
+    collision.hit(this.damage);
+    this.board.remove(this);
+  }
+
+  if(Math.random() < 0.01 && this.reload <= 0) {
+    this.reload = this.reloadTime;
+    if(this.missiles == 2) {
+      this.board.add(new EnemyMissile(this.x+this.w-2,this.y+this.h));
+      this.board.add(new EnemyMissile(this.x+2,this.y+this.h));
+    } else {
+      this.board.add(new EnemyMissile(this.x+this.w/2,this.y+this.h));
+    }
+
+  }
+  this.reload-=dt;
+
+  if(this.y > Game.height ||
+     this.x < -this.w ||
+     this.x > Game.width) {
+       this.board.remove(this);
+  }
+};
+
+Enemy.prototype.hit = function(damage) {
+  this.health -= damage;
+  if(this.health <=0) {
+    if(this.board.remove(this)) {
+      Game.points += this.points || 100;
+      this.board.add(new Explosion(this.x + this.w/2, 
+                                   this.y + this.h/2));
+    }
+  }
+};
+
+var EnemyMissile = function(x,y) {
+  this.setup('enemy_missile',{ vy: 200, damage: 10 });
+  this.x = x - this.w/2;
+  this.y = y;
+};
+
+EnemyMissile.prototype = new Sprite();
+EnemyMissile.prototype.type = OBJECT_ENEMY_PROJECTILE;
+
+EnemyMissile.prototype.step = function(dt)  {
+  this.y += this.vy * dt;
+  var collision = this.board.collide(this,OBJECT_PLAYER)
+  if(collision) {
+    collision.hit(this.damage);
+    this.board.remove(this);
+  } else if(this.y > Game.height) {
+      this.board.remove(this); 
+  }
+};
+
+
+
+var Explosion = function(centerX,centerY) {
+  this.setup('explosion', { frame: 0 });
+  this.x = centerX - this.w/2;
+  this.y = centerY - this.h/2;
+};
+
+Explosion.prototype = new Sprite();
+
+Explosion.prototype.step = function(dt) {
+  this.frame++;
+  if(this.frame >= 12) {
+    this.board.remove(this);
+  }
+};
+
+window.addEventListener("load", function() {
+  parent.postMessage('GameInit', '*');
+  Game.initialize("game",sprites,startGame);
+});
+
+
diff --git a/space-shooter-plugin/space-shooter-resources/game/game.manifest 
b/space-shooter-plugin/space-shooter-resources/game/game.manifest
new file mode 100644
index 0000000..78aa44c
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/game/game.manifest
@@ -0,0 +1,14 @@
+CACHE MANIFEST
+
+# v1.02
+CACHE:
+index.html
+engine.js
+game.js
+images/sprites.png
+base.css
+
+NETWORK:
+http://fonts.googleapis.com/
+http://www.google-analytics.com/
+http://themes.googleusercontent.com/
diff --git 
a/space-shooter-plugin/space-shooter-resources/game/images/sprites.png 
b/space-shooter-plugin/space-shooter-resources/game/images/sprites.png
new file mode 100644
index 0000000..78c9175
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/game/images/sprites.png
Binary files differ
diff --git a/space-shooter-plugin/space-shooter-resources/game/index.html 
b/space-shooter-plugin/space-shooter-resources/game/index.html
new file mode 100644
index 0000000..c1f8362
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/game/index.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="UTF-8"/>
+  <title>Alien Invasion</title>
+  <link rel="stylesheet" href="base.css" type="text/css" />
+  <link href='http://fonts.googleapis.com/css?family=Bangers' rel='stylesheet' 
type='text/css'>
+  <meta name="viewport" content="width=device-width, user-scalable=0, 
minimum-scale=1.0, maximum-scale=1.0"/>
+  <meta name="apple-mobile-web-app-capable" content="yes">
+  <meta name="apple-mobile-web-app-status-bar-style" 
content="black-translucent">
+</head>
+<body>
+  <div id='container'>
+    <canvas id='game' width='320' height='480'></canvas>
+  </div>
+  <script src='engine.js'></script>
+  <script src='game.js'></script>
+</body>
+</html>
+
+
diff --git a/space-shooter-plugin/space-shooter-resources/modernizr.custom.js 
b/space-shooter-plugin/space-shooter-resources/modernizr.custom.js
new file mode 100644
index 0000000..1b40fa6
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/modernizr.custom.js
@@ -0,0 +1,4 @@
+/* Modernizr 2.6.2 (Custom Build) | MIT & BSD
+ * Build: http://modernizr.com/download/#-canvas-canvastext-localstorage
+ */
+;window.Modernizr=function(a,b,c){function t(a){i.cssText=a}function 
u(a,b){return t(prefixes.join(a+";")+(b||""))}function v(a,b){return typeof 
a===b}function w(a,b){return!!~(""+a).indexOf(b)}function x(a,b,d){for(var e in 
a){var f=b[a[e]];if(f!==c)return 
d===!1?a[e]:v(f,"function")?f.bind(d||b):f}return!1}var 
d="2.6.2",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l={},m={},n={},o=[],p=o.slice,q,r={}.hasOwnProperty,s;!v(r,"undefined")&&!v(r.call,"undefined")?s=function(a,b){return
 r.call(a,b)}:s=function(a,b){return b in 
a&&v(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var
 c=this;if(typeof c!="function")throw new TypeError;var 
d=p.call(arguments,1),e=function(){if(this instanceof e){var 
a=function(){};a.prototype=c.prototype;var f=new 
a,g=c.apply(f,d.concat(p.call(arguments)));return Object(g)===g?g:f}return 
c.apply(b,d.concat(p.call(arguments)))};return e}),l.canvas=functi!
 on(){var 
a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},l.canvastext=function(){return!!e.canvas&&!!v(b.createElement("canvas").getContext("2d").fillText,"function")},l.localstorage=function(){try{return
 
localStorage.setItem(g,g),localStorage.removeItem(g),!0}catch(a){return!1}};for(var
 y in 
l)s(l,y)&&(q=y.toLowerCase(),e[q]=l[y](),o.push((e[q]?"":"no-")+q));return 
e.addTest=function(a,b){if(typeof a=="object")for(var d in 
a)s(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return 
e;b=typeof b=="function"?b():b,typeof 
enableClasses!="undefined"&&enableClasses&&(f.className+=" 
"+(b?"":"no-")+a),e[a]=b}return 
e},t(""),h=j=null,e._version=d,e}(this,this.document);
\ No newline at end of file
diff --git a/space-shooter-plugin/space-shooter-resources/plugin.html 
b/space-shooter-plugin/space-shooter-resources/plugin.html
new file mode 100644
index 0000000..e16dacc
--- /dev/null
+++ b/space-shooter-plugin/space-shooter-resources/plugin.html
@@ -0,0 +1,217 @@
+<!doctype html>
+<!--
+       oVirt Space Shooter: plugin host page
+       contains plugin code that interacts with plugin API
+-->
+<html>
+<head>
+    <meta charset="utf-8">
+       <!-- Use Modernizr for detecting necessary HTML5 features like Canvas 
and Local Storage -->
+       <script src='modernizr.custom.js'></script>
+</head>
+<body>
+
+<script type='text/javascript'>
+
+       // Get API object for 'space-shooter' plugin
+    // Note: using 'parent' due to plugin code being evaluated within an 
iframe context
+       var api = parent.pluginApi('space-shooter');
+
+    // Get runtime plugin configuration, i.e. custom configuration (if any)
+    // merged on top of default configuration (if any)
+       var config = api.configObject();
+
+    // Customize API options that affect specific features of plugin API
+       api.options({
+               // Configure source origin(s), i.e. protocol://domain:port
+               // from which HTML5 message events will be accepted
+               allowedMessageOrigins: config.allowedOrigins
+       });
+
+       var victory = false; // Has player won at least one game in the current 
game dialog?
+       var dataCenter = null; // Data Center entity associated with the 
current game dialog
+
+       var gameContentWindow = null; // Reference to game Window object
+       var subTabWindow = null; // Reference to 'Score' sub tab Window object
+
+       var dialogToken = 'space-shooter-dialog';
+       var subTabToken = 'space-shooter-dc-score';
+
+       var dialogUrl = 'plugin/space-shooter/game/index.html';
+       var subTabUrl = 'plugin/space-shooter/dc-score.html';
+
+       var init = function() {
+               // Add new action button to Data Center main tab
+               api.addMainTabActionButton('DataCenter', 'Protect DataCenter 
from Alien Invasion',
+            // Extra options for addMainTabActionButton function
+            {
+                isEnabled: function() {
+                    // Enable button only when selecting single Data Center
+                    return arguments.length == 1;
+                           },
+                           onClick: function() {
+                    // Reset victory flag and open new game dialog
+                                   victory = false;
+                                   openDialog();
+                           },
+                           location: 'OnlyFromContext' // Button available 
only from context menu
+                   }
+        );
+
+               // Add new 'Score' sub tab under Data Center main tab
+               api.addSubTab('DataCenter', 'Space Shooter Score', subTabToken, 
subTabUrl);
+       };
+
+       var openDialog = function() {
+               // Show modal dialog with actual game content
+               api.showDialog(dataCenter.name + ' under attack', dialogToken, 
dialogUrl,
+            '340px', '560px',
+                       // Extra options for showDialog function
+                       {
+                               closeIconVisible: false, // Hide dialog's close 
icon
+                               closeOnEscKey: false, // Prevent user from 
closing dialog with Escape key
+                               resizeEnabled: true, // Allow dialog resizing
+                               buttons:
+                               [
+                    // Button for those who cannot beat the game (it can 
happen!)
+                                       {
+                                               label: 'Cheat',
+                                               onClick: cheatGame
+                                       },
+                    // Button to close the dialog
+                                       {
+                                               label: 'Get me outta here',
+                                               onClick: closeDialog
+                                       }
+                               ]
+                       });
+       };
+
+       var closeDialog = function() {
+               if (victory) {
+                       api.closeDialog(dialogToken);
+               } else {
+                       alert('Not so fast! Destroy the aliens at least once.');
+               }
+       };
+
+       var cheatGame = function() {
+               if (gameContentWindow != null) {
+                       // Invoke function on game Window object
+                       gameContentWindow.winGame();
+               }
+       };
+
+       var getKey = function() {
+               // Calculate unique key (string) for storing and retrieving 
game score for
+               // given user + Data Center combination, i.e. 
admin@internal_dc123_DCScore
+               return dataCenter != null ? api.loginUserName() + "_" + 
dataCenter.id + "_DCScore" : null;
+       };
+
+       var getScore = function() {
+               // Return current score as string, or 0 if there is no score yet
+               return localStorage.getItem(getKey()) || '0';
+       };
+
+       var incScore = function() {
+               // Increment current score by 1, store the result as string
+               localStorage.setItem(getKey(), (parseInt(getScore(), 10) + 1) + 
'');
+       };
+
+       var updateSubTab = function() {
+               if (subTabWindow == null || dataCenter == null) {
+                       return;
+               }
+
+               // Get the current user score
+               var score = getScore();
+               var intScore = parseInt(score, 10);
+
+               // Calculate rank based on user score
+               var rank, rankColor;
+               switch (true) {
+                       case (intScore >= 10):
+                               rank = 'Laser Master';
+                               rankColor = 'green';
+                               break;
+                       case (intScore >= 3):
+                               rank = 'Veteran';
+                               rankColor = 'orange';
+                               break;
+                       case (intScore >= 1):
+                               rank = 'Survivor';
+                               rankColor = 'red';
+                               break;
+                       default:
+                               rank = 'Newbie';
+                               rankColor = 'gray';
+                               break;
+               }
+
+               // Update 'Score' sub tab
+               subTabWindow.update(dataCenter.name, score, rank, rankColor);
+       };
+
+       var browserRocks = function() {
+               // Detect necessary HTML5 features via Modernizr
+               return Modernizr.canvas && Modernizr.canvastext && 
Modernizr.localstorage;
+       };
+
+       // Register event handler functions for later invocation by UI plugin 
infrastructure
+       api.register({
+
+        // Called by the infrastructure as part of plugin initialization,
+        // will be called just once during the lifetime of a plugin
+               UiInit: function() {
+                       if (browserRocks()) {
+                               init();
+                       }
+               },
+
+        // Called when item selection changes in Data Center main tab,
+        // useful for keeping track of currently selected Data Center entity
+               DataCenterSelectionChange: function() {
+                       if (arguments.length == 1) {
+                               // Single entity was selected, remember it and 
make 'Score' sub tab visible
+                               dataCenter = arguments[0];
+                               api.setTabAccessible(subTabToken, true);
+                       } else {
+                               // Otherwise, forget the entity and hide 
'Score' sub tab
+                               dataCenter = null;
+                               api.setTabAccessible(subTabToken, false);
+                       }
+               },
+
+        // Called when another Window (i.e. custom UI contributed by a plugin)
+        // sends message to WebAdmin Window via HTML5 postMessage API,
+        // ideal for cross-window communication that works across different 
origins
+               MessageReceived: function(data, sourceWindow) {
+                       // If we get here, we already passed allowed source 
origin check
+                       switch (data) {
+                               // Game content just got loaded
+                               case 'GameInit':
+                                       gameContentWindow = sourceWindow;
+                                       break;
+                               // User just won the game
+                               case 'GameWin':
+                                       victory = true;
+                                       incScore();
+                                       updateSubTab();
+                                       break;
+                               // 'Score' sub tab asks for current user score
+                               case 'GetDataCenterScore':
+                                       subTabWindow = sourceWindow;
+                                       updateSubTab();
+                                       break;
+                       }
+               }
+
+       });
+
+       // Tell plugin infrastructure that we are good to go, expect UiInit 
callback
+       api.ready();
+
+</script>
+
+</body>
+</html>
diff --git a/space-shooter-plugin/space-shooter.json 
b/space-shooter-plugin/space-shooter.json
new file mode 100644
index 0000000..36f6741
--- /dev/null
+++ b/space-shooter-plugin/space-shooter.json
@@ -0,0 +1,19 @@
+{
+
+    // Unique name of the plugin
+    "name": "space-shooter",
+
+    // URL of plugin host page that bootstraps plugin code
+    // This URL maps to 
$ENGINE_USR/ui-plugins/space-shooter-resources/plugin.html
+    "url": "plugin/space-shooter/plugin.html",
+
+    // Path to plugin resource files relative to descriptor
+    // This path maps to $ENGINE_USR/ui-plugins/space-shooter-resources
+    "resourcePath": "space-shooter-resources",
+
+    // Default configuration associated with the plugin
+    "config": {
+        "allowedOrigins": ["http://127.0.0.1:8080";]
+    }
+
+}


-- 
To view, visit http://gerrit.ovirt.org/23813
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I347b18ca57b7b41e4cdf42e528cf5a7aff60c583
Gerrit-PatchSet: 1
Gerrit-Project: samples-uiplugins
Gerrit-Branch: master
Gerrit-Owner: Vojtech Szocs <[email protected]>
_______________________________________________
Engine-patches mailing list
[email protected]
http://lists.ovirt.org/mailman/listinfo/engine-patches

Reply via email to