├── .gitignore ├── images ├── icons.png ├── delete.png ├── appicon_16.png ├── appicon_32.png ├── appicon_57.png ├── appicon_64.png ├── appicon_72.png ├── colorspace.png ├── spectrum.png ├── appicon_114.png ├── appicon_128.png ├── appicon_256.png ├── appicon_512.png ├── slider_icons.png └── spectrum-handle.png ├── logoutprocess.php ├── cache.manifest ├── deleteimage.php ├── saveimage.php ├── LICENSE ├── README.md ├── getimages.php ├── loginprocess.php ├── registration.php ├── js ├── production │ ├── browsercheck.min.1293642061.js │ └── app-combined.min.1294012932.js ├── browsercheck.js ├── apphelp.slider.js ├── apphelp.colorpicker.js ├── draw.js ├── apphelp.js └── app.js ├── lib └── user.php ├── .htaccess ├── index.php └── css ├── style.min.1294013044.css └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | db.php 3 | -------------------------------------------------------------------------------- /images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/icons.png -------------------------------------------------------------------------------- /images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/delete.png -------------------------------------------------------------------------------- /images/appicon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/appicon_16.png -------------------------------------------------------------------------------- /images/appicon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/appicon_32.png -------------------------------------------------------------------------------- /images/appicon_57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/appicon_57.png -------------------------------------------------------------------------------- /images/appicon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/appicon_64.png -------------------------------------------------------------------------------- /images/appicon_72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/appicon_72.png -------------------------------------------------------------------------------- /images/colorspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/colorspace.png -------------------------------------------------------------------------------- /images/spectrum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/spectrum.png -------------------------------------------------------------------------------- /images/appicon_114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/appicon_114.png -------------------------------------------------------------------------------- /images/appicon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/appicon_128.png -------------------------------------------------------------------------------- /images/appicon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/appicon_256.png -------------------------------------------------------------------------------- /images/appicon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/appicon_512.png -------------------------------------------------------------------------------- /images/slider_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/slider_icons.png -------------------------------------------------------------------------------- /images/spectrum-handle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koggdal/drawpad/HEAD/images/spectrum-handle.png -------------------------------------------------------------------------------- /logoutprocess.php: -------------------------------------------------------------------------------- 1 | setLogout(); 13 | 14 | // Return JSON string with status 15 | echo '{ "status": "ok" }'; 16 | ?> -------------------------------------------------------------------------------- /cache.manifest: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # rev 4 3 | 4 | 5 | CACHE: 6 | css/style.min.1293642465.css 7 | images/colorspace.png 8 | images/delete.png 9 | images/icons.png 10 | images/slider_icons.png 11 | images/spectrum-handle.png 12 | images/spectrum.png 13 | images/appicon_57.png 14 | images/appicon_72.png 15 | images/appicon_114.png 16 | js/production/browsercheck.min.1293642061.js 17 | js/production/app-combined.min.1294012932.js 18 | 19 | # For testing purposes 20 | #css/style.css 21 | #js/browsercheck.js 22 | #js/apphelp.js 23 | #js/apphelp.slider.js 24 | #js/apphelp.colorpicker.js 25 | #js/draw.js 26 | #js/app.js 27 | 28 | 29 | 30 | NETWORK: 31 | login/ 32 | logout/ 33 | register/ 34 | cloud-save/ 35 | cloud-delete/ 36 | cloud-images/ -------------------------------------------------------------------------------- /deleteimage.php: -------------------------------------------------------------------------------- 1 | isLoggedIn( $userID ) ) { 16 | echo '{ "status": "permission-denied" }'; 17 | exit; 18 | } 19 | 20 | $db->query( sprintf( "DELETE FROM drawapp_images WHERE user_id = '%d' AND id = '%d'", $userID, $imageID ) ); 21 | $db->query( sprintf( "DELETE FROM drawapp_thumbs WHERE user_id = '%d' AND id = '%d'", $userID, $imageID ) ); 22 | 23 | echo '{ "status": "ok" }'; 24 | ?> -------------------------------------------------------------------------------- /saveimage.php: -------------------------------------------------------------------------------- 1 | real_escape_string( $_POST['fullsize'] ) ); 11 | $thumb = str_replace( " ", "+", $db->real_escape_string( $_POST['thumb'] ) ); 12 | 13 | // Instantiate new User class 14 | $u = new User( "drawapp" ); 15 | 16 | if( !$u->isLoggedIn( $userID ) ) { 17 | echo '{ "status": "permission-denied" }'; 18 | exit; 19 | } 20 | 21 | $db->query( sprintf( "INSERT INTO drawapp_images ( user_id, data_url ) VALUES ( '%d', '%s' )", $userID, $fullsize ) ); 22 | $db->query( sprintf( "INSERT INTO drawapp_thumbs ( user_id, data_url ) VALUES ( '%d', '%s' )", $userID, $thumb ) ); 23 | 24 | echo '{ "status": "ok" }'; 25 | ?> -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Johannes Koggdal 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DrawPad 2 | ==================================================== 3 | 4 | DrawPad is a web app for drawing anything you like. Since it is a web app it is not restricted to only the iPhone, even though it is made primarily for the iPhone. It can be used in all modern browsers that has support for HTML5 canvas. The current release is not fully supported on Android, because multitouch is not supported in that browser which makes it impossible to hide the toolbars. 5 | 6 | The app allows you to draw with different brush sizes and opacity, and also offers a complete color picker with swatches. You can undo 10 steps (this will increase in future releases) and redo to get it back again. 7 | 8 | You can also save the image to one of two destinations; local browser storage or cloud storage. The browser storage uses HTML5 Web Storage, which means the image is stored in the browser's cache until the cache is cleared (it survives restarts and everything though, unless the cache is cleared on close). When saved to cloud storage, it is saved on the app server and is accessible from any browser through a login. That means saving to the cloud requires you to register, but only email and password are required. -------------------------------------------------------------------------------- /getimages.php: -------------------------------------------------------------------------------- 1 | isLoggedIn( $userID ) ) { 15 | echo '{ "status": "permission-denied" }'; 16 | exit; 17 | } 18 | 19 | // Get images from DB 20 | if( $result = $db->query( sprintf( "SELECT i.id as id, i.data_url as image_url, t.data_url as thumb_url FROM drawapp_images as i, drawapp_thumbs as t WHERE i.user_id = '%d' AND t.user_id = i.user_id AND t.id = i.id ORDER BY i.id DESC", $userID ) ) ) { 21 | 22 | echo '{ "status": "ok", "images": ['; 23 | 24 | // Get images 25 | $i = 0; 26 | $num = $result->num_rows; 27 | while( $image = $result->fetch_object() ) { 28 | 29 | // Return JSON string with image data 30 | echo '{ "id": "'.$image->id.'", "fullsize": "'.$image->image_url.'", "thumbnail": "'.$image->thumb_url.'" }'.($i < $num-1 ? ', ' : ''); 31 | $i++; 32 | } 33 | 34 | echo '] }'; 35 | 36 | } else { 37 | echo '{ "status": "no-images" }'; 38 | exit; 39 | } 40 | ?> -------------------------------------------------------------------------------- /loginprocess.php: -------------------------------------------------------------------------------- 1 | real_escape_string( strtolower( $_POST['email'] ) ); 10 | $password = $db->real_escape_string( $_POST['password'] ); 11 | 12 | // Get user data from DB 13 | if( $result = $db->query( sprintf( "SELECT * FROM drawapp_users WHERE email = '%s'", $email ) ) ) { 14 | 15 | // Get data 16 | $user = $result->fetch_object(); 17 | 18 | if( $user === NULL ) { 19 | // Return JSON string with status 20 | echo '{ "status": "wrong-email" }'; 21 | exit; 22 | } 23 | 24 | // Instantiate new User class 25 | $u = new User( "drawapp" ); 26 | 27 | // Validate if the password was correct 28 | if( $u->validPassword( $password, $user->password ) ) { 29 | 30 | // Set session to remember that the user is logged in 31 | $u->setLogin( $user->id ); 32 | 33 | // Return JSON string with user data 34 | echo '{ "status": "ok", "user": { "id": "'.$user->id.'", "email": "'.$user->email.'" } }'; 35 | 36 | } else { 37 | 38 | // Return JSON string with status 39 | echo '{ "status": "wrong-password" }'; 40 | } 41 | } 42 | ?> -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | real_escape_string( strtolower( $_POST['email'] ) ); 10 | $password = $db->real_escape_string( $_POST['password'] ); 11 | 12 | // Get user data from DB 13 | if( $result = $db->query( sprintf( "SELECT * FROM drawapp_users WHERE email = '%s'", $email ) ) ) { 14 | 15 | // Get data 16 | $user = $result->fetch_object(); 17 | 18 | // Return error code if e-mail already exists 19 | if( $user !== NULL ) { 20 | // Return JSON string with status 21 | echo '{ "status": "email-exists" }'; 22 | exit; 23 | } 24 | 25 | // Instantiate new User class 26 | $u = new User( "drawapp" ); 27 | 28 | // Add user to the DB 29 | if( $db->query( sprintf( "INSERT INTO drawapp_users ( email, password ) VALUES ( '%s', '%s' )", $email, $u->generateHash( $password ) ) ) ) { 30 | 31 | // Get user ID 32 | $id = $db->insert_id; 33 | 34 | // Set session to remember that the user is logged in 35 | $u->setLogin( $id ); 36 | 37 | // Return JSON string with user data 38 | echo '{ "status": "ok", "user": { "id": "'.$id.'", "email": "'.$email.'" } }'; 39 | } 40 | } 41 | ?> -------------------------------------------------------------------------------- /js/production/browsercheck.min.1293642061.js: -------------------------------------------------------------------------------- 1 | /* 2 | * BrowserCheck 3 | * 4 | * by Johannes Koggdal 5 | * 6 | * Gets and stores information about the current browser 7 | * Also adds classes to the html tag for styling for specific browsers 8 | * 9 | * Borrowed and edited code from the jQuery source 10 | * Thanks to the jQuery Team 11 | */ 12 | (function(b,a,c){b.browserCheck={userAgent:navigator.userAgent,uaMatch:function(p){p=p.toLowerCase();var f=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,r=/ms(ie) ([\w.]+)/,n=/(firefox)\/([\w.]+)/,t=/(mozilla)(?:.*? rv:([\w.]+))?/,g=/(mac).*?([\d\.]+);/,w=/(windows);/,e=/; (windows) nt/,l=/(linux)/,m=/(iphone|ipad).*? os (|\d|\d_\d|\d_\d_\d) /,y=/(android) (\d\.\d)/;var j=f.exec(p)||s.exec(p)||r.exec(p)||p.indexOf("compatible")<0&&n.exec(p)||p.indexOf("compatible")<0&&t.exec(p)||[],v=y.exec(p)||m.exec(p)||g.exec(p)||w.exec(p)||e.exec(p)||l.exec(p)||[],h=j[2].split("."),x=v[2]?v[2].split("."):[],u=[j[1],v[1]],d="",q="";if(x.length==1){x=v[2].split("_")}var k=v[1];for(var o=0;o0){browserCheck.os[d.os]=true}}},addClass:function(e){var d=a.documentElement.className;a.documentElement.className+=(d=="")?e:" "+e},addClasses:function(){var f=browserCheck.browser.classNames,d=f.length;for(var e=d;e--;){browserCheck.addClass(f[e])}}};browserCheck.add();browserCheck.addClasses()})(window,document); -------------------------------------------------------------------------------- /lib/user.php: -------------------------------------------------------------------------------- 1 | sitename = $sitename."_"; 8 | } 9 | 10 | public function isLoggedIn( $id ){ 11 | if($_SESSION[$this->sitename.'loggedin'] == $_SERVER['REMOTE_ADDR'] && $_SESSION[$this->sitename.'user_id'] == $id) 12 | return true; 13 | else 14 | return false; 15 | } 16 | 17 | public function setLogin( $id ){ 18 | $_SESSION[$this->sitename.'loggedin'] = $_SERVER['REMOTE_ADDR']; 19 | $_SESSION[$this->sitename.'user_id'] = $id; 20 | } 21 | 22 | public function setLogout(){ 23 | unset($_SESSION[$this->sitename.'loggedin']); 24 | unset($_SESSION[$this->sitename.'user_id']); 25 | } 26 | 27 | public function checkPasswords($old,$new,$new_repeated){ 28 | $errors = array(); 29 | if(empty($old)) 30 | $errors['old'] = 'Fyll i ett lösenord.'; 31 | else if(!$this->validPassword($old)) 32 | $errors['old'] = 'Lösenordet är felaktigt.'; 33 | 34 | if(empty($new)) 35 | $errors['new'] = 'Fyll i ett lösenord.'; 36 | if(empty($new_repeated)) 37 | $errors['new_repeat'] = 'Fyll i ett lösenord.'; 38 | else if($new !== $new_repeated) 39 | $errors['new_repeat'] = 'Stämmer inte överens.'; 40 | 41 | if(count($errors) == 0) 42 | return true; 43 | else 44 | return $errors; 45 | } 46 | 47 | public function validPassword($password,$correct_pass){ 48 | $salt = substr($correct_pass,0,8); 49 | $this_hash = $this->generateHash($password,$salt); 50 | if($correct_pass === $this_hash) 51 | return true; 52 | else 53 | return false; 54 | } 55 | 56 | public function generateCode($num,$usedChars="a-zA-Z0-9") 57 | { 58 | $array = array(); 59 | if(strpos($usedChars,"a-z") !== false) 60 | { 61 | array_push($array,"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"); 62 | } 63 | if(strpos($usedChars,"A-Z") !== false) 64 | { 65 | array_push($array,"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"); 66 | } 67 | if(strpos($usedChars,"0-9") !== false) 68 | { 69 | array_push($array,"0","1","2","3","4","5","6","7","8","9"); 70 | } 71 | if(strpos($usedChars,"[") !== false) 72 | { 73 | preg_match("/\[(.*?)\]/",$usedChars,$matches); 74 | $special = $matches[1]; 75 | $spec = chunk_split($special,1,"####"); 76 | $spec = substr($spec,0,-4); 77 | $special = explode("####",$spec); 78 | foreach($special as $sp) 79 | { 80 | array_push($array,$sp); 81 | } 82 | } 83 | 84 | $array_keys = array_rand($array,$num); 85 | shuffle($array_keys); 86 | for($i=0; $i < $num; $i++){ 87 | $code .= $array[$array_keys[$i]]; 88 | } 89 | return $code; 90 | } 91 | 92 | public function generateHash($password,$salt=false) 93 | { 94 | if($salt === false) 95 | { 96 | $salt = substr(hash('sha512',self::generateCode(8,"a-z0-9")),0,8); 97 | } 98 | else 99 | { 100 | $salt = substr($salt,0,8); 101 | } 102 | 103 | return $salt.hash('sha512',$password.$salt); 104 | } 105 | } 106 | ?> -------------------------------------------------------------------------------- /js/browsercheck.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * BrowserCheck 3 | * 4 | * by Johannes Koggdal 5 | * 6 | * Gets and stores information about the current browser 7 | * Also adds classes to the html tag for styling for specific browsers 8 | * 9 | * Borrowed and edited code from the jQuery source 10 | * Thanks to the jQuery Team 11 | */ 12 | (function(window,document,undefined){ 13 | 14 | window.browserCheck = { 15 | 16 | userAgent: navigator.userAgent, 17 | 18 | uaMatch: function( ua ) { 19 | ua = ua.toLowerCase(); 20 | 21 | // Useragent RegExp 22 | var rwebkit = /(webkit)[ \/]([\w.]+)/, 23 | ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, 24 | rmsie = /ms(ie) ([\w.]+)/, 25 | rfirefox = /(firefox)\/([\w.]+)/, 26 | rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, 27 | mac = /(mac).*?([\d\.]+);/, 28 | windows = /(windows);/, 29 | windows_compatible = /; (windows) nt/, 30 | linux = /(linux)/, 31 | ios = /(iphone|ipad).*? os (|\d|\d_\d|\d_\d_\d) /, 32 | android = /(android) (\d\.\d)/; 33 | 34 | 35 | var match = rwebkit.exec( ua ) || 36 | ropera.exec( ua ) || 37 | rmsie.exec( ua ) || 38 | ua.indexOf("compatible") < 0 && rfirefox.exec( ua ) || 39 | ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || 40 | [], 41 | 42 | match_os = android.exec( ua ) || 43 | ios.exec( ua ) || 44 | mac.exec( ua ) || 45 | windows.exec( ua ) || 46 | windows_compatible.exec( ua ) || 47 | linux.exec( ua ) || 48 | [], 49 | 50 | version_parts = match[2].split("."), 51 | version_parts_os = match_os[2] ? match_os[2].split(".") : [], 52 | classes = [match[1],match_os[1]], 53 | lastClass = '', 54 | lastClassOS = ''; 55 | 56 | if( version_parts_os.length == 1 ) 57 | version_parts_os = match_os[2].split("_"); 58 | 59 | var output = match_os[1]; 60 | 61 | for(var i = 0; i < version_parts.length; i++){ 62 | classes.push(match[1]+lastClass+version_parts[i]); 63 | lastClass += version_parts[i]; 64 | } 65 | 66 | for(var i = 0; i < version_parts_os.length; i++){ 67 | output += "\n"+match_os[1]+lastClassOS+version_parts_os[i]; 68 | classes.push(match_os[1]+lastClassOS+version_parts_os[i]); 69 | lastClassOS += version_parts_os[i]; 70 | } 71 | 72 | return { browser: match[1] || "", version: match[2] || "0", os: match_os[1] || "", classNames: classes }; 73 | }, 74 | 75 | browser: {}, 76 | os: {}, 77 | 78 | add: function(){ 79 | var browserMatch = browserCheck.uaMatch( browserCheck.userAgent ); 80 | if ( browserMatch.browser ) { 81 | browserCheck.browser[ browserMatch.browser ] = true; 82 | browserCheck.browser.version = browserMatch.version; 83 | browserCheck.browser.name = browserMatch.browser; 84 | browserCheck.browser.classNames = browserMatch.classNames; 85 | browserCheck.os.name = browserMatch.os; 86 | if(browserMatch.os.length > 0) 87 | browserCheck.os[ browserMatch.os ] = true; 88 | } 89 | }, 90 | 91 | addClass: function(className){ 92 | var classes = document.documentElement.className; 93 | document.documentElement.className += (classes == "") ? className : " "+className; 94 | }, 95 | 96 | addClasses: function(){ 97 | var classes = browserCheck.browser.classNames, 98 | len = classes.length; 99 | for(var i = len; i--;) 100 | browserCheck.addClass(classes[i]); 101 | } 102 | }; 103 | 104 | browserCheck.add(); 105 | browserCheck.addClasses(); 106 | 107 | })(window,document); -------------------------------------------------------------------------------- /js/apphelp.slider.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Slider v1.0 3 | * appHelp Plugin 4 | * 5 | * Copyright 2010, Johannes Koggdal 6 | * http://koggdal.com/ 7 | */ 8 | (function( window, document, $, undefined ) { 9 | 10 | // Add the plugin to appHelp 11 | $.fn.slider = function( options ) { 12 | 13 | // Set default settings 14 | var settings = { 15 | min: 1, 16 | max: 10, 17 | start: 1, 18 | begin: function( newValue ){}, 19 | change: function( newValue ){}, 20 | end: function( newValue ){} 21 | }, 22 | wrapper = this.get(0), 23 | bg = wrapper.firstElementChild, 24 | handle = bg.firstElementChild, 25 | bgWidth = parseInt(window.getComputedStyle( bg, null ).getPropertyValue( 'width' )), 26 | moving = false, 27 | mousemove, 28 | mouseup, 29 | currentValue, 30 | setNewValue; 31 | 32 | // Update settings with custom settings 33 | $.extend( settings, options ); 34 | 35 | // Function to set a new value for the slider. 36 | // Sets the new handle position, updates the value and triggers the change callback 37 | setNewValue = function( e ) { 38 | 39 | // Triggered by event 40 | if( e.type === undefined ) { 41 | 42 | currentValue = ~~e; 43 | handle.style.left = Math.floor( currentValue / ( settings.max - settings.min ) * bgWidth )+'px'; 44 | } 45 | 46 | // Triggered by code 47 | else { 48 | 49 | // Get left position 50 | var left = $.getPos( e, 0, bg ).x; 51 | 52 | 53 | // Set new position for the handle and set currentValue 54 | if( left <= 0 ) { 55 | handle.style.left = '0px'; 56 | currentValue = settings.min; 57 | } else if( left > bgWidth ) { 58 | handle.style.left = bgWidth+'px'; 59 | currentValue = settings.max; 60 | } else { 61 | handle.style.left = left+'px'; 62 | currentValue = Math.ceil( left / bgWidth * ( settings.max - settings.min ) ); 63 | if( currentValue <= settings.min ) 64 | currentValue = settings.min; 65 | else if ( currentValue > settings.max ) 66 | currentValue = settings.max; 67 | } 68 | } 69 | 70 | 71 | // Trigger the change method with the new value 72 | settings.change( currentValue ); 73 | } 74 | 75 | // Set start value 76 | setNewValue( settings.start ); 77 | 78 | // Function that is called continuosly during the dragging 79 | mousemove = function( e ) { 80 | 81 | 82 | // Prevent touch devices from trigger the emulated mouse event 83 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 84 | return false; 85 | 86 | if( moving ) { 87 | setNewValue( e ); 88 | } 89 | }; 90 | 91 | // Function that is called on mouseup 92 | mouseup = function( e ) { 93 | 94 | // Prevent touch devices from trigger the emulated mouse event 95 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 96 | return false; 97 | 98 | if( moving ) { 99 | moving = false; 100 | 101 | // Trigger end callback 102 | settings.end(); 103 | 104 | // Unset the event handlers, to abort the dragging 105 | $( document ) 106 | .unbind( 'touchmove mousemove', mousemove ) 107 | .unbind( 'touchend mouseup', mouseup ); 108 | } 109 | }; 110 | 111 | // Set event handler for mousedown on the slider handle 112 | // This will start the dragging 113 | $( wrapper ) 114 | .bind( 'touchstart mousedown', function( e ) { 115 | 116 | // Prevent touch devices from trigger the emulated mouse event 117 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 118 | return false; 119 | 120 | // Prevent original behaviour where the div is sometimes dragged away like an image 121 | e.preventDefault(); 122 | 123 | // Set identifier 124 | id = e.touches ? e.touches[ e.touches.length-1 ].identifier : 'mouse'; 125 | 126 | // Set moving state 127 | moving = true; 128 | 129 | // Trigger begin callback 130 | settings.begin(); 131 | 132 | // Trigger new value 133 | setNewValue( e ); 134 | 135 | 136 | // Set event handlers on the document element so you dont have to 137 | // keep the mouse pointer inside the slider handle 138 | $( document ) 139 | .bind( 'touchmove mousemove', mousemove ) 140 | .bind( 'touchend mouseup', mouseup ); 141 | }); 142 | 143 | // Return a function to enable updating the slider from outside 144 | return function( position ) { 145 | setNewValue( position ); 146 | }; 147 | }; 148 | 149 | })( window, window.document, appHelp ); -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | 3 | RewriteRule ^code(|/)$ code.php 4 | 5 | RewriteRule ^login/$ loginprocess.php 6 | RewriteRule ^logout/$ logoutprocess.php 7 | RewriteRule ^register/$ registration.php 8 | RewriteRule ^cloud-save/$ saveimage.php 9 | RewriteRule ^cloud-delete/$ deleteimage.php 10 | 11 | RewriteCond %{QUERY_STRING} ^user_id=(\d+)$ 12 | RewriteRule ^cloud-images/$ getimages.php?user_id=%1 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ################## 21 | 22 | 23 | 24 | Options -MultiViews 25 | Options -Indexes 26 | 27 | # Force the latest IE version, in various cases when it may fall back to IE7 mode 28 | # github.com/rails/rails/commit/123eb25#commitcomment-118920 29 | # Use ChromeFrame if it's installed for a better experience for the poor IE folk 30 | 31 | 32 | BrowserMatch MSIE ie 33 | Header set X-UA-Compatible "IE=Edge,chrome=1" env=ie 34 | 35 | 36 | 37 | 38 | # Because X-UA-Compatible isn't sent to non-IE (to save header bytes), 39 | # We need to inform proxies that content changes based on UA 40 | Header append Vary User-Agent 41 | # Cache control is set only if mod_headers is enabled, so that's unncessary to declare 42 | 43 | 44 | 45 | # video 46 | AddType video/ogg ogg 47 | AddType video/ogg ogv 48 | AddType video/mp4 mp4 49 | AddType video/webm webm 50 | 51 | # Proper svg serving. Required for svg webfonts on iPad 52 | # twitter.com/FontSquirrel/status/14855840545 53 | AddType image/svg+xml svg svgz 54 | AddEncoding gzip svgz 55 | 56 | # webfonts 57 | AddType application/vnd.ms-fontobject eot 58 | AddType font/truetype ttf 59 | AddType font/opentype otf 60 | AddType font/woff woff 61 | 62 | # assorted types 63 | AddType image/vnd.microsoft.icon ico 64 | AddType image/webp webp 65 | AddType text/cache-manifest manifest 66 | AddType text/x-component htc 67 | AddType application/x-chrome-extension crx 68 | 69 | 70 | # gzip compression. 71 | 72 | 73 | # html, txt, css, js, json, xml, htc: 74 | AddOutputFilterByType DEFLATE text/html text/plain text/css application/json 75 | AddOutputFilterByType DEFLATE text/javascript application/javascript application/x-javascript 76 | AddOutputFilterByType DEFLATE text/xml application/xml text/x-component 77 | 78 | # webfonts and svg: 79 | 80 | SetOutputFilter DEFLATE 81 | 82 | 83 | 84 | # disable gzip compression for video files 85 | SetEnvIfNoCase Request_URI \.(og[gv]|mp4|m4v|webm)$ no-gzip dont-vary 86 | 87 | 88 | 89 | 90 | # these are pretty far-future expires headers 91 | # they assume you control versioning with cachebusting query params like 92 | # 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 |
35 | 36 |

DrawPad

37 | 38 |
39 | 40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 51 |
52 |

All the swatches are filled. Choose one to replace.

53 |
54 |
55 | 56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 64 |
65 |
66 |
67 |
68 |
69 | 70 |
71 |
72 |
73 | 83 |
84 |
+
New Image
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |

Choose color

103 |

Easily switch between colors with swatches.
Add swatches from the color picker.

104 |

Brush size

105 |

Brush opacity

106 |

Right click on canvas
to hide the toolbars
(click to hide tooltips)

107 |

Hiding the toolbars is
not available on Android
(tap to hide tooltips)

108 |

Tap with two fingers
to hide the toolbars
(tap to hide tooltips)

109 |
110 |
111 |
112 |
113 | 114 |
115 |
116 | 117 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /js/draw.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Draw v1.0 3 | * http://draw.koggdal.com/ 4 | * 5 | * Copyright 2010, Johannes Koggdal 6 | * http://koggdal.com/ 7 | */ 8 | (function( window, document, $, undefined ) { 9 | 10 | // Define the app class 11 | var Draw = function(){ 12 | 13 | // Set default settings 14 | this.settings = { 15 | canvas: document.createElement( 'canvas' ), 16 | change: function(){}, 17 | width: window.innerWidth, 18 | height: window.innerHeight, 19 | hideToolbars: function(){}, 20 | showToolbars: function(){}, 21 | toolbars_visible: false, 22 | historyLimit: 10, 23 | 24 | background: "#ffffff", 25 | density: 0.66, 26 | lineWidth: 2, 27 | strokeStyle: "#000000", 28 | fillStyle: "rgba(120,0,0,0.1)", 29 | opacity: 1, 30 | alpha: 1 31 | }; 32 | 33 | 34 | // Add a history storage to enable undo/redo 35 | this.history = []; 36 | this.historyPos = 0; 37 | 38 | // Vars needed for the drawing process 39 | this.pointers = {}; 40 | this.activePointers = 0; 41 | this.twofinger_tap = false; 42 | }; 43 | 44 | // The member methods 45 | Draw.fn = Draw.prototype = { 46 | 47 | // Function to set up the canvas 48 | setup: function( options ){ 49 | var _this = this; 50 | 51 | // Update the settings object with new values 52 | $.extend( this.settings, options ); 53 | 54 | // Get the canvas used for drawing 55 | this.canvas = this.settings.canvas; 56 | 57 | // Get the drawing context used for drawing 58 | this.context = this.canvas.getContext( '2d' ); 59 | 60 | // Set the image canvas 61 | this.canvasImage = document.createElement( 'canvas' ); 62 | this.contextImage = this.canvasImage.getContext( '2d' ); 63 | this.canvas.parentNode.insertBefore( this.canvasImage, this.canvas.nextElementSibling ); 64 | 65 | // Set the canvas size to the size set in settings 66 | // Using a timer because iOS Safari doesnt load innerWidth/innerHeight (default size) correctly directly on load 67 | setTimeout( function() { 68 | _this.initDraw(); 69 | }, 1 ); 70 | 71 | // Cancel drawing process if mouse button is released outside of the canvas 72 | $( document ).bind( 'touchend mouseup' , function( e ){ _this.drawEnd( e ); } ); 73 | 74 | // Bind touch and mouse events to the canvas 75 | $( this.canvas ) 76 | .bind( 'touchstart mousedown', function( e ){ _this.drawStart( e ); } ) 77 | .bind( 'touchmove mousemove', function( e ){ _this.drawMove( e ); } ) 78 | .bind( 'touchend mouseup', function( e ){ _this.drawEnd( e ); } ); 79 | 80 | // Clean up memory from all the data in the history object when user navigates away from the app 81 | $( window ).bind( 'unload', function() { 82 | _this.history = null; 83 | }); 84 | }, 85 | 86 | initDraw: function(){ 87 | this.canvas.width = this.settings.width; 88 | this.canvas.height = this.settings.height; 89 | this.canvasImage.width = this.settings.width; 90 | this.canvasImage.height = this.settings.height; 91 | 92 | this.clear(); 93 | this.reset(); 94 | }, 95 | 96 | // Function that starts the drawing process 97 | drawStart: function( e ) { 98 | 99 | // Prevent touch devices from trigger the emulated mouse event 100 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 101 | return false; 102 | 103 | 104 | // Cancel event if the mouse button is not the primary 105 | if( e.button && e.button !== 0 ){ 106 | e.preventDefault(); 107 | 108 | this.twofinger_tap = true; 109 | 110 | return false; 111 | } 112 | 113 | // Increase the number of active pointers (mouse down / touch press) 114 | this.activePointers++; 115 | 116 | // Loop through all the touch events (or one iteration for mouse down) 117 | for(var x = 0; x < (e.touches ? e.touches.length : 1); x++){ 118 | // The touch identifier is used to distinguish the different touches 119 | var id = e.touches ? e.touches[x].identifier : 'mouse'; 120 | 121 | // Add the pointer 122 | this.pointers[id] = { 123 | enabled: true, 124 | last: { 125 | x: $.getPos( e, x, this.canvas ).x, 126 | y: $.getPos( e, x, this.canvas ).y 127 | } 128 | }; 129 | } 130 | 131 | if( e.touches && e.touches.length == 2 ) { 132 | 133 | this.twofinger_tap = true; 134 | 135 | } else { 136 | 137 | this.canvas.style.opacity = this.settings.opacity; 138 | 139 | // Loop through all the touch events (or one iteration for mouse down) 140 | for(var x = 0; x < (e.touches ? e.touches.length : 1); x++){ 141 | this.context.beginPath(); 142 | this.context.fillStyle = this.settings.fillStyle; 143 | this.context.arc(this.pointers[id].last.x,this.pointers[id].last.y,this.settings.lineWidth/2,0,Math.PI*2,false); 144 | this.context.fill(); 145 | this.context.closePath(); 146 | } 147 | } 148 | }, 149 | 150 | // Function that draws the content when the pointer is moved 151 | drawMove: function( e ) { 152 | 153 | // Prevent touch devices from trigger the emulated mouse event 154 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 155 | return false; 156 | 157 | // Cancel event if the mouse button is not the primary, or if there are no active pointers 158 | if( e.button && e.button !== 0 || this.activePointers == 0 ) 159 | return false; 160 | 161 | if( this.twofinger_tap ) 162 | this.twofinger_tap = false; 163 | 164 | // Loop through the pointers 165 | var num_pointers = e.touches ? e.touches.length : 1; 166 | for(var i = 0; i < num_pointers; i++){ 167 | 168 | // Get current pointer 169 | var id = e.touches ? e.touches[i].identifier : 'mouse', 170 | pointer = this.pointers[id]; 171 | 172 | // Only try to draw if the current pointer is enabled 173 | if(pointer.enabled){ 174 | 175 | // Calculate positions 176 | var pos = $.getPos( e, i, this.canvas ), 177 | last = pointer.last, 178 | dist = { 179 | x: pos.x - last.x, 180 | y: pos.y - last.y, 181 | }, 182 | x = last.x, 183 | y = last.y, 184 | steps, step; 185 | dist.d = Math.sqrt(dist.x*dist.x + dist.y*dist.y); 186 | steps = dist.d*this.settings.density; 187 | step = { 188 | x: dist.x * ( 1 / steps ), 189 | y: dist.y * ( 1 / steps ) 190 | }; 191 | 192 | // Draw several times to fill in gaps between event triggerings 193 | for(var n = 0; n < steps; n++){ 194 | this.context.beginPath(); 195 | this.context.fillStyle = this.settings.fillStyle; 196 | this.context.arc(x,y,this.settings.lineWidth/2,0,Math.PI*2,false); 197 | this.context.fill(); 198 | this.context.closePath(); 199 | 200 | // Increment the x and y position for the next iteration 201 | x += step.x; 202 | y += step.y; 203 | } 204 | 205 | // Set the last drawn position of the current pointer 206 | this.pointers[id].last.x = pos.x; 207 | this.pointers[id].last.y = pos.y; 208 | } 209 | } 210 | }, 211 | 212 | // Function that ends the drawing process 213 | drawEnd: function( e ) { 214 | 215 | var salt = Math.random(); 216 | // Prevent touch devices from trigger the emulated mouse event 217 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 218 | return false; 219 | 220 | // Toggle the toolbars when canvas is tapped with two fingers / right clicked 221 | if( this.twofinger_tap ){ 222 | if( this.settings.toolbars_visible ){ 223 | this.settings.hideToolbars(); 224 | this.settings.toolbars_visible = false; 225 | }else{ 226 | this.settings.showToolbars(); 227 | this.settings.toolbars_visible = true; 228 | } 229 | this.twofinger_tap = false; 230 | this.activePointers = 0; 231 | return false; 232 | } 233 | 234 | // Cancel event if the mouse button is not the primary, or if there are no active pointers 235 | if( e.button && e.button !== 0 || this.activePointers == 0 ) 236 | return false; 237 | 238 | // Decrease the number of active pointers 239 | this.activePointers--; 240 | 241 | var ids = []; 242 | 243 | // Add all active identifiers to the ids array 244 | if( e.touches ) { 245 | for(var t = 0, len = e.touches.length; t < len; t++) 246 | ids.push(e.touches[t].identifier); 247 | } else { 248 | ids.push('mouse'); 249 | } 250 | 251 | // Compare the IDs found in the pointers object against the ids array 252 | // If there is no match, the pointer is set to disabled 253 | for(var id in this.pointers){ 254 | if(ids.indexOf(id) == -1) 255 | this.pointers[id].enabled = false; 256 | } 257 | 258 | // Move the drawn image to the primary canvas 259 | this.contextImage.globalAlpha = this.settings.opacity; 260 | this.contextImage.drawImage( this.canvas, 0, 0 ); 261 | this.contextImage.globalAlpha = '1'; 262 | 263 | // Clear the secondary canvas 264 | this.context.clearRect( 0, 0, this.canvas.width, this.canvas.height ); 265 | this.canvas.style.opacity = '0'; 266 | 267 | // Clear history states after the current position 268 | if( this.historyPos < this.history.length-1 ) { 269 | for( var i = this.historyPos+1, l = this.history.length; i < l; i++ ) 270 | this.history.pop(); 271 | } 272 | 273 | // Clear earlier history states if more than the limit 274 | if( this.history.length > this.settings.historyLimit ) { 275 | this.history.shift(); 276 | } 277 | 278 | // Add to history 279 | this.history.push( this.contextImage.getImageData( 0, 0, this.canvasImage.width, this.canvasImage.height ) ); 280 | this.historyPos = this.history.length-1; 281 | 282 | // Trigger change callback 283 | this.settings.change(); 284 | }, 285 | 286 | // Function to go back in history state 287 | undo: function() { 288 | // Clear the canvas 289 | this.contextImage.clearRect( 0, 0, this.canvasImage.width, this.canvasImage.height ); 290 | // Add the image data from the previous state 291 | this.contextImage.putImageData( this.history[ --this.historyPos ], 0, 0 ); 292 | }, 293 | 294 | // Function to go forward in the history states 295 | redo: function() { 296 | // Clear the canvas 297 | this.contextImage.clearRect( 0, 0, this.canvasImage.width, this.canvasImage.height ); 298 | // Add the image data from the previous state 299 | this.contextImage.putImageData( this.history[ ++this.historyPos ], 0, 0 ); 300 | }, 301 | 302 | // Function to reset the drawing process 303 | reset: function() { 304 | this.history = []; 305 | this.historyPos = 0; 306 | 307 | // Save first state to history 308 | this.history.push( this.contextImage.getImageData( 0, 0, this.canvasImage.width, this.canvasImage.height ) ); 309 | 310 | // Update settings 311 | this.settings.change(); 312 | }, 313 | 314 | // Function to clear the entire canvas 315 | clear: function(){ 316 | 317 | // Clear canvas 318 | this.contextImage.fillStyle = this.settings.background; 319 | this.contextImage.fillRect( 0, 0, this.settings.width, this.settings.height ); 320 | 321 | // Save state to history 322 | this.history.push( this.contextImage.getImageData( 0, 0, this.canvasImage.width, this.canvasImage.height ) ); 323 | 324 | // Update settings 325 | this.settings.change(); 326 | }, 327 | 328 | // Function to load an image into the canvas 329 | loadImage: function( url, x, y, reset, callback ) { 330 | reset = reset || false; 331 | var _this = this, 332 | img = document.createElement( 'img' ); 333 | img.src = url; 334 | img.onload = function(){ 335 | 336 | // Draw the image content onto the canvas 337 | _this.contextImage.drawImage( img, x, y ); 338 | 339 | if( reset ) { 340 | // Reset the history 341 | _this.reset(); 342 | } else { 343 | 344 | // Save state to history 345 | _this.history.push( _this.contextImage.getImageData( 0, 0, _this.canvasImage.width, _this.canvasImage.height ) ); 346 | 347 | // Update settings 348 | _this.settings.change(); 349 | } 350 | 351 | // Trigger callback function 352 | callback(); 353 | }; 354 | }, 355 | 356 | // Function to get the color values 357 | getColorValues: function( color, format ) { 358 | 359 | // Get color from RGBA value 360 | if( format == "rgba" ) { 361 | var fill = color || this.settings.fillStyle, 362 | color_matches = /rgba\((\d+),(\d+),(\d+),(.*?)\)/.exec( fill ), 363 | 364 | colors = { 365 | red: parseInt(color_matches[1]), 366 | green: parseInt(color_matches[2]), 367 | blue: parseInt(color_matches[3]), 368 | alpha: parseFloat(color_matches[4]) 369 | }; 370 | } 371 | 372 | // Get color from RGB value 373 | if( format == "rgb" ) { 374 | var color_matches = /rgb\((\d+),(\d+),(\d+)\)/.exec( color ), 375 | 376 | colors = { 377 | red: parseInt(color_matches[1]), 378 | green: parseInt(color_matches[2]), 379 | blue: parseInt(color_matches[3]), 380 | alpha: 1 381 | }; 382 | } 383 | 384 | // Get color from hex value 385 | if( format == "hex" ) { 386 | var color_matches = /#([A-Za-z0-9]{2})([A-Za-z0-9]{2})([A-Za-z0-9]{2})/.exec( color ), 387 | 388 | colors = { 389 | red: color_matches[1], 390 | green: color_matches[2], 391 | blue: color_matches[3], 392 | alpha: 1 393 | }; 394 | } 395 | 396 | return colors; 397 | }, 398 | 399 | // Convert between different color formats 400 | convertColorFormat: function( color, currentFormat, newFormat ) { 401 | 402 | // Convert rgba to hex 403 | if( currentFormat == "rgba" && newFormat == "hex" ) { 404 | 405 | // Function to convert one decimal value to hex (255 will translate to ff) 406 | var dec2hex = function( decValue ){ 407 | var conversion = { 0:0, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9, 10:'a', 11:'b', 12:'c', 13:'d', 14:'e', 15:'f' }, 408 | hex_one = conversion[ Math.floor( decValue / 16 ) ], 409 | hex_two = conversion[ decValue % 16 ], 410 | hex = hex_one+''+hex_two; 411 | 412 | return hex; 413 | }, 414 | 415 | // Get the different color channels from the passed color value 416 | colors = this.getColorValues( color, 'rgba' ), 417 | hex = { 418 | red: dec2hex( colors.red ), 419 | green: dec2hex( colors.green ), 420 | blue: dec2hex( colors.blue ) 421 | }; 422 | 423 | return "#"+hex.red+hex.green+hex.blue; 424 | } 425 | 426 | // Convert hex to rgba 427 | if( currentFormat == "hex" && newFormat == "rgba" ) { 428 | 429 | // Function to convert one hex value to dec (ff will translate to 255) 430 | var hex2dec = function( hexValue ){ 431 | var conversion = { 0:0, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9, 'a':10, 'b':11, 'c':12, 'd':13, 'e':14, 'f':15 }, 432 | dec_one = conversion[ hexValue[0] ] * 16, 433 | dec_two = conversion[ hexValue[1] ], 434 | decVal = dec_one+dec_two; 435 | 436 | return decVal; 437 | }, 438 | 439 | // Get the different color channels from the passed color value 440 | colors = this.getColorValues( color, 'hex' ), 441 | rgba = { 442 | red: hex2dec( colors.red ), 443 | green: hex2dec( colors.green ), 444 | blue: hex2dec( colors.blue ), 445 | alpha: colors.alpha 446 | }; 447 | 448 | return "rgba("+rgba.red+","+rgba.green+","+rgba.blue+","+rgba.alpha+")"; 449 | } 450 | 451 | // Convert rgb to rgba 452 | if( currentFormat == "rgb" && newFormat == "rgba" ) { 453 | 454 | // Get the different color channels from the passed color value 455 | var colors = this.getColorValues( color, 'rgb' ); 456 | 457 | return "rgba("+colors.red+","+colors.green+","+colors.blue+","+colors.alpha+")"; 458 | } 459 | }, 460 | 461 | // Function to set the brush opacity 462 | setBrushOpacity: function( newOpacity ) { 463 | 464 | this.settings.opacity = newOpacity/100; 465 | }, 466 | 467 | // Function to set the brush color. Accepts hex, rgb and rgba values 468 | setBrushColor: function( newColor ) { 469 | if( newColor.indexOf( '#' ) > -1 ) 470 | newColor = this.convertColorFormat( newColor, 'hex', 'rgba' ); 471 | else if( newColor.indexOf( 'rgb(' ) > -1 ) 472 | newColor = this.convertColorFormat( newColor, 'rgb', 'rgba' ); 473 | 474 | this.settings.fillStyle = newColor; 475 | }, 476 | 477 | // Function to set a new brush size 478 | setBrushSize: function( newSize ) { 479 | this.settings.lineWidth = newSize; 480 | if( newSize == 1 ) 481 | this.settings.density = 1; 482 | else 483 | this.settings.density = 0.66; 484 | } 485 | 486 | }; 487 | 488 | 489 | // Add the app class to the global namespace in window 490 | window.Draw = Draw; 491 | 492 | })( window, window.document, appHelp ); -------------------------------------------------------------------------------- /css/style.min.1294013044.css: -------------------------------------------------------------------------------- 1 | html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent;}body{line-height:1;}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block;}nav ul{list-style:none;}blockquote,q{quotes:none;}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent;}ins{background-color:#ff9;color:#000;text-decoration:none;}mark{background-color:#ff9;color:#000;font-style:italic;font-weight:bold;}del{text-decoration:line-through;}abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help;}table{border-collapse:collapse;border-spacing:0;}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0;}input,select{vertical-align:middle;}::-moz-focus-inner{border:0;padding:0;}*:not(form *){-webkit-user-select:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:rgba(0,0,0,0);}html,body{height:100%;}body{overflow:hidden;font:12px/1.2 'Helvetica Neue',Arial,Helvetica,sans-serif;color:#ddd;}h1{font-size:20px;}h2{font-size:18px;}h3{font-size:16px;}h4{font-size:15px;}h5{font-size:14px;}h6{font-size:12px;}label{font-size:14px;font-weight:bold;}label b{color:#d55;font-style:italic;}input{font-size:14px;text-indent:10px;color:#fff;margin:5px 0 20px;width:100%;padding:0;border:0;height:40px;background:#444;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:inset 0 1px 2px #000;-moz-box-shadow:inset 0 1px 2px #000;box-shadow:inset 0 1px 2px #000;}input[type=email]{text-transform:lowercase;}.toolbar{background:#000;background:-moz-linear-gradient(top,#444 0,#000 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#444),color-stop(100%,#000));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#444444',endColorstr='#000000',GradientType=0);min-height:44px;color:#fff;text-align:center;}.toolbar_wrapper{width:320px;margin:0 auto;}.toolbar button.active{background:#333;}.slider{width:100%;height:44px;}.slider .slider-bg{width:100%;height:10px;margin-top:17px;background:#666;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}.slider .slider-bg .slider-handle{width:30px;height:30px;background:#999;position:relative;top:-10px;margin-left:-15px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 0 5px #333;-moz-box-shadow:0 0 5px #333;box-shadow:0 0 5px #333;cursor:pointer;}.colorbox{border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:inset 1px 1px 3px rgba(0,0,0,0.6);-moz-box-shadow:inset 1px 1px 3px rgba(0,0,0,0.6);box-shadow:inset 1px 1px 3px rgba(0,0,0,0.6);pointer-events:none;}.colorbox.filled{cursor:pointer;pointer-events:auto;}.colorbox.highlighted{border:1px solid #ff0;}.colorpicker{overflow:hidden;}.colorpicker .message{position:absolute;top:0;left:10px;width:280px;height:14px;z-index:20;text-align:center;background:#000;color:#ff0;padding:5px 10px 10px 10px;-webkit-border-radius:0 0 10px 10px;-moz-border-radius:0 0 10px 10px;border-radius:0 0 10px 10px;display:none;}.colorpicker .colorspace{width:300px;height:300px;margin:5px auto 0 auto;position:relative;overflow:hidden;}.colorpicker .colorspace .marker{width:14px;height:14px;position:absolute;top:0;left:300px;margin:-10px 0 0 -10px;border:1px solid #fff;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;}.colorpicker .colorspace .marker:before{content:'';position:absolute;top:-2px;left:-2px;width:16px;height:16px;border:1px solid #000;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;}.colorpicker .spectrum{width:300px;height:37px;margin:10px auto;background:url('../images/spectrum.png');}.colorpicker .spectrum .slider-bg{width:297px;margin:0;height:100%;background:transparent;}.colorpicker .spectrum .marker{width:37px;height:37px;background:url('../images/spectrum-handle.png');margin:0 0 0 -16px;left:0;top:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.colorpicker .buttons{width:300px;margin:0 auto;}.colorpicker .buttons button{display:block;border:0;padding:0;width:298px;height:36px;color:#fff;font-size:14px;background:#333;background:-moz-linear-gradient(top,#555 0,#333 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#555),color-stop(100%,#333));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555',endColorstr='#333333',GradientType=0);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;cursor:pointer;}.colorpicker .buttons button:hover{background:#444;background:-moz-linear-gradient(top,#666 0,#444 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#666),color-stop(100%,#444));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#666666',endColorstr='#444444',GradientType=0);}.colorpicker .buttons button:active{background:#222;background:-moz-linear-gradient(top,#222 0,#444 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#222),color-stop(100%,#444));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222',endColorstr='#444444',GradientType=0);}#preload{display:none;}#stage{position:absolute;}#app_header{position:absolute;z-index:10;top:0;left:0;width:100%;}#app_header .toolbar_wrapper{position:relative;}#app_header #color{width:40px;height:40px;margin:5px 10px 5px 10px;float:left;cursor:pointer;pointer-events:auto;}#app_header #color:after{content:'';display:block;width:1px;height:40px;background:#222;border-right:1px solid #444;margin-left:55px;}#app_header #swatches{float:right;margin-right:10px;}#app_header #swatches div{width:29px;height:29px;margin:9px 8px 0 0;background:#444;-webkit-background-clip:padding-box;float:left;}#app_header #swatches div:last-child{margin-right:0;}#app_header #colorpicker{width:100%;height:408px;position:absolute;top:52px;left:0;background:#111;display:none;}#app_content{height:100%;}#app_content .message{position:absolute;top:40%;left:0;z-index:10;text-align:center;font-size:16px;font-weight:bold;line-height:1;padding:15px 30px;background:#000;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;-webkit-box-shadow:0 0 10px #000;-moz-box-shadow:0 0 10px #000;box-shadow:0 0 10px #000;opacity:0;display:none;}#app_content .message h2{font-size:20px;margin:0 0 5px 0;}#app_content .message button{border:0;padding:10px 15px;margin:20px 10px 0 10px;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;background:#222;background:-moz-linear-gradient(top,#444 0,#222 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#444),color-stop(100%,#222));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#444444',endColorstr='#222222',GradientType=0);font-size:14px;color:#fff;cursor:pointer;float:left;}#app_content .message button:hover{background:#333;background:-moz-linear-gradient(top,#555 0,#333 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#555),color-stop(100%,#333));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#555',endColorstr='#333',GradientType=0);}#app_content .message button:active{background:#333;background:-moz-linear-gradient(top,#111 0,#333 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#111),color-stop(100%,#333));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#111',endColorstr='#333',GradientType=0);}#app_content .message .save_yes{background:#444;background:-moz-linear-gradient(top,#666 0,#444 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#666),color-stop(100%,#444));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#666',endColorstr='#444',GradientType=0);}#app_content .message .save_yes:hover{background:#555;background:-moz-linear-gradient(top,#777 0,#555 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#777),color-stop(100%,#555));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#777',endColorstr='#555',GradientType=0);}#app_content .message .save_yes:active{background:#555;background:-moz-linear-gradient(top,#333 0,#555 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333),color-stop(100%,#555));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333',endColorstr='#555',GradientType=0);}#draw .toolbar_wrapper{position:relative;z-index:7;top:52px;display:none;}#draw .tooltips p{position:absolute;z-index:7;text-align:center;font-size:14px;font-weight:bold;line-height:1.5;background:#111;padding:5px;margin:0;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 0 5px #000;-moz-box-shadow:0 0 5px #000;box-shadow:0 0 5px #000;}#draw .tooltips p:before{display:block;position:absolute;content:'';margin:0;padding:0;height:0;width:0;font-size:0;line-height:0;width:0;}#draw .tooltips .tip_up:before{top:-10px;border-bottom:10px solid #111;border-right:5px solid transparent;border-left:5px solid transparent;}#draw .tooltips .tip_down:before{bottom:-10px;border-top:10px solid #111;border-right:5px solid transparent;border-left:5px solid transparent;}#draw .tooltips .color{width:100px;top:5px;left:5px;}#draw .tooltips .color:before{left:20px;}#draw .tooltips .swatches{width:180px;top:5px;right:5px;}#draw .tooltips .swatches:before{left:10px;}#draw .tooltips .size{width:85px;bottom:5px;left:5px;}#draw .tooltips .size:before{left:13px;}#draw .tooltips .opacity{width:105px;bottom:5px;right:5px;}#draw .tooltips .opacity:before{right:15px;}#draw .tooltips .info_ios,#draw .tooltips .info_android{display:none;}.iphone #draw .tooltips .info_desktop,.ipad #draw .tooltips .info_desktop,.iphone #draw .tooltips .info_android,.ipad #draw .tooltips .info_android{display:none;}.android #draw .tooltips .info_desktop,.android #draw .tooltips .info_ios{display:none;}.iphone #draw .tooltips .info_ios,.ipad #draw .tooltips .info_ios{display:block;}.android #draw .tooltips .info_android{display:block;}#draw .tooltips .info{width:200px;left:55px;top:47%;font-size:16px;}#draw .tooltips .info small{font-size:12px;color:#aaa;}#draw .brush_size{position:absolute;top:100px;width:10px;height:10px;-webkit-border-radius:50px;-moz-border-radius:50px;border-radius:50px;background:#fff;border:1px solid #222;margin:0 auto;display:none;}#app_footer{position:absolute;z-index:5;bottom:0;left:0;width:100%;}#app_footer .slider{width:115px;float:left;}#app_footer #size{margin:8px 10px 8px 20px;}#app_footer #opacity{margin:8px 0 8px 35px;}#footer_draw .slider-handle{background-image:url('../images/slider_icons.png');}#app_footer #size .slider-handle{background-position:0 0;}#app_footer #opacity .slider-handle{background-position:-30px 0;}#app_footer .buttons{clear:both;width:100%;margin:10px 0 0 0;overflow:hidden;background:#000;background:-moz-linear-gradient(top,#444 0,#000 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#444),color-stop(100%,#000));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#444444',endColorstr='#000000',GradientType=0);}#app_footer .buttons button{float:left;border:0;padding:0;margin:10px 25px 10px 25px;width:30px;cursor:pointer;background:transparent;color:#fff;}.retina{width:45px;margin:10px 5px 10px 5px;}#app_footer .buttons span{display:block;height:30px;width:30px;margin:0 0 5px 0;background:url('../images/icons.png');}#app_footer .buttons button.disabled{opacity:.2;pointer-events:none;}#app_footer .home span,.iphone #app_footer .home:hover span{background-position:0 0;}#app_footer .undo span,.iphone #app_footer .undo:hover span{background-position:0 -30px;}#app_footer .redo span,.iphone #app_footer .redo:hover span{background-position:0 -60px;}#app_footer .save span,.iphone #app_footer .save:hover span{background-position:0 -90px;}#app_footer .home:hover span{background-position:-30px 0;}#app_footer .undo:hover span{background-position:-30px -30px;}#app_footer .redo:hover span{background-position:-30px -60px;}#app_footer .save:hover span{background-position:-30px -90px;}#app_footer .home.active span,#app_footer .home:active span,.iphone #app_footer .home:active span{background-position:-60px 0;}#app_footer .undo.active span,#app_footer .undo:active span,.iphone #app_footer .undo:active span{background-position:-60px -30px;}#app_footer .redo.active span,#app_footer .redo:active span,.iphone #app_footer .redo:active span{background-position:-60px -60px;}#app_footer .save.active span,#app_footer .save:active span,.iphone #app_footer .save:active span{background-position:-60px -90px;}#app_footer .save_box{position:absolute;bottom:-367px;width:100%;height:367px;text-align:left;background:#000;background:-moz-linear-gradient(top,#333 0,#000 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333),color-stop(100%,#000));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333',endColorstr='#000000',GradientType=0);}#app_footer .save_box .save_choices{display:block;}#app_footer .save_box .cloud_info,#app_footer .save_box .cloud_register,#app_footer .save_box .cloud_login{display:none;}#app_footer .save_box h2{margin:15px 10px 5px 10px;}#app_footer .save_box p{margin:0 10px;font-size:14px;}#app_footer .save_box button{display:block;border:0;padding:0;margin:10px;width:300px;height:40px;color:#fff;font-size:14px;background:#444;background:-moz-linear-gradient(top,#666 0,#444 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#666),color-stop(100%,#444));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#666666',endColorstr='#444444',GradientType=0);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;cursor:pointer;}#app_footer .save_box button:hover{background:#555;background:-moz-linear-gradient(top,#777 0,#555 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#777),color-stop(100%,#555));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#777777',endColorstr='#555555',GradientType=0);}#app_footer .save_box button:active{background:#555;background:-moz-linear-gradient(top,#333 0,#555 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333),color-stop(100%,#555));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333',endColorstr='#555555',GradientType=0);}#app_footer .save_box button.disabled{pointer-events:none;color:#999;background:#222;background:-moz-linear-gradient(top,#333 0,#222 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333),color-stop(100%,#222));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333',endColorstr='#222',GradientType=0);}#app_footer .save_box .save_cancel,#app_footer .save_box .back_button{margin-top:20px;background:#222;background:-moz-linear-gradient(top,#333 0,#222 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333),color-stop(100%,#222));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333',endColorstr='#222222',GradientType=0);}#app_footer .save_box .save_cancel:hover{background:#333;background:-moz-linear-gradient(top,#444 0,#333 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#444),color-stop(100%,#333));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#444444',endColorstr='#333333',GradientType=0);}#app_footer .save_box .save_cancel:active{background:#111;background:-moz-linear-gradient(top,#111 0,#222 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#111),color-stop(100%,#222));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#111111',endColorstr='#222222',GradientType=0);}#app_footer .save_box .save_local{margin-top:30px;}#app_footer .save_box .cloud_info p,#app_footer .save_box .cloud_register p,#app_footer .save_box .cloud_login p{margin:0 10px 15px 10px;}#app_footer .save_box .cloud_register form,#app_footer .save_box .cloud_login form{margin:0 10px;}#app_footer .save_box .cloud_register input,#app_footer .save_box .cloud_login input{width:300px;margin:5px 0 10px 0;}#app_footer .save_box .cloud_register button,#app_footer .save_box .cloud_login button{margin:5px 0 0 0;}#app_footer .save_box .cloud_register .back_button,#app_footer .save_box .cloud_login .back_button{width:100px;float:left;margin-right:15px;}#app_footer .save_box .cloud_register .register_button,#app_footer .save_box .cloud_login .login_button{width:185px;}#header_home,#header_draw,#app_content #home,#app_content #draw,#footer_home,#footer_draw{display:none;}#mode_home #header_home,#mode_home #app_content #home,#mode_home #footer_home{display:block;}#mode_draw #header_draw,#mode_draw #app_content #draw,#mode_draw #footer_draw{display:block;}#header_home h1{margin-top:8px;}#header_home button{position:absolute;height:30px;padding:2px 15px 0 15px;margin:0;border:0;color:#fff;font-size:12px;font-weight:bold;background:#222;background:-moz-linear-gradient(top,#555 0,#222 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#555),color-stop(100%,#222));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555',endColorstr='#222222',GradientType=0);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:inset 0 1px 2px #111;-moz-box-shadow:inset 0 1px 1px #111;box-shadow:inset 0 1px 2px #111;cursor:pointer;}#header_home button:hover{background:#333;background:-moz-linear-gradient(top,#666 0,#333 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#666),color-stop(100%,#333));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#666666',endColorstr='#333333',GradientType=0);}#header_home button:active{background:#444;background:-moz-linear-gradient(top,#333 0,#444 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333),color-stop(100%,#444));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333',endColorstr='#444444',GradientType=0);}#header_home .edit{top:-2px;left:5px;}#header_home .edit.hidden{display:none;}#header_home .login{top:-2px;right:5px;}#app_content #home{width:100%;height:100%;background:#111;}#mode_home #app_footer{display:none!important;}#home .home_content{width:300px;margin:44px auto 0 auto;position:relative;}#home .login_box{position:absolute;top:-239px;left:-10px;width:320px;height:239px;background:#000;background:-moz-linear-gradient(top,#333 0,#000 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333),color-stop(100%,#000));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333',endColorstr='#000000',GradientType=0);}#home .login_box fieldset{margin:10px;}#home .login_box button{display:block;margin:10px 0 0 0;border:0;padding:0;width:100%;height:40px;color:#fff;font-size:14px;background:#333;background:-moz-linear-gradient(top,#555 0,#333 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#555),color-stop(100%,#333));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555',endColorstr='#333333',GradientType=0);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;cursor:pointer;}#home .login_box button:hover{background:#444;background:-moz-linear-gradient(top,#666 0,#444 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#666),color-stop(100%,#444));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#666',endColorstr='#444',GradientType=0);}#home .login_box button:active{background:#444;background:-moz-linear-gradient(top,#222 0,#444 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#222),color-stop(100%,#444));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#222',endColorstr='#444',GradientType=0);}#home .grid{padding:10px 0 0 3px;}#home .grid .image{width:88px;height:88px;margin:0 12px 12px 0;float:left;border:1px solid #444;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;}#home .grid .image:nth-child(3n+0){margin-right:0;}#home .grid:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#home .grid .image.new{background:#444;font-size:14px;font-weight:bold;line-height:22px;text-align:center;}#home .grid .image.new b{font-size:80px;font-weight:bold;line-height:50px;}#home .grid .image.new:hover,#home .grid .image.new:active{background:#666;}#home .grid .image.active{cursor:pointer;}#home .grid .image.active.delete{position:relative;}#home .grid .image.active.delete:not(.new):before{content:'';position:absolute;top:19px;left:18px;width:52px;height:50px;background:url('../images/delete.png');}#home ul li{list-style:none;margin:10px 0 0 0;font-size:14px;color:#aaa;}#home ul li strong{font-weight:bold;color:#ddd;} -------------------------------------------------------------------------------- /js/apphelp.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * appHelp v1.0 3 | * 4 | * Copyright 2010, Johannes Koggdal 5 | * http://koggdal.com/ 6 | */ 7 | (function( window, document, undefined ) { 8 | 9 | var appHelp, $, Helpers, pointer_down = false; 10 | 11 | // Wrapper function to easily access helper functions 12 | // Uses the dollar sign. If you want to use jQuery or any other library that uses $, 13 | // use the full variable name (like jQuery instead of $) inside of this file 14 | appHelp = $ = function( selector ) { 15 | 16 | // Return a new instance of the Helpers object 17 | return new Helpers( selector ); 18 | }; 19 | 20 | // Constructor for the helper functions 21 | Helpers = function( selector ) { 22 | 23 | var elements; 24 | this.touch_enabled = undefined; 25 | this.animating = []; 26 | 27 | // Get the correct elements based on a CSS selector if argument is a string 28 | if( typeof selector === "string" ) { 29 | elements = this.nodeListToArray( document.querySelectorAll( selector ) ); 30 | } 31 | 32 | // Get the elements from the selector if its an array 33 | else if( selector.length && selector.length > 0 ) { 34 | elements = selector; 35 | } 36 | 37 | // Get the element from the selector if its a single element 38 | else { 39 | elements = [selector]; 40 | } 41 | 42 | // Set animation status to false for all elements 43 | for( var i = 0, l = elements.length; i < l; i++ ) 44 | this.animating.push( false ); 45 | 46 | // Assign the selected elements to the object 47 | if( elements !== null && elements !== undefined ) 48 | this.elements = elements; 49 | 50 | // Return the object itself 51 | return this; 52 | }; 53 | 54 | // Connect the helpers prototype to the appHelp class, to enable plugins to hook into the class 55 | Helpers.prototype = appHelp.fn = { 56 | 57 | // Function to get a specific element 58 | get: function( index ) { 59 | return this.elements[ index ]; 60 | }, 61 | 62 | // Function to find elements inside of the current element collection with a selector 63 | find: function( selector ) { 64 | 65 | var allElements = []; 66 | 67 | // Loop through all the selected elements 68 | for( var i = 0, l = this.elements.length; i < l; i++ ) { 69 | var elem = this.elements[ i ], 70 | newElements = this.nodeListToArray( elem.querySelectorAll( selector ) ); 71 | 72 | allElements = allElements.concat( newElements ); 73 | } 74 | 75 | return $( allElements ); 76 | }, 77 | 78 | // Function to convert a NodeList to an array 79 | nodeListToArray: function( nodeList ) { 80 | var ret = []; 81 | 82 | for( var i = 0, l = nodeList.length; i < l; i++ ) 83 | ret.push( nodeList[ i ] ); 84 | 85 | return ret; 86 | }, 87 | 88 | // Function to make it easier to debug objects in mobile devices where dev tools arent available 89 | alert: function( obj ){ 90 | var output = ''; 91 | 92 | if( typeof obj === "object" && obj.nodeType === undefined ) { 93 | for(var x in obj){ 94 | output += x+' : '+obj[x]+"\n"; 95 | } 96 | } else if( typeof obj === "string" ) { 97 | output = obj; 98 | } 99 | 100 | alert(output); 101 | }, 102 | 103 | // Function to bind events to selected elements 104 | bind: function( events_string, fn ) { 105 | 106 | // Loop through all the selected elements 107 | for( var n = 0, len = this.elements.length; n < len; n++ ) { 108 | var elem = this.elements[ n ]; 109 | 110 | // Support multiple events in one string 111 | // Split the events and add the callback function for each event type 112 | var events = events_string.split( ' ' ); 113 | for( var i = 0, l = events.length; i < l; i++ ){ 114 | 115 | // Custom event that takes care of taps for touch devices 116 | // It behaves like click would on a desktop browser 117 | // The click event cant be used since its emulated and slow on mobile devices 118 | // This event uses touchstart/touchend for mobile devices and mousedown/mouseup for desktop 119 | // It will only trigger if the enter and leave events occur on the same element 120 | if( events[i] == "touchclick" ) { 121 | 122 | doc_pointerup = function( e ){ 123 | // Prevent touch devices from trigger the emulated mouse event 124 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 125 | return false; 126 | 127 | pointer_down = false; 128 | }; 129 | 130 | var _this = this, 131 | 132 | // Function triggered when touching starts / mouse button is pushes down 133 | start = function( e ) { 134 | // Prevent touch devices from trigger the emulated mouse event 135 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 136 | return false; 137 | 138 | pointer_down = true; 139 | 140 | 141 | 142 | if( elem !== document ) { 143 | // Add event handlers for the document which will trigger if the pointer is released outside of element 144 | document.addEventListener( 'touchend', doc_pointerup, false ); 145 | document.addEventListener( 'mouseup', doc_pointerup, false ); 146 | } 147 | }, 148 | 149 | // Function triggered when touching ends / mouse button is released 150 | end = function( e, m ) { 151 | // Prevent touch devices from trigger the emulated mouse event 152 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 153 | return false; 154 | 155 | // Get pointer position and element position 156 | var pos = $.getPos( e, 0 ), 157 | dimensions = this === document ? {top:0,right:window.innerWidth,bottom:window.innerHeight,left:0} : this.getBoundingClientRect(); 158 | 159 | // Check if pointer is down, and inside of the element 160 | if( pointer_down && pos.x >= dimensions.left && pos.x <= dimensions.right && pos.y > dimensions.top && pos.y < dimensions.bottom ) { 161 | 162 | if( elem !== document ) { 163 | // Unbind event handler on document 164 | document.removeEventListener( 'touchend', doc_pointerup, false ); 165 | document.removeEventListener( 'mouseup', doc_pointerup, false ); 166 | } 167 | 168 | pointer_down = false; 169 | 170 | // Trigger the user set callback method 171 | fn.call( this, e, m ); 172 | } 173 | }; 174 | 175 | // Bind event handlers to the element 176 | elem.addEventListener( 'touchstart', start, false ); 177 | elem.addEventListener( 'mousedown', start, false ); 178 | elem.addEventListener( 'touchend', (function( m ){ return function( e ){ end.call(this, e, m ) }; })(n), false ); 179 | elem.addEventListener( 'mouseup', (function( m ){ return function( e ){ end.call(this, e, m ) }; })(n), false ); 180 | } else { 181 | 182 | // Add the specified event handler 183 | elem.addEventListener( events[i], fn, false ); 184 | } 185 | } 186 | } 187 | 188 | return this; 189 | }, 190 | 191 | // Function to unbind events from selected elements 192 | unbind: function( events, fn ) { 193 | 194 | // Loop through all the selected elements 195 | for( var i = 0, l = this.elements.length; i < l; i++ ) { 196 | var elem = this.elements[ i ]; 197 | 198 | // Support multiple events in one string 199 | // Split the events and remove the callback function for each event type 200 | events = events.split( ' ' ); 201 | for( var i = 0, l = events.length; i < l; i++ ) 202 | elem.removeEventListener( events[i], fn, false ); 203 | } 204 | 205 | return this; 206 | }, 207 | 208 | // Function to check for touch device 209 | isTouchDevice: function( e ) { 210 | if( this.touch_enabled === undefined || this.touch_enabled === false ) 211 | this.touch_enabled = !!e.touches; 212 | 213 | return this.touch_enabled; 214 | }, 215 | 216 | // Function to extend objects with new properties 217 | extend: function( target, obj ) { 218 | 219 | // Loop through all the properties and add them to the target object 220 | for( var property in obj ) { 221 | if( typeof obj[property] === "object" && obj[property].length === undefined && obj[property].nodeType === undefined ) 222 | $.extend( target[property], obj[property] ); 223 | else 224 | target[property] = obj[property]; 225 | } 226 | 227 | return target; 228 | }, 229 | 230 | // Function to find the current pointer position (both mouse and touch are supported) 231 | getPos: function( e, touchIndex, elem ){ 232 | touchIndex = touchIndex || 0; 233 | 234 | // Get elements offset from the page edges 235 | var offset = elem ? elem.getBoundingClientRect() : ( this.elements ? this.elements[ 0 ].getBoundingClientRect() : {top:0,right:0,bottom:0,left:0} ), 236 | 237 | // The pointer position relative to the window 238 | pointer = { 239 | x: e.touches ? (e.touches.length > 0 ? e.touches[touchIndex].clientX : e.changedTouches[touchIndex].clientX ) : e.clientX, 240 | y: e.touches ? (e.touches.length > 0 ? e.touches[touchIndex].clientY : e.changedTouches[touchIndex].clientY ) : e.clientY, 241 | }, 242 | 243 | // The scroll position 244 | scroll = { 245 | x: window.scrollX, 246 | y: window.scrollY 247 | }, 248 | 249 | // The position within the element 250 | pos = { 251 | x: pointer.x - offset.left - scroll.x, 252 | y: pointer.y - offset.top - scroll.y 253 | }; 254 | 255 | return pos; 256 | }, 257 | 258 | // Function to see if an element is hidden or not 259 | // Selects first element if it is called on a collection of elements 260 | isHidden: function() { 261 | var elem = this.elements[ 0 ], 262 | style = window.getComputedStyle( elem, null ), 263 | visibility = style.getPropertyValue( 'visibility' ), 264 | display = style.getPropertyValue( 'display' ); 265 | 266 | if( display == "none" || visibility == "hidden" ) 267 | return true; 268 | else 269 | return false; 270 | }, 271 | 272 | // Function to hide an element 273 | hide: function() { 274 | 275 | // Loop through all the selected elements 276 | for( var i = 0, l = this.elements.length; i < l; i++ ) { 277 | var elem = this.elements[ i ]; 278 | 279 | if( elem.getAttribute( 'data-olddisplay' ) == "" ) 280 | elem.setAttribute( 'data-olddisplay', elem.style.display == "" ? "block" : elem.style.display ); 281 | 282 | elem.style.display = "none"; 283 | } 284 | 285 | return this; 286 | }, 287 | 288 | // Function to show an element 289 | show: function() { 290 | 291 | // Loop through all the selected elements 292 | for( var i = 0, l = this.elements.length; i < l; i++ ) { 293 | var elem = this.elements[ i ], 294 | 295 | oldBlock = elem.getAttribute( 'data-olddisplay' ) || "block"; 296 | oldVisibility = elem.getAttribute( 'data-oldvisibility' ) || "visible"; 297 | 298 | elem.style.display = oldBlock; 299 | elem.style.visibility = oldVisibility; 300 | } 301 | 302 | return this; 303 | }, 304 | 305 | // Function to check if an element has a specific class 306 | // Selects first element if it is called on a collection of elements 307 | hasClass: function( className ) { 308 | 309 | var elem = this.elements[ 0 ]; 310 | 311 | // Use native method if available 312 | if( elem.classList !== undefined ) { 313 | return elem.classList.contains( className ); 314 | } 315 | 316 | // Emulate the native behaviour if not available 317 | else { 318 | if( ~(' '+elem.className+' ').indexOf( ' '+className+' ' ) ) 319 | return true; 320 | else 321 | return false; 322 | } 323 | }, 324 | 325 | // Function to add a class to an element 326 | addClass: function( className ) { 327 | 328 | // Loop through all the selected elements 329 | for( var i = 0, l = this.elements.length; i < l; i++ ) { 330 | var elem = this.elements[ i ]; 331 | 332 | // Use native method if available 333 | if( elem.classList !== undefined ) { 334 | elem.classList.add( className ); 335 | } 336 | 337 | // Emulate the native behaviour if not available 338 | else { 339 | var classes = elem.className; 340 | if( !~classes.indexOf( className ) ) 341 | elem.className = classes.length > 0 ? classes+' '+className : className; 342 | } 343 | } 344 | 345 | return this; 346 | }, 347 | 348 | // Function to remove a class from an element 349 | removeClass: function( className ) { 350 | 351 | // Loop through all the selected elements 352 | for( var i = 0, l = this.elements.length; i < l; i++ ) { 353 | var elem = this.elements[ i ]; 354 | 355 | // Use native method if available 356 | if( elem.classList !== undefined ) { 357 | elem.classList.remove( className ); 358 | } 359 | 360 | // Emulate the native behaviour if not available 361 | else { 362 | var classes = elem.className, 363 | regexp = new RegExp(className,"g"); 364 | elem.className = classes.replace(regexp,''); 365 | } 366 | } 367 | 368 | return this; 369 | }, 370 | 371 | // Function to remove elements from the DOM 372 | remove: function() { 373 | // Loop through all the selected elements 374 | for( var n = 0, len = this.elements.length; n < len; n++ ) { 375 | var elem = this.elements[ n ]; 376 | 377 | elem.parentNode.removeChild( elem ); 378 | } 379 | }, 380 | 381 | // Function to get a specific computed style 382 | // Selects first element if it is called on a collection of elements 383 | getStyle: function( property ){ 384 | var elem = this.elements[ 0 ]; 385 | return window.getComputedStyle( elem, null ).getPropertyValue( property ); 386 | }, 387 | 388 | // Function to animate an elements CSS properties 389 | animate: function( parameters, duration, fps, callback ) { 390 | 391 | // Set default values if not passed to the function 392 | duration = duration || 1000; 393 | fps = fps || 30; 394 | callback = callback || function(){}; 395 | 396 | // Loop through all the selected elements 397 | for( var n = 0, len = this.elements.length; n < len; n++ ) { 398 | var elem = this.elements[ n ]; 399 | 400 | // Cancel animation if another animation is already running on this element 401 | // Will probably add some sort of animation queue later 402 | if( this.animating[ n ] ) 403 | continue; 404 | 405 | // Variable declarations 406 | var _this = this, 407 | cssTransitionProperties = '', 408 | properties = [], 409 | endValues = [], 410 | startValues = [], 411 | changes = [], 412 | frames = Math.ceil(duration * ( fps / 1000 )), 413 | frame = 1, 414 | animation, 415 | 416 | // Boolean values to see if the browser supports CSS Transitions 417 | transition_webkit = elem.style.WebkitTransitionProperty !== undefined, 418 | transition_mozilla = elem.style.MozTransitionProperty !== undefined, 419 | transition_opera = elem.style.OTransitionProperty !== undefined, 420 | 421 | // Function that gets the unit for a specified property 422 | unit = function( property ) { 423 | var units = { 424 | opacity: '', 425 | _default: 'px' 426 | }; 427 | 428 | if( units[property] !== undefined ) 429 | return units[property]; 430 | else 431 | return units._default; 432 | }; 433 | 434 | // Loop through all the properties and save the values 435 | for( var property in parameters ) { 436 | cssTransitionProperties += property+', '; 437 | properties.push( property ); 438 | endValues.push( parseFloat( parameters[ property ] ) ); 439 | startValues.push( parseFloat( this.getStyle( property ) ) ); 440 | changes.push( Math.round( ( endValues[ endValues.length-1 ] - startValues[ startValues.length-1 ] ) / frames * 100)/100 ); 441 | } 442 | 443 | // Remove the last comma and space from the list of properties for CSS Transitions 444 | cssTransitionProperties = cssTransitionProperties.substring( 0, cssTransitionProperties.length-2 ); 445 | 446 | // If the browser supports CSS Transitions 447 | if( transition_webkit || transition_mozilla || transition_opera ) { 448 | 449 | // Set transition for Webkit browsers 450 | if( transition_webkit ) { 451 | elem.style.WebkitTransitionProperty = cssTransitionProperties; 452 | elem.style.WebkitTransitionDuration = duration+"ms"; 453 | } 454 | else 455 | // Set transition for Firefox 4 and up 456 | if( transition_mozilla ) { 457 | elem.style.MozTransitionProperty = cssTransitionProperties; 458 | elem.style.MozTransitionDuration = duration+"ms"; 459 | } 460 | else 461 | // Set transition for Opera 10.50 and up 462 | if( transition_opera ) { 463 | elem.style.OTransitionProperty = cssTransitionProperties; 464 | elem.style.OTransitionDuration = duration+"ms"; 465 | } 466 | 467 | // Set the end values for all the properties, and the browser will animate this change 468 | for( var i = 0, l = properties.length; i < l; i++ ) { 469 | elem.style[ properties[ i ] ] = endValues[ i ]+unit( properties[ i ] ); 470 | } 471 | 472 | // Set animation status, to prevent other animations on the same element to interfere 473 | this.animating[ n ] = true; 474 | 475 | // Set up a timer to trigger ended() if the duration has passes 476 | var end_timer = setTimeout( function(){ 477 | ended(); 478 | }, duration ); 479 | 480 | // Function that triggers on transition end 481 | var ended = (function( m ){ return function( e ) { 482 | 483 | // Set timer 484 | clearTimeout( end_timer ); 485 | 486 | // Set animation status 487 | _this.animating[ m ] = false; 488 | 489 | // Unbind event handler 490 | $( elem ).unbind( 'webkitTransitionEnd oTransitionEnd transitionend', ended, false ); 491 | 492 | // Trigger the callback 493 | callback(); 494 | } })( n ); 495 | 496 | // Bind event handler to reset the animation status on completion 497 | $( elem ).bind( 'webkitTransitionEnd oTransitionEnd transitionend', ended, false ); 498 | } 499 | 500 | // If the browser doesnt support CSS Transitions, use a normal JavaScript animation 501 | else { 502 | 503 | this.animating[ n ] = true; 504 | 505 | // Function that sets the new value for all the properties 506 | animation = function( m ) { 507 | 508 | // Set new value for all properties 509 | for( var i = 0, l = properties.length; i < l; i++ ) 510 | elem.style[ properties[ i ] ] = ( startValues[ i ] + changes[ i ] * frame )+unit(properties[i]); 511 | 512 | // Advance frame count 513 | frame++; 514 | 515 | // Trigger function again after a short delay if animation is not complete 516 | if( frame <= frames ) { 517 | setTimeout( animation, 1000 / fps, m ); 518 | } 519 | 520 | // If animation is complete 521 | else { 522 | 523 | // Set the end value for all properties 524 | for( var i = 0, l = properties.length; i < l; i++ ) 525 | elem.style[ properties[ i ] ] = ( endValues[ i ] )+unit(properties[i]); 526 | 527 | // Reset animation status 528 | _this.animating[ m ] = false; 529 | 530 | // Trigger the callback function 531 | callback(); 532 | } 533 | }; 534 | 535 | // Trigger the animation function to start the animation 536 | animation( n ); 537 | } 538 | } 539 | }, 540 | 541 | // Function for doing AJAX calls 542 | ajax: function( options ) { 543 | 544 | var settings = { 545 | url: '', 546 | data: '', 547 | type: 'GET', 548 | success: function(){}, 549 | failure: function(){} 550 | }; 551 | settings = $.extend( settings, options ); 552 | 553 | var xhr = new XMLHttpRequest(); 554 | if( settings.type == 'GET' ) { 555 | settings.data = settings.data.length > 0 ? '?'+settings.data : ''; 556 | xhr.open( settings.type, settings.url+settings.data ); 557 | xhr.send( null ); 558 | } else { 559 | xhr.open( settings.type, settings.url ); 560 | xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 561 | xhr.send( settings.data ); 562 | } 563 | 564 | xhr.onreadystatechange = function() { 565 | if( this.readyState == 4 && this.status == 200 ) 566 | settings.success( this.responseText ); 567 | else if( this.readyState == 4 && this.status != 200 ) 568 | settings.failure( this.status ); 569 | }; 570 | } 571 | }; 572 | 573 | // Enable shorthands 574 | $.alert = Helpers.prototype.alert; 575 | $.isTouchDevice = Helpers.prototype.isTouchDevice; 576 | $.extend = Helpers.prototype.extend; 577 | $.getPos = Helpers.prototype.getPos; 578 | $.getStyle = Helpers.prototype.getStyle; 579 | $.ajax = Helpers.prototype.ajax; 580 | 581 | // Add the helper class to the global namespace in window 582 | window.appHelp = window.$ = appHelp; 583 | 584 | })( window, window.document ); -------------------------------------------------------------------------------- /js/production/app-combined.min.1294012932.js: -------------------------------------------------------------------------------- 1 | /* 2 | * appHelp v1.0 3 | * 4 | * Copyright 2010, Johannes Koggdal 5 | * http://koggdal.com/ 6 | */ 7 | (function(e,b,g){var a,f,d,c=false;a=f=function(h){return new d(h)};d=function(h){var m;this.touch_enabled=g;this.animating=[];if(typeof h==="string"){m=this.nodeListToArray(b.querySelectorAll(h))}else{if(h.length&&h.length>0){m=h}else{m=[h]}}for(var k=0,j=m.length;k=l.left&&v.x<=l.right&&v.y>l.top&&v.y0?k.touches[j].clientX:k.changedTouches[j].clientX):k.clientX,y:k.touches?(k.touches.length>0?k.touches[j].clientY:k.changedTouches[j].clientY):k.clientY,},h={x:e.scrollX,y:e.scrollY},n={x:l.x-m.left-h.x,y:l.y-m.top-h.y};return n},isHidden:function(){var j=this.elements[0],i=e.getComputedStyle(j,null),h=i.getPropertyValue("visibility"),k=i.getPropertyValue("display");if(k=="none"||h=="hidden"){return true}else{return false}},hide:function(){for(var j=0,h=this.elements.length;j0?j+" "+m:m}}}return this},removeClass:function(m){for(var k=0,h=this.elements.length;k0?"?"+i.data:"";j.open(i.type,i.url+i.data);j.send(null)}else{j.open(i.type,i.url);j.setRequestHeader("Content-type","application/x-www-form-urlencoded");j.send(i.data)}j.onreadystatechange=function(){if(this.readyState==4&&this.status==200){i.success(this.responseText)}else{if(this.readyState==4&&this.status!=200){i.failure(this.status)}}}}};f.alert=d.prototype.alert;f.isTouchDevice=d.prototype.isTouchDevice;f.extend=d.prototype.extend;f.getPos=d.prototype.getPos;f.getStyle=d.prototype.getStyle;f.ajax=d.prototype.ajax;e.appHelp=e.$=a})(window,window.document); 8 | /* 9 | * Slider v1.0 10 | * appHelp Plugin 11 | * 12 | * Copyright 2010, Johannes Koggdal 13 | * http://koggdal.com/ 14 | */ 15 | (function(b,a,c,d){c.fn.slider=function(o){var g={min:1,max:10,start:1,begin:function(p){},change:function(p){},end:function(p){}},e=this.get(0),i=e.firstElementChild,m=i.firstElementChild,l=parseInt(b.getComputedStyle(i,null).getPropertyValue("width")),f=false,k,h,n,j;c.extend(g,o);j=function(q){if(q.type===d){n=~~q;m.style.left=Math.floor(n/(g.max-g.min)*l)+"px"}else{var p=c.getPos(q,0,i).x;if(p<=0){m.style.left="0px";n=g.min}else{if(p>l){m.style.left=l+"px";n=g.max}else{m.style.left=p+"px";n=Math.ceil(p/l*(g.max-g.min));if(n<=g.min){n=g.min}else{if(n>g.max){n=g.max}}}}}g.change(n)};j(g.start);k=function(p){if(c.isTouchDevice(p)&&~p.type.indexOf("mouse")){return false}if(f){j(p)}};h=function(p){if(c.isTouchDevice(p)&&~p.type.indexOf("mouse")){return false}if(f){f=false;g.end();c(a).unbind("touchmove mousemove",k).unbind("touchend mouseup",h)}};c(e).bind("touchstart mousedown",function(p){if(c.isTouchDevice(p)&&~p.type.indexOf("mouse")){return false}p.preventDefault();id=p.touches?p.touches[p.touches.length-1].identifier:"mouse";f=true;g.begin();j(p);c(a).bind("touchmove mousemove",k).bind("touchend mouseup",h)});return function(p){j(p)}}})(window,window.document,appHelp); 16 | /* 17 | * ColorPicker v1.0 18 | * appHelp Plugin 19 | * 20 | * Copyright 2010, Johannes Koggdal 21 | * http://koggdal.com/ 22 | */ 23 | (function(b,a,c,d){c.fn.colorPicker=function(g){var u={startValue:175,addSwatch_cb:function(x){},change:function(x){}};c.extend(u,g);var m={r:0,g:0,b:0},f={x:299,y:0},j={red:0,green:0,blue:0},s=1,l=this.find(".colorspace").get(0),h=this.find(".colorspace .marker").get(0),w=this.find(".spectrum").get(0),q=this.find(".add-swatch").get(0),o=parseInt(b.getComputedStyle(w,null).getPropertyValue("width")),t=false,k=false,i,p=function(H,G,D){H=H/299*255;G=G/299*255;var E=H/255,A=255-G,B=1-D.red/255,I=1-D.green/255,J=1-D.blue/255,z=Math.round((1-B*E)*A),C=Math.round((1-I*E)*A),F=Math.round((1-J*E)*A);return{red:z,green:C,blue:F}},n=function(x){var z,y;if(x===d){z=f}else{z=c.getPos(x,0,l)}if(z.x>300||z.y>300||z.x<0||z.y<0){return false}h.style.left=z.x+"px";h.style.top=z.y+"px";y=p(z.x,z.y,m);f={x:z.x,y:z.y};j={red:y.red,green:y.green,blue:y.blue};u.change(j)},v=function(x){if(c.isTouchDevice(x)&&~x.type.indexOf("mouse")){return false}if(t){n(x)}},r=function(x){if(c.isTouchDevice(x)&&~x.type.indexOf("mouse")){return false}if(t){t=false;c(a).unbind("touchend mouseup",r)}};c(l).bind("touchstart mousedown",function(x){if(c.isTouchDevice(x)&&~x.type.indexOf("mouse")){return false}x.preventDefault();t=true;n(x);c(a).bind("touchend mouseup",r)}).bind("touchmove mousemove",v);var e=c(w).slider({min:1,max:299,start:u.startValue,change:function(E){var z=E,y=o/6,C=Math.ceil(E/y),B=z%y,F=(255/y)*B,G=(255-F),x=Math.round(zthis.settings.historyLimit){this.history.shift()}this.history.push(this.contextImage.getImageData(0,0,this.canvasImage.width,this.canvasImage.height));this.historyPos=this.history.length-1;this.settings.change()},undo:function(){this.contextImage.clearRect(0,0,this.canvasImage.width,this.canvasImage.height);this.contextImage.putImageData(this.history[--this.historyPos],0,0)},redo:function(){this.contextImage.clearRect(0,0,this.canvasImage.width,this.canvasImage.height);this.contextImage.putImageData(this.history[++this.historyPos],0,0)},reset:function(){this.history=[];this.historyPos=0;this.history.push(this.contextImage.getImageData(0,0,this.canvasImage.width,this.canvasImage.height));this.settings.change()},clear:function(){this.contextImage.fillStyle=this.settings.background;this.contextImage.fillRect(0,0,this.settings.width,this.settings.height);this.history.push(this.contextImage.getImageData(0,0,this.canvasImage.width,this.canvasImage.height));this.settings.change()},loadImage:function(h,f,l,i,k){i=i||false;var j=this,g=a.createElement("img");g.src=h;g.onload=function(){j.contextImage.drawImage(g,f,l);if(i){j.reset()}else{j.history.push(j.contextImage.getImageData(0,0,j.canvasImage.width,j.canvasImage.height));j.settings.change()}k()}},getColorValues:function(g,j){if(j=="rgba"){var i=g||this.settings.fillStyle,h=/rgba\((\d+),(\d+),(\d+),(.*?)\)/.exec(i),f={red:parseInt(h[1]),green:parseInt(h[2]),blue:parseInt(h[3]),alpha:parseFloat(h[4])}}if(j=="rgb"){var h=/rgb\((\d+),(\d+),(\d+)\)/.exec(g),f={red:parseInt(h[1]),green:parseInt(h[2]),blue:parseInt(h[3]),alpha:1}}if(j=="hex"){var h=/#([A-Za-z0-9]{2})([A-Za-z0-9]{2})([A-Za-z0-9]{2})/.exec(g),f={red:h[1],green:h[2],blue:h[3],alpha:1}}return f},convertColorFormat:function(g,l,m){if(l=="rgba"&&m=="hex"){var k=function(r){var q={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,10:"a",11:"b",12:"c",13:"d",14:"e",15:"f"},p=q[Math.floor(r/16)],o=q[r%16],n=p+""+o;return n},f=this.getColorValues(g,"rgba"),j={red:k(f.red),green:k(f.green),blue:k(f.blue)};return"#"+j.red+j.green+j.blue}if(l=="hex"&&m=="rgba"){var i=function(r){var q={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,a:10,b:11,c:12,d:13,e:14,f:15},o=q[r[0]]*16,n=q[r[1]],p=o+n;return p},f=this.getColorValues(g,"hex"),h={red:i(f.red),green:i(f.green),blue:i(f.blue),alpha:f.alpha};return"rgba("+h.red+","+h.green+","+h.blue+","+h.alpha+")"}if(l=="rgb"&&m=="rgba"){var f=this.getColorValues(g,"rgb");return"rgba("+f.red+","+f.green+","+f.blue+","+f.alpha+")"}},setBrushOpacity:function(f){this.settings.opacity=f/100},setBrushColor:function(f){if(f.indexOf("#")>-1){f=this.convertColorFormat(f,"hex","rgba")}else{if(f.indexOf("rgb(")>-1){f=this.convertColorFormat(f,"rgb","rgba")}}this.settings.fillStyle=f},setBrushSize:function(f){this.settings.lineWidth=f;if(f==1){this.settings.density=1}else{this.settings.density=0.66}}};b.Draw=d})(window,window.document,appHelp); 32 | /* 33 | * DrawApp v1.0 34 | * http://draw.koggdal.com/ 35 | * 36 | * Copyright 2010, Johannes Koggdal 37 | * http://koggdal.com/ 38 | */ 39 | (function(ab,h,Q,t){Q(h).bind("touchmove",function(ao){ao.preventDefault()});Q(h).bind("touchstart",function(ao){ao.preventDefault()});var am=new Draw(),V=Q("#app_header"),q=Q("#app_footer"),ai=Q("#header_home .edit"),y=Q("#header_home .login"),l=Q("#home .login_box"),d=Q("#home .login_box #email"),aj=Q("#home .login_box #password"),E=Q("#home .login_box button"),al,C,j=Q("#home .new"),H=Q("#color"),w={wrapper:Q("#colorpicker"),message:Q("#colorpicker .message"),space:Q("#colorpicker .colorspace"),spectrum:Q("#colorpicker .spectrum"),addSwatch:Q("#colorpicker .add-swatch")},c=Q("#swatches .colorbox").elements,X=[],O={},J,z={wrapper:Q("#size"),bg:Q("#size .slider-bg"),handle:Q("#size .slider-handle")},g,U={wrapper:Q("#opacity"),bg:Q("#opacity .slider-bg"),handle:Q("#opacity .slider-handle")},A,af=Q(".home"),k=Q(".undo"),an=Q(".redo"),e=Q(".save"),T=Q(".save_box"),ae=0,s=Q(".save_local"),S=Q(".save_cloud"),Y=Q(".save_cancel"),p=Q(".cloud_info .register_button"),ah=Q(".cloud_info .login_button"),D=Q(".cloud_info .back_button"),ac=Q(".cloud_register .back_button"),b=Q(".cloud_register .register_button"),m=Q(".cloud_login .back_button"),f=Q(".cloud_login .login_button"),R=false,B=0,P=Q("#home .grid .image"),v=Q("#home .grid"),r=1,Z=[],aa="unsaved",W=false,G,ak,I,N=1,n=Q("#draw .message"),L,ag,a,x=Q("#draw .toolbar_wrapper"),i=Q("#draw .brush_size"),o=false;init=function(){for(var at=0;at1){k.removeClass("disabled");an.addClass("disabled");ae++}else{k.addClass("disabled");an.addClass("disabled");ae=0}},historyLimit:10,hideToolbars:function(){V.hide();q.hide()},showToolbars:function(){V.show();q.show()},toolbars_visible:true});x.get(0).style.height=(ab.innerHeight-52-129)+"px";var ar=function(au){am.setBrushColor(au);H.get(0).style.background=au;H.get(0).style.WebkitBackgroundClip="padding-box"};var ap=w.wrapper.colorPicker({startValue:170,change:function(au){ar("rgb("+au.red+","+au.green+","+au.blue+")")},addSwatch_cb:function(av,aw,az,au){for(var ax=0;ax0){L('Image has changed
');Q("#app_content .message .save_yes").bind("touchclick",function(){ag();W=true;T.animate({bottom:0},200,30)});Q("#app_content .message .save_no").bind("touchclick",function(){ag();F();h.body.setAttribute("id","mode_home")})}else{F();h.body.setAttribute("id","mode_home")}});k.bind("touchclick",function(au){am.undo();if(am.historyPos==0){k.addClass("disabled")}an.removeClass("disabled")});an.bind("touchclick",function(au){am.redo();if(am.historyPos==am.history.length-1){an.addClass("disabled")}k.removeClass("disabled")});var ao,aq;e.bind("touchclick",function(au){T.animate({bottom:0},200,30)});s.bind("touchclick",function(au){T.animate({bottom:-1*parseInt(T.getStyle("height"))},200,30);if(aa=="unsaved"){G()}else{G(N)}L("

Image saved

to browser storage",1500)});S.bind("touchclick",function(av){if(B>0){if(R){return false}R=true;var au=S.get(0).innerHTML;S.addClass("disabled").get(0).innerHTML="Processing...";ak(B,function(){C(B,function(){R=false;S.removeClass("disabled").get(0).innerHTML=au;T.animate({bottom:-1*parseInt(T.getStyle("height"))},200,30);L("

Image saved

to cloud storage",1500)})})}else{T.find(".save_choices").hide();T.find(".cloud_info").show()}});D.bind("touchclick",function(au){T.find(".cloud_info").hide();T.find(".save_choices").show()});p.bind("touchclick",function(au){T.find(".cloud_info").hide();T.find(".cloud_register").show()});ac.bind("touchclick",function(au){T.find(".cloud_register").hide();T.find(".cloud_info").show()});Q(".cloud_register").find("input, .register_button").bind("keypress touchclick click",function(ax){if(ax.type=="click"){ax.preventDefault()}else{if((this.nodeName=="INPUT"&&ax.type=="keypress"&&(ax.keyCode==13||ax.which==13))||(this.nodeName=="BUTTON"&&ax.type!="keypress")){ax.preventDefault();var aB=T.find("label[for=reg_email]").get(0),aA=T.find("label[for=reg_password_1]").get(0),ay=T.find("label[for=reg_password_2]").get(0),au=T.find("#reg_email").get(0),aw=T.find("#reg_password_1").get(0),av=T.find("#reg_password_2").get(0),az=0;aB.innerHTML="E-mail";aA.innerHTML="Password";ay.innerHTML="Password again";if(/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i.test(au.value)===false){aB.innerHTML="E-mail - Incorrect address";az++}if(aw.value.length<6){aA.innerHTML="Password - Too short";az++}if(aw.value!=av.value){ay.innerHTML="Password again - Does not match";az++}if(az==0){if(R){return false}R=true;Q.ajax({url:"../register/",data:"email="+au.value+"&password="+aw.value,type:"POST",success:function(aC){var aD=JSON.parse(aC);if(aD.status=="ok"){ak(aD.user.id,function(){al("save_register",function(aE){T.animate({bottom:-1*parseInt(T.getStyle("height"))},200,30,function(){au.value="";aw.value="";av.value="";T.find(".cloud_register").hide();T.find(".save_choices").show();R=false});Q("#app_header").get(0).scrollIntoView();au.blur();aw.blur();av.blur();L("

Image saved

to cloud storage",1500)})})}else{if(aD.status=="email-exists"){aB.innerHTML="E-mail - Already exists in database";R=false}}}})}}}});ah.bind("touchclick",function(au){T.find(".cloud_info").hide();T.find(".cloud_login").show()});m.bind("touchclick",function(au){T.find(".cloud_login").hide();T.find(".cloud_info").show()});Q(".cloud_login").find("input, .login_button").bind("keypress touchclick click",function(au){if(au.type=="click"){au.preventDefault()}else{if((this.nodeName=="INPUT"&&au.type=="keypress"&&(au.keyCode==13||au.which==13))||(this.nodeName=="BUTTON"&&au.type!="keypress")){au.preventDefault();if(R){return false}R=true;al("save_login",function(av){ak(av,function(){C(av,function(){T.animate({bottom:-1*parseInt(T.getStyle("height"))},200,30,function(){T.find(".cloud_login").hide();T.find(".save_choices").show();R=false});L("

Image saved

to cloud storage",1500)})})})}}});Y.bind("touchclick",function(au){Q(h).unbind("touchstart mousedown",ao);T.unbind("touchstart mousedown",aq);T.animate({bottom:-1*parseInt(T.getStyle("height"))},200,30)});Q(h).bind("touchstart mousedown",function(au){x.hide()});o=true};L=function(ar,aq){var ap=300;if(aq=="no-animation"){ap=0;aq=t}var ao=n.get(0);ao.innerHTML=ar;ao.style.opacity="0";ao.style.display="block";ao.style.left=((ab.innerWidth-parseInt(n.getStyle("width"))-parseInt(n.getStyle("padding-left"))-parseInt(n.getStyle("padding-right")))/2)+"px";if(ap>0){n.animate({opacity:1},ap,30,function(){if(aq!==t){a=setTimeout(function(){ag()},aq)}})}else{ao.style.opacity="1";if(aq!==t){a=setTimeout(function(){ag()},aq)}}};ag=function(){n.animate({opacity:0},500,30,function(){n.get(0).style.display="none"})};I=function(au){var ao=h.createElement("canvas"),ap=ao.getContext("2d"),aq=am.canvas.width>am.canvas.height?am.canvas.height:am.canvas.width,av=Math.abs(am.canvas.width-am.canvas.height)/2,at=am.canvas.width>am.canvas.height?av:0,ar=am.canvas.width>am.canvas.height?0:av;ao.width=88;ao.height=88;ap.drawImage(am.canvasImage,at,ar,aq,aq,0,0,88,88);if(au=="data_url"){return ao.toDataURL()}else{return ao}};G=function(ao){if(ao===t){ao=~~ab.localStorage.getItem("last_id")+1;ab.localStorage.setItem("last_id",ao)}ab.localStorage.setItem("image_"+ao,am.canvasImage.toDataURL());ab.localStorage.setItem("thumb_"+ao,I("data_url"));aa="saved";ae=0;if(W){W=false;F();h.body.setAttribute("id","mode_home")}};ak=function(ao,ap){ap=ap||function(){};Q.ajax({url:"../cloud-save/",data:"user_id="+ao+"&fullsize="+am.canvasImage.toDataURL()+"&thumb="+I("data_url"),type:"POST",success:function(aq){var ar=JSON.parse(aq);aa="saved";ae=0;if(W){W=false;F();h.body.setAttribute("id","mode_home")}ap()}})};ai.bind("touchclick",function(){var ao=ai.get(0);if(ao.innerHTML=="Edit"){ao.innerHTML="Done";y.hide();P.addClass("delete")}else{if(ao.innerHTML=="Done"){ao.innerHTML="Edit";y.show();P.removeClass("delete")}}});y.bind("touchclick",function(){var ao=parseInt(l.getStyle("top"));if(ao<0){if(y.get(0).innerHTML=="Log out"){Q.ajax({url:"../logout/",type:"POST",success:function(){y.get(0).innerHTML="Log in";Z=[];F();B=0;if(Q("#home .grid .active:not(.new)").elements.length==0){ai.hide();P.removeClass("delete")}}})}else{d.get(0).value=ab.localStorage.getItem("login_email");l.animate({top:0},200,30);ai.hide();y.get(0).innerHTML="Cancel"}}else{l.animate({top:-1*parseInt(l.getStyle("height"))},200,30);ai.show();l.find("input").get(0).blur();l.find("input").get(1).blur();y.get(0).innerHTML="Log in"}});var M=false;Q(".login_box input, .cloud_login input").bind("blur",function(){M=false;setTimeout(function(){if(!M){Q("#app_header").get(0).scrollIntoView()}},10)});Q(".login_box input, .cloud_login input").bind("focus",function(){M=true});C=function(ao,ap){ap=ap||function(){};Q.ajax({url:"../cloud-images/",data:"user_id="+ao,type:"GET",success:function(at){var av=JSON.parse(at);if(av.status=="ok"){Z=[];for(var au=0,aq=av.images,ar=aq.length;au0){ai.show()}aw(aA.user.id)})}else{if(aA.status=="wrong-email"){ao.innerHTML="E-mail - Address not found";ao.scrollIntoView(false);if(ap!==false){ap.removeClass("disabled").get(0).innerHTML=ax;au.removeClass("disabled")}}else{if(aA.status=="wrong-password"){aq.innerHTML="Password - Not correct";if(ap!==false){ap.removeClass("disabled").get(0).innerHTML=ax;au.removeClass("disabled")}}}}}})};l.find("input, button").bind("keypress touchclick click",function(ao){if(ao.type=="click"){ao.preventDefault()}else{if((this.nodeName=="INPUT"&&ao.type=="keypress"&&(ao.keyCode==13||ao.which==13))||(this.nodeName=="BUTTON"&&ao.type!="keypress")){ao.preventDefault();al("header")}}});j.bind("touchclick",function(ap){var ao=Q("#stage").get(0);ao.width=ab.innerWidth;ao.height=ab.innerHeight;h.body.setAttribute("id","mode_draw");if(!o){init()}am.clear();am.reset();g(2);A(100);l.animate({top:-1*parseInt(l.getStyle("height"))},200,30);ai.show();aa="unsaved";x.show()});var K=function(aq){if(Q(this).hasClass("new")||!Q(this).hasClass("active")){return false}if(Q(this).hasClass("delete")){var at=this.getAttribute("data-id"),ap=this.getAttribute("data-source");if(ap=="local"){ab.localStorage.removeItem("image_"+at);ab.localStorage.removeItem("thumb_"+at);if(ab.localStorage.getItem("last_id")==at){var ao=this.nextElementSibling.getAttribute("data-id");ab.localStorage.setItem("last_id",ao)}F();if(Q("#home .grid .active:not(.new)").elements.length==0){ai.hide();ai.get(0).innerHTML="Edit";y.show();P.removeClass("delete")}}else{if(ap=="server"){Q.ajax({url:"../cloud-delete/",data:"user_id="+B+"&image_id="+at,type:"POST",success:function(){C(B,function(){F();if(Q("#home .grid .active:not(.new)").elements.length==0){ai.hide();ai.get(0).innerHTML="Edit";y.show();P.removeClass("delete")}})}})}}}else{h.body.setAttribute("id","mode_draw");if(!o){init()}l.animate({top:-1*parseInt(l.getStyle("height"))},200,30);ai.show();L("Loading image...","no-animation");if(this.getAttribute("data-source")=="local"){var at=this.getAttribute("data-id"),ar=ab.localStorage.getItem("image_"+at)}else{if(this.getAttribute("data-source")=="server"){var at=this.getAttribute("data-index"),ar=Z[at].fullsize}}am.clear();am.loadImage(ar,0,0,true,function(){ag()});aa="saved";N=at;x.hide()}};P.bind("touchclick",K);var ad=function(ao,ar,at,aq,ap){ap=ap||(ap==0?0:"");P.elements[ao].style.background=ar.length>0?"url('"+ar+"')":"";P.elements[ao].style.WebkitBackgroundClip="padding-box";if(ar.length>0){Q(P.elements[ao]).addClass("active")}else{Q(P.elements[ao]).removeClass("active")}P.elements[ao].setAttribute("data-id",at);P.elements[ao].setAttribute("data-index",ap);P.elements[ao].setAttribute("data-source",aq)};var u=function(ap,ar,ao){ao=ao||(ao==0?0:"");if(ai.hasClass("hidden")){ai.removeClass("hidden")}if(ap=="local"){var aq=ab.localStorage.getItem("thumb_"+ar);if(aq!==null&&~aq.indexOf("data:")){ad(r,aq,ar,ap);r++}}else{if(ap=="server"){ad(r,Z[ao].thumbnail,ar,ap,ao);r++}}},F=function(){if(Z.length>0){for(var ap=0,ao=Z.length;ap0;ar--){if(P.elements[r]===t){break}u("local",ar)}for(var ap=r,ao=P.elements.length;ap 1 ) { 109 | button_undo.removeClass( 'disabled' ); 110 | button_redo.addClass( 'disabled' ); 111 | historyStatesSinceSave++; 112 | } else { 113 | button_undo.addClass( 'disabled' ); 114 | button_redo.addClass( 'disabled' ); 115 | historyStatesSinceSave = 0; 116 | } 117 | }, 118 | 119 | // Set number of times you can undo 120 | historyLimit: 10, 121 | 122 | // Functions that hide and show the toolbars 123 | hideToolbars: function() { 124 | toolbar_top.hide(); 125 | toolbar_bottom.hide(); 126 | }, 127 | showToolbars: function() { 128 | toolbar_top.show(); 129 | toolbar_bottom.show(); 130 | }, 131 | toolbars_visible: true 132 | }); 133 | 134 | // Set tooltip wrapper height ( window height - header height - footer height ) 135 | tooltipsWrapper.get(0).style.height = ( window.innerHeight - 52 - 129 ) + 'px'; 136 | 137 | // Function to set the brush color (both the current color swatch and the brush itself) 138 | var setColor = function( newColor ) { 139 | draw.setBrushColor( newColor ); 140 | color.get(0).style.background = newColor; 141 | color.get(0).style.WebkitBackgroundClip = 'padding-box'; 142 | }; 143 | 144 | // Set up the color picker 145 | // (it returns a function for updating the color picker) 146 | var colorPickerUpdate = colorpicker.wrapper.colorPicker({ 147 | 148 | // Set a start value for the spectrum (position in pixels) 149 | startValue: 170, 150 | 151 | // Callback fired when the color picker is updated 152 | change: function( color ) { 153 | setColor( "rgb("+color.red+","+color.green+","+color.blue+")" ); 154 | }, 155 | 156 | // Callback fired when hitting the Add Swatch button 157 | addSwatch_cb: function( color, shadingPosition, baseColor, sliderPosition ) { 158 | 159 | // Loop through all swatches 160 | for( var i = 0; i < swatches.length; i++ ) { 161 | 162 | // Find the first empty swatch 163 | if( swatchStatuses[i] === false ) { 164 | 165 | // Set the current color as the background color for this swatch 166 | swatches[i].style.background = 'rgb('+color.red+','+color.green+','+color.blue+')'; 167 | swatches[i].style.WebkitBackgroundClip = 'padding-box'; 168 | 169 | // Set the swatch status 170 | $( swatches[i] ).addClass( 'filled' ); 171 | swatchStatuses[i] = { 172 | color: color, 173 | shadingPosition: shadingPosition, 174 | baseColor: baseColor, 175 | sliderPosition: sliderPosition 176 | }; 177 | 178 | // Cancel the loop 179 | break; 180 | 181 | } 182 | 183 | // If it is the last iteration, and all swatches are filled 184 | else if( i == swatches.length -1 ) { 185 | 186 | // Show message about the swatches 187 | colorpicker.message.show(); 188 | 189 | // Highlight the swatches 190 | for( var n = 0; n < swatches.length; n++ ) 191 | $( swatches[n] ).addClass( 'highlighted' ); 192 | 193 | // Save the color data in a temporary variable for access by other method 194 | tempSwatchStatus = { 195 | color: color, 196 | shadingPosition: shadingPosition, 197 | baseColor: baseColor, 198 | sliderPosition: sliderPosition 199 | }; 200 | 201 | // Function to hide the message 202 | hideColorPickerMessage = function( e ){ 203 | 204 | // Prevent touch devices from trigger the emulated mouse event 205 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 206 | return false; 207 | 208 | // Hide message about the swatches 209 | colorpicker.message.hide(); 210 | 211 | // Remove the highlight on the swatches 212 | for( var n = 0; n < swatches.length; n++ ) 213 | $( swatches[n] ).removeClass( 'highlighted' ); 214 | 215 | // Remove this event handler 216 | $( document ).unbind( 'touchstart mousedown', hideColorPickerMessage ); 217 | }; 218 | 219 | // Add event handler for hiding the message when user taps anywhere on the screen 220 | $( document ).bind( 'touchstart mousedown', hideColorPickerMessage ); 221 | } 222 | } 223 | } 224 | }); 225 | 226 | // Bind event to brush color box 227 | color.bind( 'touchstart mousedown', function( e ) { 228 | 229 | // Prevent touch devices from trigger the emulated mouse event 230 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 231 | return false; 232 | 233 | // Toggle the color picker 234 | if( colorpicker.wrapper.isHidden() ) 235 | colorpicker.wrapper.show(); 236 | else 237 | colorpicker.wrapper.hide(); 238 | }); 239 | 240 | // Bind event to swatch boxes 241 | // Using CSS property pointer-events to disable non-filled swatches 242 | for( var i = 0; i < swatches.length; i++ ) { 243 | (function(n){ 244 | $( swatches[n] ).bind( 'touchstart mousedown', function( e ) { 245 | 246 | // Prevent touch devices from trigger the emulated mouse event 247 | if($.isTouchDevice( e ) && ~e.type.indexOf( 'mouse' )) 248 | return false; 249 | 250 | // If the swatch is highlighted (because all swatches are full) 251 | if( $( swatches[n] ).hasClass( 'highlighted' ) ) { 252 | 253 | // Set the current color as the background color for this swatch 254 | var color = tempSwatchStatus.color; 255 | swatches[n].style.background = 'rgb('+color.red+','+color.green+','+color.blue+')'; 256 | swatches[n].style.WebkitBackgroundClip = 'padding-box'; 257 | 258 | // Update the swatch status 259 | swatchStatuses[n] = tempSwatchStatus; 260 | } 261 | 262 | // If no highlight, update the color 263 | else { 264 | var swatch = swatchStatuses[n]; 265 | colorPickerUpdate( swatch.shadingPosition, swatch.sliderPosition ); 266 | } 267 | }); 268 | })(i); 269 | } 270 | 271 | 272 | // Set up slider for brush size (returns an update method) 273 | slider_size_update = slider_size.wrapper.slider({ 274 | min: 1, 275 | max: 50, 276 | start: 2, 277 | begin: function() { 278 | brushSizeFeedback.show(); 279 | }, 280 | change: function( newSize ) { 281 | 282 | // Set brush size 283 | draw.setBrushSize( newSize ); 284 | 285 | // Show size feedback 286 | var circle = brushSizeFeedback.get(0); 287 | circle.style.width = newSize + 'px'; 288 | circle.style.height = newSize + 'px'; 289 | circle.style.top = ( ( window.innerHeight - 52 - 129 - newSize ) / 2 + 52 ) + 'px'; 290 | circle.style.left = ( ( window.innerWidth - newSize ) / 2 ) + 'px'; 291 | circle.style.background = draw.settings.fillStyle; 292 | }, 293 | end: function() { 294 | brushSizeFeedback.hide(); 295 | } 296 | }); 297 | 298 | // Set up slider for brush opacity (returns an update method) 299 | slider_opacity_update = slider_opacity.wrapper.slider({ 300 | min: 0, 301 | max: 100, 302 | start: 100, 303 | begin: function(){ 304 | brushSizeFeedback.show(); 305 | }, 306 | change: function( newOpacity ) { 307 | draw.setBrushOpacity( newOpacity ); 308 | brushSizeFeedback.get(0).style.background = draw.settings.fillStyle; 309 | brushSizeFeedback.get(0).style.WebkitBackgroundClip = 'padding-box'; 310 | brushSizeFeedback.get(0).style.opacity = draw.settings.opacity; 311 | }, 312 | end: function(){ 313 | brushSizeFeedback.hide(); 314 | }, 315 | }); 316 | 317 | // Bind event handler to home button 318 | button_home.bind( 'touchclick', function( e ) { 319 | 320 | // Hide old message 321 | clearTimeout( messageTimer ); 322 | message.get(0).style.display = 'none'; 323 | message.get(0).style.opacity = '0'; 324 | 325 | // If something is changed 326 | if( historyStatesSinceSave > 0 ) { 327 | 328 | // Show question 329 | showMessage( 'Image has changed
' ); 330 | 331 | // Bind event handler to Save button 332 | $("#app_content .message .save_yes").bind( 'touchclick', function(){ 333 | 334 | // Hide message 335 | hideMessage(); 336 | 337 | // Set variable to let the save box know that the user wants to go Home 338 | saveGoToHome = true; 339 | 340 | // Show the save box 341 | save_box.animate( { bottom: 0 }, 200, 30 ); 342 | }); 343 | 344 | // Bind event handler to Dont save button 345 | $("#app_content .message .save_no").bind( 'touchclick', function(){ 346 | 347 | // Hide message 348 | hideMessage(); 349 | 350 | // Update the thumbnails 351 | setThumbnails(); 352 | 353 | // Set the app mode 354 | document.body.setAttribute( 'id', 'mode_home' ); 355 | }); 356 | } 357 | 358 | // If nothing is changed 359 | else { 360 | // Update the thumbnails 361 | setThumbnails(); 362 | 363 | // Set the app mode 364 | document.body.setAttribute( 'id', 'mode_home' ); 365 | } 366 | }); 367 | 368 | // Bind event handler to undo button 369 | button_undo.bind( 'touchclick', function( e ) { 370 | draw.undo(); 371 | if( draw.historyPos == 0 ) 372 | button_undo.addClass( 'disabled' ); 373 | 374 | button_redo.removeClass( 'disabled' ); 375 | }); 376 | 377 | // Bind event handler to redo button 378 | button_redo.bind( 'touchclick', function( e ) { 379 | draw.redo(); 380 | if( draw.historyPos == draw.history.length-1 ) 381 | button_redo.addClass( 'disabled' ); 382 | 383 | button_undo.removeClass( 'disabled' ); 384 | }); 385 | 386 | var doc_down, box_down; 387 | // Bind event handler to save button 388 | button_save.bind( 'touchclick', function( e ) { 389 | save_box.animate( { bottom: 0 }, 200, 30 ); 390 | }); 391 | 392 | // Bind event handler to local save button 393 | button_save_local.bind( 'touchclick', function( e ) { 394 | 395 | // Hide the box 396 | save_box.animate( { bottom: -1*parseInt(save_box.getStyle( 'height' )) }, 200, 30 ); 397 | 398 | // Save 399 | if( saveStatus == 'unsaved' ) 400 | saveToLocal(); 401 | else 402 | saveToLocal( currentImage ); 403 | 404 | // Show save message 405 | showMessage( '

Image saved

to browser storage', 1500 ); 406 | }); 407 | 408 | // Bind event handler to cloud save button 409 | button_save_cloud.bind( 'touchclick', function( e ) { 410 | 411 | if( cloudUserID > 0 ) { 412 | 413 | if( is_submitting ) 414 | return false; 415 | 416 | is_submitting = true; 417 | 418 | // Set new button text 419 | var oldText = button_save_cloud.get(0).innerHTML; 420 | button_save_cloud.addClass( 'disabled' ).get(0).innerHTML = 'Processing...'; 421 | 422 | 423 | // Save image to app server 424 | saveToCloud( cloudUserID, function() { 425 | 426 | getServerImages( cloudUserID, function() { 427 | 428 | is_submitting = false; 429 | 430 | // Reset the button text 431 | button_save_cloud.removeClass( 'disabled' ).get(0).innerHTML = oldText; 432 | 433 | // Hide the save box 434 | save_box.animate( { bottom: -1*parseInt(save_box.getStyle( 'height' )) }, 200, 30 ); 435 | 436 | // Show save message 437 | showMessage( '

Image saved

to cloud storage', 1500 ); 438 | }); 439 | }); 440 | } else { 441 | 442 | // Switch view 443 | save_box.find(".save_choices").hide(); 444 | save_box.find(".cloud_info").show(); 445 | } 446 | }); 447 | 448 | // Bind event handler to the back button in the registration form 449 | button_cloud_back.bind( 'touchclick', function( e ) { 450 | 451 | save_box.find(".cloud_info").hide(); 452 | save_box.find(".save_choices").show(); 453 | }); 454 | 455 | // Bind event handler to the back button in the registration form 456 | button_cloud_register.bind( 'touchclick', function( e ) { 457 | 458 | save_box.find(".cloud_info").hide(); 459 | save_box.find(".cloud_register").show(); 460 | }); 461 | 462 | // Bind event handler to the back button in the registration form 463 | button_reg_back.bind( 'touchclick', function( e ) { 464 | 465 | save_box.find(".cloud_register").hide(); 466 | save_box.find(".cloud_info").show(); 467 | }); 468 | 469 | 470 | // Handle how the login form is submitted 471 | $(".cloud_register").find("input, .register_button").bind( 'keypress touchclick click', function( e ) { 472 | 473 | // Prevent click from submitting the form (special event touchclick takes care of submitting) 474 | if( e.type == 'click' ) { 475 | e.preventDefault(); 476 | } else 477 | 478 | if( // Enter key in input field 479 | ( this.nodeName == 'INPUT' && e.type == 'keypress' && ( e.keyCode == 13 || e.which == 13 ) ) 480 | || 481 | // Click / tap on button 482 | ( this.nodeName == 'BUTTON' && e.type != 'keypress' ) 483 | ) { 484 | 485 | // Prevent form from being submitted 486 | e.preventDefault(); 487 | 488 | // Get elements 489 | var label_email = save_box.find("label[for=reg_email]").get(0), 490 | label_pass_1 = save_box.find("label[for=reg_password_1]").get(0), 491 | label_pass_2 = save_box.find("label[for=reg_password_2]").get(0), 492 | email = save_box.find("#reg_email").get(0), 493 | pass_1 = save_box.find("#reg_password_1").get(0), 494 | pass_2 = save_box.find("#reg_password_2").get(0), 495 | 496 | errors = 0; 497 | 498 | // Reset labels 499 | label_email.innerHTML = 'E-mail'; 500 | label_pass_1.innerHTML = 'Password'; 501 | label_pass_2.innerHTML = 'Password again'; 502 | 503 | // Validate e-mail 504 | if( /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i.test( email.value ) === false ) { 505 | label_email.innerHTML = 'E-mail - Incorrect address'; 506 | errors++; 507 | } 508 | 509 | // Validate password length 510 | if( pass_1.value.length < 6 ) { 511 | label_pass_1.innerHTML = 'Password - Too short'; 512 | errors++; 513 | } 514 | 515 | // Validate if passwords match 516 | if( pass_1.value != pass_2.value ) { 517 | label_pass_2.innerHTML = 'Password again - Does not match'; 518 | errors++; 519 | } 520 | 521 | // Go on with registration if no errors occured 522 | if( errors == 0 ) { 523 | 524 | // Prevent double submission 525 | if( is_submitting ) 526 | return false; 527 | 528 | is_submitting = true; 529 | 530 | $.ajax({ 531 | url: '../register/', 532 | data: 'email='+email.value+'&password='+pass_1.value, 533 | type: 'POST', 534 | success: function( response ){ 535 | var data = JSON.parse( response ); 536 | 537 | // User was registered successfully 538 | if( data.status == "ok" ) { 539 | 540 | // Save 541 | saveToCloud( data.user.id, function(){ 542 | 543 | // Login 544 | doLogin( 'save_register', function( userID ) { 545 | 546 | // Hide the save box 547 | save_box.animate( { bottom: -1*parseInt(save_box.getStyle( 'height' )) }, 200, 30, function() { 548 | email.value = ''; 549 | pass_1.value = ''; 550 | pass_2.value = ''; 551 | save_box.find(".cloud_register").hide(); 552 | save_box.find(".save_choices").show(); 553 | is_submitting = false; 554 | }); 555 | 556 | // Scroll to the top 557 | $("#app_header").get(0).scrollIntoView(); 558 | 559 | // Remove focus from input fields (remove keyboard on iOS) 560 | email.blur(); 561 | pass_1.blur(); 562 | pass_2.blur(); 563 | 564 | // Show save message 565 | showMessage( '

Image saved

to cloud storage', 1500 ); 566 | }); 567 | 568 | }); 569 | } 570 | 571 | // The e-mail already exists in the database 572 | else if( data.status == "email-exists" ) { 573 | 574 | label_email.innerHTML = 'E-mail - Already exists in database'; 575 | is_submitting = false; 576 | } 577 | } 578 | }); 579 | } 580 | } 581 | }); 582 | 583 | // Bind event handler to the back button in the registration form 584 | button_cloud_login.bind( 'touchclick', function( e ) { 585 | 586 | save_box.find(".cloud_info").hide(); 587 | save_box.find(".cloud_login").show(); 588 | }); 589 | 590 | // Bind event handler to the back button in the registration form 591 | button_login_back.bind( 'touchclick', function( e ) { 592 | 593 | save_box.find(".cloud_login").hide(); 594 | save_box.find(".cloud_info").show(); 595 | }); 596 | 597 | // Handle how the login form is submitted 598 | $(".cloud_login").find("input, .login_button").bind( 'keypress touchclick click', function( e ) { 599 | 600 | // Prevent click from submitting the form (special event touchclick takes care of submitting) 601 | if( e.type == 'click' ) { 602 | e.preventDefault(); 603 | } else 604 | 605 | if( // Enter key in input field 606 | ( this.nodeName == 'INPUT' && e.type == 'keypress' && ( e.keyCode == 13 || e.which == 13 ) ) 607 | || 608 | // Click / tap on button 609 | ( this.nodeName == 'BUTTON' && e.type != 'keypress' ) 610 | ) { 611 | 612 | // Prevent form from being submitted 613 | e.preventDefault(); 614 | 615 | // Prevent double submission 616 | if( is_submitting ) 617 | return false; 618 | 619 | is_submitting = true; 620 | 621 | // Login 622 | doLogin( 'save_login', function( userID ) { 623 | 624 | saveToCloud( userID, function(){ 625 | getServerImages( userID, function() { 626 | 627 | // Hide the save box 628 | save_box.animate( { bottom: -1*parseInt(save_box.getStyle( 'height' )) }, 200, 30, function() { 629 | save_box.find(".cloud_login").hide(); 630 | save_box.find(".save_choices").show(); 631 | is_submitting = false; 632 | }); 633 | 634 | // Show save message 635 | showMessage( '

Image saved

to cloud storage', 1500 ); 636 | }); 637 | }); 638 | }); 639 | } 640 | }); 641 | 642 | // Bind event handler to cancel save button 643 | button_save_cancel.bind( 'touchclick', function( e ) { 644 | $( document ).unbind( 'touchstart mousedown', doc_down ); 645 | save_box.unbind( 'touchstart mousedown', box_down ); 646 | save_box.animate( { bottom: -1*parseInt(save_box.getStyle( 'height' )) }, 200, 30 ); 647 | }); 648 | 649 | // Bind event handler to document to be able to hide tooltips on tap 650 | $( document ).bind( 'touchstart mousedown', function( e ) { 651 | 652 | // Hide the tooltips 653 | tooltipsWrapper.hide(); 654 | }); 655 | 656 | initialized = true; 657 | }; 658 | 659 | 660 | // Function to show a message for a short while 661 | showMessage = function( text, duration ){ 662 | 663 | var fade_time = 300; 664 | 665 | if( duration == 'no-animation' ) { 666 | fade_time = 0; 667 | duration = undefined; 668 | } 669 | 670 | // Get the element 671 | var elem = message.get(0); 672 | 673 | // Set the text 674 | elem.innerHTML = text; 675 | 676 | // Set display to block to enable the message 677 | elem.style.opacity = '0'; 678 | elem.style.display = 'block'; 679 | 680 | // Set the position 681 | elem.style.left = ( 682 | ( 683 | window.innerWidth - parseInt( message.getStyle( 'width' ) ) // window width - message width 684 | - 685 | parseInt( message.getStyle( 'padding-left' ) ) // - padding-left 686 | - 687 | parseInt( message.getStyle( 'padding-right' ) ) // - padding-right 688 | ) / 2 // Divide the total space to the sides in two to calculate the left position 689 | )+'px'; 690 | 691 | if( fade_time > 0 ) { 692 | // Animate the opacity to 1 (fully visible) 693 | message.animate( { opacity: 1 }, fade_time, 30, function() { 694 | 695 | if( duration !== undefined ) { 696 | // Start the timer when the message is fully visible 697 | messageTimer = setTimeout( function() { 698 | hideMessage(); 699 | }, duration ); 700 | } 701 | }); 702 | } else { 703 | elem.style.opacity = '1'; 704 | if( duration !== undefined ) { 705 | // Start the timer when the message is fully visible 706 | messageTimer = setTimeout( function() { 707 | hideMessage(); 708 | }, duration ); 709 | } 710 | } 711 | }; 712 | 713 | // Function to hide a message if no duration was passed 714 | hideMessage = function() { 715 | 716 | // Animate the opacity to 0 (invisible) 717 | message.animate( { opacity: 0 }, 500, 30, function() { 718 | 719 | // Set display to none to disable the message 720 | message.get(0).style.display = 'none'; 721 | }); 722 | }; 723 | 724 | // Function to create a thumbnail of the current image 725 | createThumbnail = function( format ) { 726 | 727 | // Create the temp canvas 728 | var tmpCanvas = document.createElement('canvas'), 729 | tmpContext = tmpCanvas.getContext('2d'), 730 | 731 | // Get dimensions 732 | size = draw.canvas.width > draw.canvas.height ? draw.canvas.height : draw.canvas.width, 733 | offset = Math.abs( draw.canvas.width - draw.canvas.height ) / 2, 734 | offset_x = draw.canvas.width > draw.canvas.height ? offset : 0, 735 | offset_y = draw.canvas.width > draw.canvas.height ? 0 : offset; 736 | 737 | // Set thumb size 738 | tmpCanvas.width = 88; 739 | tmpCanvas.height = 88; 740 | 741 | // Create the thumbnail 742 | tmpContext.drawImage( draw.canvasImage, offset_x, offset_y, size, size, 0, 0, 88, 88 ); 743 | 744 | if( format == "data_url" ) 745 | return tmpCanvas.toDataURL(); 746 | else 747 | return tmpCanvas; 748 | }; 749 | 750 | // Save image to local storage 751 | saveToLocal = function( id ) { 752 | 753 | // Get ID from local storage if not present as argument 754 | if( id === undefined ) { 755 | id = ~~window.localStorage.getItem( 'last_id' )+1; 756 | window.localStorage.setItem( 'last_id', id ); 757 | } 758 | 759 | // Save to local storage 760 | window.localStorage.setItem( 'image_' + id, draw.canvasImage.toDataURL() ); 761 | window.localStorage.setItem( 'thumb_' + id, createThumbnail( 'data_url' ) ); 762 | 763 | 764 | // Set save status 765 | saveStatus = 'saved'; 766 | 767 | // Reset number of history states since save 768 | historyStatesSinceSave = 0; 769 | 770 | if( saveGoToHome ) { 771 | 772 | // Reset var 773 | saveGoToHome = false; 774 | 775 | // Update the thumbnails 776 | setThumbnails(); 777 | 778 | // Set the app mode 779 | document.body.setAttribute( 'id', 'mode_home' ); 780 | } 781 | }; 782 | 783 | // Save image to app server 784 | saveToCloud = function( user_id, callback ) { 785 | 786 | callback = callback || function(){}; 787 | 788 | $.ajax({ 789 | url: '../cloud-save/', 790 | data: 'user_id='+user_id+'&fullsize='+draw.canvasImage.toDataURL()+'&thumb='+createThumbnail( 'data_url' ), 791 | type: 'POST', 792 | success: function( response ){ 793 | var data = JSON.parse( response ); 794 | 795 | // Set save status 796 | saveStatus = 'saved'; 797 | 798 | // Reset number of history states since save 799 | historyStatesSinceSave = 0; 800 | 801 | if( saveGoToHome ) { 802 | 803 | // Reset var 804 | saveGoToHome = false; 805 | 806 | // Update the thumbnails 807 | setThumbnails(); 808 | 809 | // Set the app mode 810 | document.body.setAttribute( 'id', 'mode_home' ); 811 | } 812 | 813 | callback(); 814 | } 815 | }); 816 | }; 817 | 818 | 819 | // Bind event handler to the edit button 820 | editButton.bind( 'touchclick', function() { 821 | var btn = editButton.get(0); 822 | 823 | if( btn.innerHTML == 'Edit' ) { 824 | 825 | // Change button state 826 | btn.innerHTML = 'Done'; 827 | 828 | // Hide login button 829 | loginButton.hide(); 830 | 831 | // Set delete icon 832 | thumbs.addClass( 'delete' ); 833 | } else 834 | 835 | if( btn.innerHTML == 'Done' ) { 836 | 837 | // Change button state 838 | btn.innerHTML = 'Edit'; 839 | 840 | // Show login button 841 | loginButton.show(); 842 | 843 | // Set delete icon 844 | thumbs.removeClass( 'delete' ); 845 | } 846 | }); 847 | 848 | // Bind event handler to the login button 849 | loginButton.bind( 'touchclick', function() { 850 | var top = parseInt( loginBox.getStyle( 'top' ) ); 851 | if( top < 0 ){ 852 | 853 | if( loginButton.get(0).innerHTML == 'Log out' ) { 854 | 855 | $.ajax({ 856 | url: '../logout/', 857 | type: 'POST', 858 | success: function(){ 859 | loginButton.get(0).innerHTML = 'Log in'; 860 | serverImages = []; 861 | setThumbnails(); 862 | cloudUserID = 0; 863 | 864 | if( $("#home .grid .active:not(.new)").elements.length == 0 ) { 865 | editButton.hide(); 866 | thumbs.removeClass( 'delete' ); 867 | } 868 | } 869 | }); 870 | 871 | } else { 872 | 873 | // Prefill the email field if it is already saved 874 | loginEmail.get(0).value = window.localStorage.getItem( 'login_email' ); 875 | 876 | // Show box 877 | loginBox.animate( { top: 0 }, 200, 30 ); 878 | 879 | // Hide edit button 880 | editButton.hide(); 881 | 882 | // Set button text 883 | loginButton.get(0).innerHTML = "Cancel"; 884 | } 885 | 886 | } else { 887 | 888 | // Hide box 889 | loginBox.animate( { top: -1*parseInt( loginBox.getStyle( 'height' )) }, 200, 30 ); 890 | 891 | // Show edit button 892 | editButton.show(); 893 | 894 | // Remove focus from input fields 895 | loginBox.find("input").get(0).blur(); 896 | loginBox.find("input").get(1).blur(); 897 | 898 | // Set button text 899 | loginButton.get(0).innerHTML = "Log in"; 900 | } 901 | }); 902 | 903 | var stopScroll = false; 904 | 905 | // Scroll to the top when password box loses focus (iOS scrolls when input fields are focused) 906 | $(".login_box input, .cloud_login input").bind( 'blur', function() { 907 | stopScroll = false; 908 | setTimeout(function(){ 909 | if( !stopScroll ) { 910 | $("#app_header").get(0).scrollIntoView(); 911 | } 912 | },10); 913 | }); 914 | $(".login_box input, .cloud_login input").bind( 'focus', function() { 915 | stopScroll = true; 916 | }); 917 | 918 | getServerImages = function( userID, callback ) { 919 | 920 | callback = callback || function(){}; 921 | 922 | // Send AJAX request to server to fetch images 923 | $.ajax({ 924 | url: '../cloud-images/', 925 | data: 'user_id='+userID, 926 | type: 'GET', 927 | success: function( response ) { 928 | var data = JSON.parse( response ); 929 | 930 | // Images are found 931 | if( data.status == "ok" ) { 932 | 933 | serverImages = []; 934 | 935 | // Loop images 936 | for( var i = 0, images = data.images, l = images.length; i < l; i++ ) { 937 | serverImages.push( images[ i ] ); 938 | } 939 | setThumbnails(); 940 | 941 | callback(); 942 | } 943 | } 944 | }); 945 | }; 946 | 947 | // Function that handles the login procedure 948 | doLogin = function( origin, callback ) { 949 | 950 | callback = callback || function(){}; 951 | 952 | var label_email = ( origin == "save_login" ) ? save_box.find("label[for=login_email]").get(0) : ( ( origin == "save_register" ) ? save_box.find("label[for=reg_email]").get(0) : loginBox.find("label[for=email]").get(0) ), 953 | label_password = ( origin == "save_login" ) ? save_box.find("label[for=login_password]").get(0) : ( ( origin == "save_register" ) ? save_box.find("label[for=reg_password_1]").get(0) : loginBox.find("label[for=password]").get(0) ), 954 | email = ( origin == "save_login" ) ? save_box.find("#login_email").get(0) : ( ( origin == "save_register" ) ? save_box.find("#reg_email").get(0) : loginEmail.get(0) ), 955 | password = ( origin == "save_login" ) ? save_box.find("#login_password").get(0) : ( ( origin == "save_register" ) ? save_box.find("#reg_password_1").get(0) : loginPass.get(0) ), 956 | login_button = ( origin == "save_login" ) ? button_login_login : ( ( origin == "save_register" ) ? button_reg_register : false ), 957 | back_button = ( origin == "save_login" ) ? button_login_back : ( ( origin == "save_register" ) ? button_reg_back : false ); 958 | 959 | // Set button status 960 | if( login_button !== false ){ 961 | var oldText = login_button.get(0).innerHTML; 962 | login_button.addClass( 'disabled' ).get(0).innerHTML = 'Processing...'; 963 | back_button.addClass( 'disabled' ); 964 | } 965 | 966 | // Send AJAX request to server to try to login 967 | $.ajax({ 968 | url: '../login/', 969 | data: 'email='+email.value+'&password='+password.value, 970 | type: 'POST', 971 | success: function( response ) { 972 | var data = JSON.parse( response ), 973 | userID = data.user ? data.user.id : 0; 974 | 975 | // Reset labels 976 | label_email.innerHTML = 'E-mail'; 977 | label_password.innerHTML = 'Password'; 978 | 979 | // Login was correct 980 | if( data.status == "ok" ) { 981 | 982 | // Save email in local storage to remember it for next app start 983 | window.localStorage.setItem( 'login_email', data.user.email ); 984 | 985 | // Set button text 986 | loginButton.get(0).innerHTML = "Log out"; 987 | 988 | // Set user ID 989 | cloudUserID = data.user.id; 990 | 991 | if( origin == "header" ) { 992 | 993 | // Hide box 994 | loginBox.animate( { top: -1*parseInt( loginBox.getStyle( 'height' )) }, 200, 30 ); 995 | } 996 | 997 | // Empty the login fields 998 | password.value = ''; 999 | 1000 | // Set focus 1001 | email.blur(); 1002 | password.blur(); 1003 | 1004 | // Get thumbnails 1005 | getServerImages( data.user.id, function() { 1006 | 1007 | // Set button status 1008 | if( login_button !== false ){ 1009 | login_button.removeClass( 'disabled' ).get(0).innerHTML = oldText; 1010 | back_button.removeClass( 'disabled' ); 1011 | } 1012 | 1013 | // Show edit button 1014 | if( $("#home .grid .active:not(.new)").elements.length > 0 ) { 1015 | editButton.show(); 1016 | } 1017 | 1018 | callback( data.user.id ); 1019 | }); 1020 | } 1021 | 1022 | // E-mail failed 1023 | else if( data.status == "wrong-email" ) { 1024 | label_email.innerHTML = 'E-mail - Address not found'; 1025 | label_email.scrollIntoView(false); 1026 | 1027 | // Set button status 1028 | if( login_button !== false ){ 1029 | login_button.removeClass( 'disabled' ).get(0).innerHTML = oldText; 1030 | back_button.removeClass( 'disabled' ); 1031 | } 1032 | } 1033 | 1034 | // Password failed 1035 | else if( data.status == "wrong-password" ) { 1036 | label_password.innerHTML = 'Password - Not correct'; 1037 | 1038 | // Set button status 1039 | if( login_button !== false ){ 1040 | login_button.removeClass( 'disabled' ).get(0).innerHTML = oldText; 1041 | back_button.removeClass( 'disabled' ); 1042 | } 1043 | } 1044 | } 1045 | }); 1046 | }; 1047 | 1048 | // Handle how the login form is submitted 1049 | loginBox.find("input, button").bind( 'keypress touchclick click', function( e ) { 1050 | 1051 | // Prevent click from submitting the form 1052 | if( e.type == 'click' ) { 1053 | e.preventDefault(); 1054 | } 1055 | 1056 | else 1057 | 1058 | 1059 | if( // Enter key in input field 1060 | ( this.nodeName == 'INPUT' && e.type == 'keypress' && ( e.keyCode == 13 || e.which == 13 ) ) 1061 | || 1062 | // Click / tap on button 1063 | ( this.nodeName == 'BUTTON' && e.type != 'keypress' ) 1064 | ) { 1065 | 1066 | // Prevent form from being submitted 1067 | e.preventDefault(); 1068 | 1069 | // Login 1070 | doLogin( 'header' ); 1071 | } 1072 | }); 1073 | 1074 | 1075 | // Bind event handler to the new image button 1076 | newImage.bind( 'touchclick', function( e ) { 1077 | 1078 | // Set canvas to full window size 1079 | var canvas = $("#stage").get(0); 1080 | canvas.width = window.innerWidth; 1081 | canvas.height = window.innerHeight; 1082 | 1083 | // Set app mode to draw 1084 | document.body.setAttribute( 'id', 'mode_draw' ); 1085 | 1086 | // Initialize the draw mode if not done already 1087 | if( !initialized ) 1088 | init(); 1089 | 1090 | // Clear the canvas 1091 | draw.clear(); 1092 | draw.reset(); 1093 | 1094 | // Reset sliders 1095 | slider_size_update( 2 ); 1096 | slider_opacity_update( 100 ); 1097 | 1098 | // Hide login box if it is visible 1099 | loginBox.animate( { top: -1*parseInt( loginBox.getStyle( 'height' )) }, 200, 30 ); 1100 | editButton.show(); 1101 | 1102 | // Set save status 1103 | saveStatus = 'unsaved'; 1104 | 1105 | // Show the tooltips 1106 | tooltipsWrapper.show(); 1107 | }); 1108 | 1109 | 1110 | // Function triggered when a thumb is clicked 1111 | var thumb_click = function( e ) { 1112 | 1113 | // Filter out the new button and empty cells 1114 | if( $( this ).hasClass( 'new' ) || !$( this ).hasClass( 'active' ) ) 1115 | return false; 1116 | 1117 | // Delete 1118 | if( $( this ).hasClass( 'delete' ) ) { 1119 | 1120 | // Get id 1121 | var id = this.getAttribute( "data-id" ), 1122 | source = this.getAttribute( "data-source" ); 1123 | 1124 | // Remove image data from local storage 1125 | if( source == "local" ) { 1126 | window.localStorage.removeItem('image_'+id); 1127 | window.localStorage.removeItem('thumb_'+id); 1128 | 1129 | // Reset last image id if this is the last image 1130 | if( window.localStorage.getItem( 'last_id' ) == id ){ 1131 | var nextID = this.nextElementSibling.getAttribute( "data-id" ); 1132 | window.localStorage.setItem( 'last_id', nextID ); 1133 | } 1134 | 1135 | setThumbnails(); 1136 | 1137 | // Set edit button visibility and remove delete icon 1138 | if( $("#home .grid .active:not(.new)").elements.length == 0 ) { 1139 | editButton.hide(); 1140 | editButton.get(0).innerHTML = 'Edit'; 1141 | loginButton.show(); 1142 | thumbs.removeClass( 'delete' ); 1143 | } 1144 | } 1145 | 1146 | // Remove image data from local storage 1147 | else if( source == "server" ) { 1148 | $.ajax({ 1149 | url: '../cloud-delete/', 1150 | data: 'user_id='+cloudUserID+'&image_id='+id, 1151 | type: 'POST', 1152 | success: function() { 1153 | getServerImages( cloudUserID, function(){ 1154 | setThumbnails(); 1155 | 1156 | // Set edit button visibility 1157 | if( $("#home .grid .active:not(.new)").elements.length == 0 ) { 1158 | editButton.hide(); 1159 | editButton.get(0).innerHTML = 'Edit'; 1160 | loginButton.show(); 1161 | thumbs.removeClass( 'delete' ); 1162 | } 1163 | }); 1164 | } 1165 | }); 1166 | } 1167 | } else { 1168 | 1169 | // Set app mode to draw 1170 | document.body.setAttribute( 'id', 'mode_draw' ); 1171 | 1172 | // Initialize the draw mode if not done already 1173 | if( !initialized ) 1174 | init(); 1175 | 1176 | // Hide login box if it is visible 1177 | loginBox.animate( { top: -1*parseInt( loginBox.getStyle( 'height' )) }, 200, 30 ); 1178 | editButton.show(); 1179 | 1180 | // Show loading message 1181 | showMessage( 'Loading image...', 'no-animation' ); 1182 | 1183 | // Get image data from local storage 1184 | if( this.getAttribute( "data-source" ) == "local" ) { 1185 | // Get image data 1186 | var id = this.getAttribute( "data-id" ), 1187 | dataURL = window.localStorage.getItem('image_'+id); 1188 | } 1189 | 1190 | // Get image data from the server variable 1191 | else if( this.getAttribute( "data-source" ) == "server" ) { 1192 | var id = this.getAttribute( "data-index" ), 1193 | dataURL = serverImages[ id ].fullsize; 1194 | } 1195 | 1196 | // Open the image 1197 | draw.clear(); 1198 | draw.loadImage( dataURL, 0, 0, true, function(){ 1199 | hideMessage(); 1200 | }); 1201 | 1202 | // Set save status 1203 | saveStatus = 'saved'; 1204 | 1205 | // Set current image 1206 | currentImage = id; 1207 | 1208 | // Hide the tooltips 1209 | tooltipsWrapper.hide(); 1210 | } 1211 | }; 1212 | 1213 | // Bind event handler to the thumbnails 1214 | thumbs.bind( 'touchclick', thumb_click ); 1215 | 1216 | 1217 | // Function to set the new thumbnail style 1218 | var setThumbnailStyle = function( index, dataURL, id, type, i ) { 1219 | 1220 | i = i || ( i == 0 ? 0 : ''); 1221 | 1222 | // Set the background of the thumbnail box 1223 | thumbs.elements[ index ].style.background = dataURL.length > 0 ? "url('"+dataURL+"')" : ''; 1224 | thumbs.elements[ index ].style.WebkitBackgroundClip = 'padding-box'; 1225 | if( dataURL.length > 0 ) 1226 | $( thumbs.elements[ index ] ).addClass( "active" ); 1227 | else 1228 | $( thumbs.elements[ index ] ).removeClass( "active" ); 1229 | thumbs.elements[ index ].setAttribute( "data-id", id ); 1230 | thumbs.elements[ index ].setAttribute( "data-index", i ); 1231 | thumbs.elements[ index ].setAttribute( "data-source", type ); 1232 | }; 1233 | 1234 | 1235 | // Function to set thumbnail 1236 | var setThumbnail = function( type, id, index ) { 1237 | 1238 | index = index || ( index == 0 ? 0 : '' ); 1239 | 1240 | // Set edit button visibility 1241 | if( editButton.hasClass( 'hidden' ) ) 1242 | editButton.removeClass( 'hidden' ); 1243 | 1244 | // Get image from local storage 1245 | if( type == "local" ) { 1246 | 1247 | // Get the base64 encoded data url from the local storage 1248 | var dataURL = window.localStorage.getItem( 'thumb_' + id ); 1249 | 1250 | // If it is set and actually is a data url 1251 | if( dataURL !== null && ~dataURL.indexOf( 'data:' ) ) { 1252 | setThumbnailStyle( currentThumb, dataURL, id, type ); 1253 | 1254 | // Increment box index 1255 | currentThumb++; 1256 | } 1257 | } 1258 | 1259 | // Get image from server 1260 | else if ( type == "server" ) { 1261 | 1262 | // Set the thumbnail 1263 | setThumbnailStyle( currentThumb, serverImages[ index ].thumbnail, id, type, index ); 1264 | 1265 | // Increment box index 1266 | currentThumb++; 1267 | } 1268 | }, 1269 | 1270 | // Function to set all thumbnails 1271 | setThumbnails = function() { 1272 | 1273 | if( serverImages.length > 0 ) { 1274 | 1275 | // Loop through all server fetched images 1276 | for( var i = 0, l = serverImages.length; i < l; i++ ) { 1277 | 1278 | // Abort loop if there are no more boxes to fill 1279 | if( thumbs.elements[ currentThumb ] === undefined ) 1280 | break; 1281 | 1282 | // Set this thumbnail 1283 | setThumbnail( 'server', serverImages[ i ].id, i ); 1284 | } 1285 | } 1286 | 1287 | // Get last added ID 1288 | var last_id = ~~window.localStorage.getItem( 'last_id' ); 1289 | 1290 | // Loop through all IDs lower than the latest 1291 | for( var id = last_id; id > 0; id-- ) { 1292 | 1293 | // Abort loop if there are no more boxes to fill 1294 | if( thumbs.elements[ currentThumb ] === undefined ) 1295 | break; 1296 | 1297 | // Set this thumbnail 1298 | setThumbnail( 'local', id ); 1299 | } 1300 | 1301 | // Clear unused thumbnails 1302 | for( var i = currentThumb, l = thumbs.elements.length; i < l; i++ ) { 1303 | setThumbnailStyle( i, '', '', '' ); 1304 | } 1305 | 1306 | // Reset 1307 | currentThumb = 1; 1308 | }; 1309 | 1310 | setThumbnails(); 1311 | 1312 | })( window, window.document, appHelp ); --------------------------------------------------------------------------------