Эх сурвалжийг харах

HUE-266. Allow the user to choose their background as a preference.

* CCS.Desktop's getState method now returns a cached state, only fetching it on startup once. Also, it now fetches the entire user pref object, not just the window state.
* Moved all the background logic into a standalone js file
* Renamed all the bg images
Aaron Newton 15 жил өмнө
parent
commit
c01773a310
33 өөрчлөгдсөн 400 нэмэгдсэн , 104 устгасан
  1. 42 81
      desktop/core/src/desktop/templates/index.mako
  2. 13 13
      desktop/core/static/art/desktops/README.txt
  3. 0 0
      desktop/core/static/art/desktops/blue_windows.jpg
  4. 0 0
      desktop/core/static/art/desktops/blue_windows.logo.png
  5. 0 0
      desktop/core/static/art/desktops/color_pencils.jpg
  6. 0 0
      desktop/core/static/art/desktops/color_pencils.logo.png
  7. 0 0
      desktop/core/static/art/desktops/fuzzy_sparkles.jpg
  8. 0 0
      desktop/core/static/art/desktops/fuzzy_sparkles.logo.png
  9. 0 0
      desktop/core/static/art/desktops/green_leaves.jpg
  10. 0 0
      desktop/core/static/art/desktops/green_leaves.logo.png
  11. 0 0
      desktop/core/static/art/desktops/hadoop.jpg
  12. 0 0
      desktop/core/static/art/desktops/hadoop.logo.png
  13. 0 0
      desktop/core/static/art/desktops/painted_wood.jpg
  14. 0 0
      desktop/core/static/art/desktops/painted_wood.logo.png
  15. 0 0
      desktop/core/static/art/desktops/pantone_cards.jpg
  16. 0 0
      desktop/core/static/art/desktops/pantone_cards.logo.png
  17. 0 0
      desktop/core/static/art/desktops/pastels.jpg
  18. 0 0
      desktop/core/static/art/desktops/pastels.logo.png
  19. 0 0
      desktop/core/static/art/desktops/pencil_tips.jpg
  20. 0 0
      desktop/core/static/art/desktops/pencil_tips.logo.png
  21. 0 0
      desktop/core/static/art/desktops/pencil_tips2.jpg
  22. 0 0
      desktop/core/static/art/desktops/pencil_tips2.logo.png
  23. 0 0
      desktop/core/static/art/desktops/purple_flower.jpg
  24. 0 0
      desktop/core/static/art/desktops/purple_flower.logo.png
  25. 0 0
      desktop/core/static/art/desktops/red_wood.jpg
  26. 0 0
      desktop/core/static/art/desktops/red_wood.logo.png
  27. 0 0
      desktop/core/static/art/desktops/tree_frog.jpg
  28. 0 0
      desktop/core/static/art/desktops/tree_frog.logo.png
  29. 44 1
      desktop/core/static/css/desktop.css
  30. 286 0
      desktop/core/static/js/Source/CCS/CCS.Desktop.BackgroundManager.js
  31. 12 7
      desktop/core/static/js/Source/CCS/CCS.Desktop.js
  32. 2 1
      desktop/core/static/js/package.yml
  33. 1 1
      ext/thirdparty/js/widgets.hash

+ 42 - 81
desktop/core/src/desktop/templates/index.mako

@@ -16,16 +16,16 @@
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
 <html>
 <head>
-	<meta http-equiv="X-UA-Compatible" content="IE=8" />
-	<title>Hue</title>
-	<link rel="shortcut icon" href="/static/art/favicon_solid.png" type="image/x-icon" /> 
-	<link rel="icon" href="/static/art/favicon_solid.png" type="image/x-icon" />
-	<link rel="stylesheet" href="/static/css/shared.css" type="text/css" media="screen" title="no title" charset="utf-8">
-	<link rel="stylesheet" href="/static/css/reset.css" type="text/css" media="screen" charset="utf-8">
-	<link rel="stylesheet" href="/static/css/windows.css" type="text/css" media="screen" charset="utf-8">
-	<link rel="stylesheet" href="/static/css/desktop.css" type="text/css" media="screen" charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=8" />
+  <title>Hue</title>
+  <link rel="shortcut icon" href="/static/art/favicon_solid.png" type="image/x-icon" /> 
+  <link rel="icon" href="/static/art/favicon_solid.png" type="image/x-icon" />
+  <link rel="stylesheet" href="/static/css/shared.css" type="text/css" media="screen" title="no title" charset="utf-8">
+  <link rel="stylesheet" href="/static/css/reset.css" type="text/css" media="screen" charset="utf-8">
+  <link rel="stylesheet" href="/static/css/windows.css" type="text/css" media="screen" charset="utf-8">
+  <link rel="stylesheet" href="/static/css/desktop.css" type="text/css" media="screen" charset="utf-8">
 
-  <script src="/depender/build?client=true&require=dbug,DomReady,Cookie,Element.Dimensions,Element.Style"></script>
+  <script src="/depender/build?client=true&require=dbug,DomReady,Cookie,Element.Dimensions,Element.Style,CCS.Desktop.BackgroundManager,Cookie"></script>
   <!--[if IE 8]>
       <script>
           window.ie8 = true;
@@ -34,33 +34,13 @@
   <script>
   
   window.addEvent('domready', function(){
-    //this method automatically sizes the desktop image to fill the screen
-    var sizer = function(){
-      //get the backgrounds (there may be more than one if rotation is in process)
-      var bgs = $('bg').getElements('.desktop-bg');
-      //get the window dimensions
-      var size = window.getSize();
-      //if the aspect ratio of the window is > 1.6
-      if (size.x/size.y > 1.6) {
-        //then set the width of the image to equal the window
-        bgs.setStyles({
-          width: size.x,
-          height: 'auto'
-        });
-      } else {
-        //else set the height to match the window
-        bgs.setStyles({
-          width: 'auto',
-          height: size.y
-        });
-      }
-    };
-    //when the window is resized, resize the background
-    window.addEvent('resize', sizer);
-    
     if (Browser.Engine.trident) {
+      //if we're in IE, there's a note about the fact that Hue doesn't love IE
+      //add a click handler for hiding this note.
       $('closeWarning').addEvent('click', function(){
+        //store the preference
         Cookie.write('desktop-browser-warned', true);
+        //remove the class (which hides the warning)
         $(document.body).removeClass('warned');
       });
       if (!Cookie.read('desktop-browser-warned')) $(document.body).addClass('warned');
@@ -68,26 +48,32 @@
     }
     var appName = "Hue";
     Depender.require({
-      scripts: ["CCS.Request", "CCS.User", "CCS.Desktop", "CCS.Desktop.Config", "CCS.Desktop.FlashMessage", 
+      scripts: ["CCS.Request", "CCS.User", "CCS.Desktop.Config", "CCS.Desktop.FlashMessage",
         "CCS.Desktop.Keys", "CCS.Login", "StickyWin.PointyTip", "Element.Delegation", "Fx.Tween", "Fx.Elements"],
       callback: function(){
-        //before fading in the screen, resize the background to match the window size
-        sizer();
+        //get the background images
         var bgEls = $('bg').getElements('img');
+        //include the background container
         bgEls.push($('bg'));
         var styles = {};
+        //loop through each and create an effect that fades them from 0 to 1 opacity
         bgEls.each(function(el, i){
           styles[i.toString()] = { opacity: [0, 1] };
         });
+        //fade them in
         new Fx.Elements(bgEls, {
           duration: 500
         }).start(styles);
-        $(document.body).addEvent('click:relay(img.desktop-logo)', rotateBG);
         
+        //configure the clientcide assets location.
         Clientcide.setAssetLocation("/static/js/ThirdParty/clientcide/Assets");
         var growled = {};
+        //add a notification for when apps are launched
         var launchGrowl = function(component){
+          //get the app name ("File Browser" from "filebrowser")
           var appName = CCS.Desktop.getAppName(component);
+          //show the appropriate flash message; if it's loaded then we're just "launching"
+          //else show "loading"
           var loading = 'Loading ' + appName;
           var launching = 'Launching ' + appName;
           var msg = loading;
@@ -109,7 +95,7 @@
           onBeforeLaunch: launchGrowl,
           onAfterLaunch: clearGrowl
         });
-
+        //fade out the ccs-loading message
         (function(){
           $('ccs-loading').fade('out').get('tween').clearChain().chain(function(){
             $('ccs-loading').destroy();
@@ -119,7 +105,9 @@
         //when the user logs in
         CCS.User.withUser(function(user){
           var bsLoaded;
+          //this method runs once the bootstrap is run and the apps are registered
           var bootstrapped = function(){
+            //ensure it only runs once
             if (bsLoaded) return;
             bsLoaded = true;
 
@@ -142,29 +130,37 @@
                   fn();
                 });
               }
+              //display the user as logged in
               $('ccs-profileLink').set('text', user.username).addClass('loggedIn');
               $(document.body).addClass('ccs-loaded');
               window.scrollTo(0,0);
+              //fade in the toolbar
               $('ccs-toolbar').show().tween('opacity', 0, 1);
+              //and the dock
               $('ccs-dock').tween('opacity', 0, 1);
             };
 
+            //IE needs a brief delay
             if (Browser.Engine.trident) finalize.delay(100);
             else finalize();
           };
 
+          //load the bootstraps
           new Element('script', {
             src: '/bootstrap.js',
             events: {
+              //on load call the bootstrapped method above
               load: function() {
                 bootstrapped();
               },
+              //IE requires you monitor the readystate yourself for script tags
               readystatechange: function(){
                 if (['loaded', 'complete'].contains(this.readyState)) bootstrapped();
               }
             }
           }).inject(document.head);
         });
+
       }
     });
   });
@@ -179,50 +175,9 @@
   </script>
 </head>
 <body>
+  <ul id="desktop-menu" class="desktop-menu" style="position:absolute; top: -100000px;">
+  </ul>
   <div id="bg">
-    <script>
-      (function(){
-        var NUMBER_OF_BACKGROUNDS = 13; //number of backgrounds in /static/art/desktops
-        var r = $random(1, NUMBER_OF_BACKGROUNDS);
-        //inject a random background
-        document.write('<img src="/static/art/desktops/' + r + '.jpg" class="desktop-bg"><img src="/static/art/desktops/' + r + '.logo.png" class="desktop-logo">');
-        //background rotation function
-        this.rotateBG = function(){
-          //grab the images there now
-          var bg = $('bg').getElement('.desktop-bg');
-          var logo = $('bg').getElement('.desktop-logo');
-          //pick a new random one
-          if (r < NUMBER_OF_BACKGROUNDS) r++;
-          else r = 1;
-          //inject them
-          new Element('img', {
-            src: '/static/art/desktops/' + r + '.logo.png',
-            'class': 'desktop-logo'
-          }).inject($('bg'), 'top');
-          new Element('img', {
-            src: '/static/art/desktops/' + r + '.jpg',
-            'class': 'desktop-bg',
-            styles: bg.getStyles('width', 'height'),
-            events: {
-              load: function(){
-                //after they load, crossfade
-                new Fx.Elements([bg, logo], {duration: 500}).start({
-                  '0': {
-                    'opacity': 0
-                  },
-                  '1': {
-                    'opacity': 0
-                  }
-                }).chain(function(){
-                  bg.destroy();
-                  logo.destroy();
-                });
-              }
-            }
-          }).inject($('bg'), 'top');
-        };
-      })();
-    </script>
   </div>
   <div id="browserWarn">Hue is best experienced in <a target="browsers" href="http://getfirefox.com">Mozilla Firefox</a>, <a target="browsers" href="http://www.apple.com/safari/">Apple Safari</a>, or <a target="browsers" href="http://www.google.com/chrome">Google Chrome</a> <a id="closeWarning"></a></div>
   <div id="ccs-desktop" class="ccs-shared">
@@ -260,8 +215,14 @@
   </div>
 
     <script>
+    (function(){
+      var state = CCS.Desktop.getState();
+      var options = {};
+      if (state.background) options.current = state.background;
+      new BackgroundManager($('bg'), $('desktop-menu'), options);
       if (Browser.Engine.trident) $(document.body).addClass('IEroot');
       $(document.body).addClass(Browser.Engine.name);
+    })();
     </script>
 </body>
 </html>

+ 13 - 13
desktop/core/static/art/desktops/README.txt

@@ -1,17 +1,17 @@
 The following desktops for Hue are included under the Creative Commons Attribution 2.0 Generic license:
 http://creativecommons.org/licenses/by/3.0/
 
-1.jpg: http://www.flickr.com/photos/luchilu/407314253/
-2.jpg: http://www.flickr.com/photos/luchilu/433047927/
-3.jpg: http://www.flickr.com/photos/imageme/3151748793/
-4.jpg: http://www.flickr.com/photos/bzuk/3205696065/
-5.jpg: http://www.flickr.com/photos/imageme/3212097572/
-6.jpg: http://www.flickr.com/photos/ehecatzin/3679887417/
-7.jpg: Aaron Newton
-8.jpg: Aaron Newton
-9.jpg: Aaron Newton
-10.jpg: Hadoop Logo, Apache License, http://svn.apache.org/repos/asf/hadoop/logos/out_rgb/
-11.jpg: http://www.flickr.com/photos/phantasy__photo/3443256207/
-12.jpg: http://www.flickr.com/photos/41369090@N02/3813622011/
-13.jpg: http://www.flickr.com/photos/artbystevejohnson/4618054521/
+pencil_tips.jpg: http://www.flickr.com/photos/luchilu/407314253/
+color_pencils.jpg: http://www.flickr.com/photos/luchilu/433047927/
+pencil_tips2.jpg: http://www.flickr.com/photos/imageme/3151748793/
+painted_wood.jpg: http://www.flickr.com/photos/bzuk/3205696065/
+purple_flower.jpg: http://www.flickr.com/photos/imageme/3212097572/
+pantone_cards.jpg: http://www.flickr.com/photos/ehecatzin/3679887417/
+blue_windows.jpg: Aaron Newton
+tree_frog.jpg: Aaron Newton
+red_wood.jpg: Aaron Newton
+hadoop.jpg: Hadoop Logo, Apache License, http://svn.apache.org/repos/asf/hadoop/logos/out_rgb/
+fuzzy_sparkles.jpg: http://www.flickr.com/photos/phantasy__photo/3443256207/
+green_leaves.jpg: http://www.flickr.com/photos/41369090@N02/3813622011/
+pastels.jpg: http://www.flickr.com/photos/artbystevejohnson/4618054521/
 

+ 0 - 0
desktop/core/static/art/desktops/7.jpg → desktop/core/static/art/desktops/blue_windows.jpg


+ 0 - 0
desktop/core/static/art/desktops/7.logo.png → desktop/core/static/art/desktops/blue_windows.logo.png


+ 0 - 0
desktop/core/static/art/desktops/2.jpg → desktop/core/static/art/desktops/color_pencils.jpg


+ 0 - 0
desktop/core/static/art/desktops/2.logo.png → desktop/core/static/art/desktops/color_pencils.logo.png


+ 0 - 0
desktop/core/static/art/desktops/11.jpg → desktop/core/static/art/desktops/fuzzy_sparkles.jpg


+ 0 - 0
desktop/core/static/art/desktops/11.logo.png → desktop/core/static/art/desktops/fuzzy_sparkles.logo.png


+ 0 - 0
desktop/core/static/art/desktops/12.jpg → desktop/core/static/art/desktops/green_leaves.jpg


+ 0 - 0
desktop/core/static/art/desktops/12.logo.png → desktop/core/static/art/desktops/green_leaves.logo.png


+ 0 - 0
desktop/core/static/art/desktops/10.jpg → desktop/core/static/art/desktops/hadoop.jpg


+ 0 - 0
desktop/core/static/art/desktops/10.logo.png → desktop/core/static/art/desktops/hadoop.logo.png


+ 0 - 0
desktop/core/static/art/desktops/4.jpg → desktop/core/static/art/desktops/painted_wood.jpg


+ 0 - 0
desktop/core/static/art/desktops/4.logo.png → desktop/core/static/art/desktops/painted_wood.logo.png


+ 0 - 0
desktop/core/static/art/desktops/6.jpg → desktop/core/static/art/desktops/pantone_cards.jpg


+ 0 - 0
desktop/core/static/art/desktops/6.logo.png → desktop/core/static/art/desktops/pantone_cards.logo.png


+ 0 - 0
desktop/core/static/art/desktops/13.jpg → desktop/core/static/art/desktops/pastels.jpg


+ 0 - 0
desktop/core/static/art/desktops/13.logo.png → desktop/core/static/art/desktops/pastels.logo.png


+ 0 - 0
desktop/core/static/art/desktops/1.jpg → desktop/core/static/art/desktops/pencil_tips.jpg


+ 0 - 0
desktop/core/static/art/desktops/1.logo.png → desktop/core/static/art/desktops/pencil_tips.logo.png


+ 0 - 0
desktop/core/static/art/desktops/3.jpg → desktop/core/static/art/desktops/pencil_tips2.jpg


+ 0 - 0
desktop/core/static/art/desktops/3.logo.png → desktop/core/static/art/desktops/pencil_tips2.logo.png


+ 0 - 0
desktop/core/static/art/desktops/5.jpg → desktop/core/static/art/desktops/purple_flower.jpg


+ 0 - 0
desktop/core/static/art/desktops/5.logo.png → desktop/core/static/art/desktops/purple_flower.logo.png


+ 0 - 0
desktop/core/static/art/desktops/9.jpg → desktop/core/static/art/desktops/red_wood.jpg


+ 0 - 0
desktop/core/static/art/desktops/9.logo.png → desktop/core/static/art/desktops/red_wood.logo.png


+ 0 - 0
desktop/core/static/art/desktops/8.jpg → desktop/core/static/art/desktops/tree_frog.jpg


+ 0 - 0
desktop/core/static/art/desktops/8.logo.png → desktop/core/static/art/desktops/tree_frog.logo.png


+ 44 - 1
desktop/core/static/css/desktop.css

@@ -19,6 +19,7 @@ body>.ccs-error-popup {
 }
 #bg {
 	width: 100%;
+	height: 100%;
 	position: absolute;
 	opacity: 0;
 	filter: alpha(opacity=0);
@@ -32,7 +33,6 @@ body>.ccs-error-popup {
 	-ms-filter: inherit;
 }
 #bg .desktop-logo {
-	cursor:pointer;
 	opacity: inherit;
 	filter: inherit;
 	-ms-filter: inherit;
@@ -41,6 +41,7 @@ body>.ccs-error-popup {
 	position: fixed;
 	bottom: 60px;
 	right: 30px;
+	z-index: 1;
 }
 
 
@@ -432,4 +433,46 @@ input[type=submit] {
 	width: 16px;
 	height: 16px;
 	background: url(/static/art/icons/grey_cross.png);
+}
+
+
+.desktop-menu {
+	font-family: Arial;
+	font-size: 13px;
+	list-style: none;
+	margin: 0;
+	padding: 5px 1px;
+}
+
+.desktop-menu li {
+	padding: 0;
+	margin: 0;
+}
+
+.desktop-menu a {
+	outline: none;
+	margin: 0;
+	display: block;
+	padding: 2px 16px;
+	text-decoration: none;
+	color: #222;
+}
+.desktop-menu a.current {
+	font-weight: bold;
+	background-color: #ccc;
+}
+.desktop-menu a.selected {
+	color: #fff;
+	background: url(/static/art/context-menu-bg.png);
+	text-shadow: 0px 1px 1px #0e4f9c;
+	text-decoration:none;
+}
+
+.desktop-menu li.menu-separator {
+	height: 4px;
+	border-bottom: 1px solid #ddd;
+	margin-bottom: 4px;
+}
+.desktop-menu li.menu-separator a {
+	display: none;
 }

+ 286 - 0
desktop/core/static/js/Source/CCS/CCS.Desktop.BackgroundManager.js

@@ -0,0 +1,286 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.	See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.	Cloudera, Inc. 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.
+/*
+---
+description: Manages the background image/color of the Hue desktop.
+provides: [CCS.Desktop.BackgroundManager]
+requires: [ccs-shared/CCS.Desktop, Widgets/ART.Menu, More/Fx.Elements, Core/Request.JSON]
+script: CCS.Desktop.BackgroundManager.js
+
+...
+*/
+
+ART.Sheet.define('menu.art.desktop-menu', {
+	'z-index': 10002
+});
+
+var BackgroundManager = new Class({
+
+	Implements: [Options, Events],
+
+	options: {
+		//onChoose: $empty,
+
+		//these images are located in desktop/core/art/desktops
+		//the object below is a key/value set of pretty name / file name pairs.
+		//each file has a logo and an image (file_name.jpg, file_name.logo.png) in
+		//the desktops directory
+		backgroundImages: {
+			'Pencil Tips': 'pencil_tips',
+			'Color Pencils': 'color_pencils',
+			'Pencil Tips2': 'pencil_tips2',
+			'Painted Wood': 'painted_wood',
+			'Purple Flower': 'purple_flower',
+			'Pantone Cards': 'pantone_cards',
+			'Blue Windows': 'blue_windows',
+			'Tree Frog': 'tree_frog',
+			'Red Wood': 'red_wood',
+			'Hadoop!': 'hadoop',
+			'Fuzzy Sparkles': 'fuzzy_sparkles',
+			'Green Leaves': 'green_leaves',
+			'Pastels': 'pastels'
+		},
+		//and these are static colors
+		backgroundColors: {
+			'Solid Grey': '#444',
+			'Rich Blue': '#2f6390',
+			'Grey Green': '#5F7D5F',
+			'Khaki': '#E0DCAD'
+		},
+		//current is the background to show
+		//by default it returns a random background image
+		//value can be a string - a key of either backgroundColors or backgroundImages
+		//or a function that returns a similar key.
+		current: function(){
+			return $H(this.options.backgroundImages).getKeys().getRandom();
+		},
+		//element to inject the menu into
+		target: 'ccs-desktop'
+	},
+	//where to get the background prefs from the server
+	prefsUrl: "prefs/background",
+
+	initialize: function(container, list, options){
+		this.setOptions(options);
+		this.list = $(list);
+		this.container = $(container);
+		//get the current /startup state
+		this.current = $lambda(this.options.current).apply(this);
+		//if the value is a color
+		if (this.options.backgroundColors[this.current]) {
+			//create an image for the hue logo, chosen at random from the background images
+			new Element('img', {
+				src: '/static/art/desktops/' + $H(this.options.backgroundImages).getValues().getRandom() + '.logo.png',
+				'class': 'desktop-logo'
+			}).inject(this.container, 'top');
+			//set the bg color
+			this.container.setStyle('background-color', this.options.backgroundColors[this.current]);
+		} else {
+			//otherwise inject the background & logo
+			this.injectImages(this.current);
+		}
+		//remaining setup...
+		this._bound = {
+			resizeImages: this.resizeImages.bind(this)
+		};
+		this.list.adopt(this.getMenuItems());
+		this.createMenu();
+		this.resizeImages();
+		this.attach();
+	},
+	//attaches resize functionality for resizing bg images
+	attach: function(){
+		//when the window is resized, resize the background
+		window.addEvent('resize', this._bound.resizeImages);
+	},
+	//detaches resize functionality
+	detach: function(){
+		window.removeEvent('resize', this._bound.resizeImages);
+	},
+	//resizes the background image (if present) to match the window dimensions
+	resizeImages: function(){
+		//get the backgrounds (there may be more than one if rotation is in process)
+		var bgs = this.container.getElements('.desktop-bg');
+		//get the window dimensions
+		var size = window.getSize();
+		//if the aspect ratio of the window is > 1.6
+		if (size.x/size.y > 1.6) {
+			//then set the width of the image to equal the window
+			bgs.setStyles({
+				width: size.x,
+				height: 'auto'
+			});
+		} else {
+			//else set the height to match the window
+			bgs.setStyles({
+				width: 'auto',
+				height: size.y
+			});
+		}
+	},
+	//injects the appropriate background and logo images into the document
+	//name - key of backgroundImages
+	//events - (object) any events to add to the background image; optional
+	injectImages: function(name, events){
+		var bg = this.container.getElement('.desktop-bg');
+		var styles = bg ? bg.getStyles('width', 'height'): {};
+		var img = new Element('img', {
+			src: '/static/art/desktops/' + this.options.backgroundImages[name] + '.jpg',
+			'class': 'desktop-bg',
+			styles: styles
+		}).inject(this.container, 'top');
+		new Element('img', {
+			src: '/static/art/desktops/' + this.options.backgroundImages[name] + '.logo.png',
+			'class': 'desktop-logo'
+		}).inject(this.container, 'top');
+
+
+		if (events) img.addEvents(events);
+		this.resizeImages();
+	},
+	//set the background to the given name
+	//noFx - boolean; optional. If true, no transition is used
+	setBackground: function(name, noFx){
+		//if the name is a key in the background images, rotate the bg image
+		if (this.options.backgroundImages[name]) {
+			this.rotateBackgroundImage(name, noFx);
+		} else if (this.options.backgroundColors[name]) {
+			//else, if the name is a key of the bg colors, hide the bg image (if there is one)
+			//and fade to the new color.
+			this.current = name;
+			//fade the bg color
+			this.container.tween('background-color', this.options.backgroundColors[name]);
+			var bg = this.container.getElement('.desktop-bg');
+			// if there's a bg image, fade it out.
+			if (bg) {
+				if (noFx){
+					bg.setStyle('opacity', 0).setStyle('visibility', 'visible');
+				} else {
+					bg.tween('opacity', 0).get('tween').chain(function(){
+						bg.setStyle('visibility', 'visible');
+					});
+				}
+			}
+		} else {
+			//key error
+			dbug.warn('could not load background preference: ', name);
+			return;
+		}
+		//get the current selected item in the menu and remove that designation
+		this.list.getElements('.current').removeClass('current');
+		//mark the new one as current
+		this.links[name].addClass('current');
+		this.fireEvent('choose', name);
+	},
+	//choose a random background image
+	chooseRandom: function(){
+		this.rotateBackgroundImage($H(this.options.backgroundImages).getValues().getRandom());
+	},
+	//rotate to a new background image
+	//name - the key of the background image; if not defined, chooses a random one
+	//noFx - boolean; if true use no transition
+	rotateBackgroundImage: function(name, noFx){
+		this.current = name;
+		//grab the images there now
+		var bg = this.container.getElement('.desktop-bg');
+		var logo = this.container.getElement('.desktop-logo');
+
+		//inject the image and icon
+		this.injectImages(name || $random(0, this.options.backgroundImages.length-1), {
+			load: function(){
+				//if no transition, just destroy the existing ones
+				if (noFx){
+					if (bg) bg.destroy();
+					if (logo) logo.destroy();
+				} else {
+					//otherwise crossfade
+					var style = {
+							'opacity': 0
+						},
+						images = [],
+						fx = {};
+					if (bg) images.push(bg);
+					if (logo) images.push(logo);
+					images.each(function(img, i){
+						fx[i] = style;
+					});
+					//after they load, crossfade
+					new Fx.Elements(images, {duration: 500}).start(fx).chain(function(){
+						if (bg) bg.destroy();
+						if (logo) logo.destroy();
+					});
+				}
+			}
+		});
+	},
+	//gets the LI elements for the menu consisting of all the images and colors
+	getMenuItems: function(){
+		//now we create the menu
+		//first we list all the names
+		var items = new Elements();
+		this.links = {};
+
+		//given a name, create a LI element with an anchor for that name
+		var makeItem = function(name){
+			var link = new Element('a', {
+				html: name,
+				'class': name == this.current ? 'current' : ''
+			});
+			this.links[name] = link;
+			return new Element('li', {
+				'class': 'menu-item'
+			}).adopt(link);
+		}.bind(this);
+
+		for (name in this.options.backgroundImages) {
+			items.push(makeItem(name));
+		}
+		//a separator
+		items.push(
+			new Element('li', {
+				'class': 'menu-separator'
+			})
+		);
+		//and then all the static colors
+		for (name in this.options.backgroundColors){
+			items.push(makeItem(name));
+		}
+		return items;
+	},
+	//creates a new instance of ART.Menu for the background container
+	createMenu: function(){
+		this.menu = new ART.Menu({
+			className: 'art desktop-menu',
+			startEvent: 'contextmenu',
+			tabIndex: 10,
+			//when the user chooses an item
+			onPress: function(link){
+				//get the index of the new one
+				var name = link.get('html');
+				//set the background and store the preference
+				this.setBackground(name);
+				this.store();
+			}.bind(this)
+		}, this.list, this.container).inject($(this.options.target));
+	},
+	store: function(){
+		//send a request to store the preference on the server side.
+		new Request.JSON({
+				url: this.prefsUrl,
+				method: "post"
+		}).send("set=" + this.current);
+	}
+});

+ 12 - 7
desktop/core/static/js/Source/CCS/CCS.Desktop.js

@@ -47,6 +47,7 @@ CCS.Desktop = {
 	launchers: {},
 	loadedComponents: {},
 	stateUrl: "prefs/state",
+	prefsUrl: "prefs/",
 
 	/******************
 	 * PUBLIC METHODS
@@ -360,8 +361,9 @@ CCS.Desktop = {
 	//restores a state to the desktop
 	//states - (object) the state of all the open apps (returned by .serialize())
 	restore: function(states){
+		this.states = $H(JSON.decode(states));
 		var loaded_component;
-		if (states) {
+		if (this.states.getLength()) {
 			var hidden, msg;
 			//hider obscures the desktop while it loads apps
 			var hider = function(){
@@ -396,7 +398,7 @@ CCS.Desktop = {
 			//reference count the number of components we're going to load
 			var components_to_load = 0;
 			//iterate over all the components
-			$each(states, function(componentStates, component){
+			this.states.each(function(componentStates, component){
 				if (!this.hasApp(component)) {
 					dbug.warn('could not find application: %s', component);
 					return;
@@ -551,24 +553,27 @@ CCS.Desktop = {
 			this.resetAndRefresh();
 			return;
 		}
-		var state = JSON.decode(this.getState(), true);
-		this.restore(state);
-		return !!state;
+		this.prefs = this.getState();
+		if (this.prefs) this.restore(this.prefs.state);
+		return !!this.prefs && this.prefs.state;
 	},
 
 	//This request is synchronous because this method determines whether or not there is a session to restore.  It is a decision point, at which the app continues in one of two ways.
 	//Either restoration, or be initializing the default desktop.  In order to do this synchronously we would need to use callbacks.
 	getState: function(){
+		//only get this once; it's syncronous
+		if (this._fetchedState) return this._fetchedState;
 		var result;
 		var jsonRequest = new Request.JSON({
-			url: this.stateUrl,
+			url: this.prefsUrl,
 			async: false,
-			method: "post",
+			method: "get",
 			onSuccess: function(data){
 				result = data;
 			}
 		});
 		jsonRequest.send();
+		this._fetchedState = result;
 		return result;
 	},
 

+ 2 - 1
desktop/core/static/js/package.yml

@@ -12,8 +12,9 @@ sources: [
   Source/JFrameRenderers/CCS.JFrame.Alert.js,
   Source/JFrameRenderers/CCS.JFrame.Prompt.js,
   Source/JFrameRenderers/CCS.JFrame.PartialRefresh.js,
-  Source/CCS/CCS.ContextMenu.js,
   Source/CCS/CCS.js,
+  Source/CCS/CCS.ContextMenu.js,
+  Source/CCS/CCS.Desktop.BackgroundManager.js,
   Source/CCS/CCS.JBrowser.js,
   Source/CCS/CCS.Error.js,
   Source/CCS/CCS.JFrame.js,

+ 1 - 1
ext/thirdparty/js/widgets.hash

@@ -1 +1 @@
-1e024c40c8f775146e7d6bd275ca7d3fa4ce2625
+4fe12d04bd63e799ff65e79f98bdb57d5501a34b