├── module.suffix ├── module.prefix ├── .bowerrc ├── src ├── icon.png ├── favicon.ico ├── assets │ ├── audio │ │ ├── purr-01.mp3 │ │ ├── yawn-01.mp3 │ │ ├── angry-meow-01.mp3 │ │ ├── angry-meow-02.mp3 │ │ ├── angry-meow-03.mp3 │ │ ├── angry-meow-04.mp3 │ │ ├── excited-meow-01.mp3 │ │ ├── excited-meow-02.mp3 │ │ ├── excited-meow-03.mp3 │ │ ├── excited-purr-01.mp3 │ │ ├── ball-bounce-soft-01.mp3 │ │ ├── ball-bounce-default-01.mp3 │ │ ├── ball-bounce-hollow-01.mp3 │ │ ├── ball-bounce-football-01.mp3 │ │ ├── ball-bounce-pingpong-01.mp3 │ │ ├── ball-bounce-basketball-01.mp3 │ │ ├── ball-bounce-basketball-02.mp3 │ │ └── ball-bounce-bowlingball-01.mp3 │ ├── fonts │ │ ├── icomoon.eot │ │ ├── icomoon.ttf │ │ ├── icomoon.woff │ │ └── Roboto-Regular.woff │ ├── images │ │ ├── ball01.png │ │ ├── ball02.png │ │ ├── ball03.png │ │ ├── ball04.png │ │ ├── ball05.png │ │ ├── ball06.png │ │ ├── ball07.png │ │ ├── ball08.png │ │ ├── ball09.png │ │ ├── ball10.png │ │ ├── ball11.png │ │ ├── ball12.png │ │ ├── ball13.png │ │ ├── cat-big.gif │ │ ├── default-image.gif │ │ ├── draw-guide-body.gif │ │ ├── draw-guide-head.gif │ │ ├── draw-guide-eyes-open.gif │ │ ├── draw-guide-left-leg.gif │ │ ├── draw-guide-mouth-open.gif │ │ ├── draw-guide-right-leg.gif │ │ ├── draw-guide-eyes-closed.gif │ │ └── draw-guide-mouth-closed.gif │ └── README.md ├── app │ ├── cat │ │ ├── services │ │ │ ├── _services.js │ │ │ ├── ratingService.js │ │ │ ├── catSimplifier.js │ │ │ └── emotion.js │ │ ├── directives │ │ │ ├── _directives.js │ │ │ ├── commentsLink.tpl.html │ │ │ ├── facebookButton.js │ │ │ ├── stage.tpl.html │ │ │ ├── commentsLink.js │ │ │ ├── twitterButton.js │ │ │ └── scrollTo.js │ │ ├── cat.less │ │ ├── cat.js │ │ └── cat.tpl.html │ ├── home │ │ ├── filters │ │ │ ├── _filters.js │ │ │ ├── byTag.js │ │ │ └── byTag.spec.js │ │ ├── home.spec.js │ │ ├── directives │ │ │ ├── tagSelector.tpl.html │ │ │ ├── pagination.tpl.html │ │ │ ├── tagSelector.js │ │ │ ├── previewPanel.tpl.html │ │ │ └── previewPanel.js │ │ ├── home.tpl.html │ │ └── home.js │ ├── draw │ │ ├── services │ │ │ ├── _services.js │ │ │ ├── thumbnailGenerator.js │ │ │ ├── drawHelper.js │ │ │ └── catBuilder.js │ │ ├── directives │ │ │ ├── _directives.js │ │ │ ├── canvas.tpl.html │ │ │ ├── drawInstructions.tpl.html │ │ │ ├── saveDialog.js │ │ │ ├── saveDialog.tpl.html │ │ │ └── drawInstructions.js │ │ ├── draw.tpl.html │ │ ├── draw.less │ │ └── draw.js │ ├── app.spec.js │ └── app.js ├── common │ ├── filters │ │ ├── _filters.js │ │ ├── urlFirendlyName.js │ │ ├── startsWith.js │ │ ├── startsWith.spec.js │ │ ├── urlFriendlyName.spec.js │ │ └── timeAgo.js │ ├── directives │ │ ├── _directives.js │ │ ├── modalDialog.tpl.html │ │ ├── infoPanel.tpl.html │ │ ├── modalDialog.js │ │ ├── infoPanel.js │ │ ├── dirDisqus.js │ │ └── touchEvents.js │ └── services │ │ ├── _services.js │ │ ├── userOptions.js │ │ ├── catFactory.spec.js │ │ ├── rafPolyfill.js │ │ ├── behaviourFactory.js │ │ ├── noiseFactory.js │ │ ├── datastore.js │ │ ├── serializer.js │ │ ├── noiseFactory.spec.js │ │ ├── datastore.spec.js │ │ ├── renderHelper.js │ │ ├── ipCookie.js │ │ ├── primitives.spec.js │ │ └── catFactory.js ├── e2e │ ├── protractor.conf.js │ └── tests │ │ └── index.e2e.js ├── less │ ├── variables.less │ ├── README.md │ ├── icons.less │ └── main.less ├── .htaccess └── index.html ├── api ├── .htaccess ├── Slim │ ├── Exception │ │ ├── Stop.php │ │ └── Pass.php │ ├── LogWriter.php │ ├── Http │ │ ├── Cookies.php │ │ └── Headers.php │ ├── Middleware.php │ └── Middleware │ │ ├── MethodOverride.php │ │ └── PrettyExceptions.php ├── Thumbnail.php └── staticPage.php ├── .gitignore ├── bower.json ├── changelog.tpl ├── package.json ├── README.md ├── karma └── karma-unit.tpl.js ├── karma.conf.js └── CHANGELOG.md /module.suffix: -------------------------------------------------------------------------------- 1 | })( window, window.angular ); 2 | -------------------------------------------------------------------------------- /module.prefix: -------------------------------------------------------------------------------- 1 | (function ( window, angular, undefined ) { 2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "vendor", 3 | "json": "bower.json" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/icon.png -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /api/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteRule ^ index.php [QSA,L] -------------------------------------------------------------------------------- /src/assets/audio/purr-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/purr-01.mp3 -------------------------------------------------------------------------------- /src/assets/audio/yawn-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/yawn-01.mp3 -------------------------------------------------------------------------------- /src/assets/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/fonts/icomoon.eot -------------------------------------------------------------------------------- /src/assets/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/fonts/icomoon.ttf -------------------------------------------------------------------------------- /src/assets/images/ball01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball01.png -------------------------------------------------------------------------------- /src/assets/images/ball02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball02.png -------------------------------------------------------------------------------- /src/assets/images/ball03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball03.png -------------------------------------------------------------------------------- /src/assets/images/ball04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball04.png -------------------------------------------------------------------------------- /src/assets/images/ball05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball05.png -------------------------------------------------------------------------------- /src/assets/images/ball06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball06.png -------------------------------------------------------------------------------- /src/assets/images/ball07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball07.png -------------------------------------------------------------------------------- /src/assets/images/ball08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball08.png -------------------------------------------------------------------------------- /src/assets/images/ball09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball09.png -------------------------------------------------------------------------------- /src/assets/images/ball10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball10.png -------------------------------------------------------------------------------- /src/assets/images/ball11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball11.png -------------------------------------------------------------------------------- /src/assets/images/ball12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball12.png -------------------------------------------------------------------------------- /src/assets/images/ball13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/ball13.png -------------------------------------------------------------------------------- /src/assets/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/fonts/icomoon.woff -------------------------------------------------------------------------------- /src/assets/images/cat-big.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/cat-big.gif -------------------------------------------------------------------------------- /src/app/cat/services/_services.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 12/03/14. 3 | */ 4 | angular.module('drawACat.cat.services', []); -------------------------------------------------------------------------------- /src/app/home/filters/_filters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 24/03/14. 3 | */ 4 | angular.module('drawACat.home.filters', []); -------------------------------------------------------------------------------- /src/assets/audio/angry-meow-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/angry-meow-01.mp3 -------------------------------------------------------------------------------- /src/assets/audio/angry-meow-02.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/angry-meow-02.mp3 -------------------------------------------------------------------------------- /src/assets/audio/angry-meow-03.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/angry-meow-03.mp3 -------------------------------------------------------------------------------- /src/assets/audio/angry-meow-04.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/angry-meow-04.mp3 -------------------------------------------------------------------------------- /src/assets/images/default-image.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/default-image.gif -------------------------------------------------------------------------------- /src/common/filters/_filters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 20/03/14. 3 | */ 4 | angular.module('drawACat.common.filters', []); -------------------------------------------------------------------------------- /src/app/cat/directives/_directives.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 13/03/14. 3 | */ 4 | angular.module('drawACat.cat.directives', []); -------------------------------------------------------------------------------- /src/app/draw/services/_services.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 14/03/14. 3 | */ 4 | angular.module('drawACat.draw.services', []); -------------------------------------------------------------------------------- /src/assets/audio/excited-meow-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/excited-meow-01.mp3 -------------------------------------------------------------------------------- /src/assets/audio/excited-meow-02.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/excited-meow-02.mp3 -------------------------------------------------------------------------------- /src/assets/audio/excited-meow-03.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/excited-meow-03.mp3 -------------------------------------------------------------------------------- /src/assets/audio/excited-purr-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/excited-purr-01.mp3 -------------------------------------------------------------------------------- /src/assets/fonts/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/fonts/Roboto-Regular.woff -------------------------------------------------------------------------------- /src/assets/images/draw-guide-body.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/draw-guide-body.gif -------------------------------------------------------------------------------- /src/assets/images/draw-guide-head.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/draw-guide-head.gif -------------------------------------------------------------------------------- /src/assets/audio/ball-bounce-soft-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/ball-bounce-soft-01.mp3 -------------------------------------------------------------------------------- /src/common/directives/_directives.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 17/03/14. 3 | */ 4 | angular.module('drawACat.common.directives', []); -------------------------------------------------------------------------------- /src/app/draw/directives/_directives.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 18/03/14. 3 | */ 4 | angular.module('drawACat.draw.directives', []); 5 | -------------------------------------------------------------------------------- /src/assets/audio/ball-bounce-default-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/ball-bounce-default-01.mp3 -------------------------------------------------------------------------------- /src/assets/audio/ball-bounce-hollow-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/ball-bounce-hollow-01.mp3 -------------------------------------------------------------------------------- /src/assets/images/draw-guide-eyes-open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/draw-guide-eyes-open.gif -------------------------------------------------------------------------------- /src/assets/images/draw-guide-left-leg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/draw-guide-left-leg.gif -------------------------------------------------------------------------------- /src/assets/images/draw-guide-mouth-open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/draw-guide-mouth-open.gif -------------------------------------------------------------------------------- /src/assets/images/draw-guide-right-leg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/draw-guide-right-leg.gif -------------------------------------------------------------------------------- /src/assets/audio/ball-bounce-football-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/ball-bounce-football-01.mp3 -------------------------------------------------------------------------------- /src/assets/audio/ball-bounce-pingpong-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/ball-bounce-pingpong-01.mp3 -------------------------------------------------------------------------------- /src/assets/images/draw-guide-eyes-closed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/draw-guide-eyes-closed.gif -------------------------------------------------------------------------------- /src/assets/images/draw-guide-mouth-closed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/images/draw-guide-mouth-closed.gif -------------------------------------------------------------------------------- /src/assets/audio/ball-bounce-basketball-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/ball-bounce-basketball-01.mp3 -------------------------------------------------------------------------------- /src/assets/audio/ball-bounce-basketball-02.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/ball-bounce-basketball-02.mp3 -------------------------------------------------------------------------------- /src/assets/audio/ball-bounce-bowlingball-01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbromley/drawACatApp/HEAD/src/assets/audio/ball-bounce-bowlingball-01.mp3 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw* 2 | *~ 3 | build/ 4 | bin/ 5 | node_modules/ 6 | vendor/ 7 | api/db.php 8 | api/thumbnails/ 9 | .idea 10 | not_code 11 | src/assets-source -------------------------------------------------------------------------------- /src/assets/README.md: -------------------------------------------------------------------------------- 1 | # The `src/assets` Directory 2 | 3 | There's really not much to say here. Every file in this directory is recursively transferred to `dist/assets/`. 4 | 5 | -------------------------------------------------------------------------------- /src/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 14/03/14. 3 | */ 4 | exports.config = { 5 | specs: [ 6 | './tests/**/*.e2e.js' 7 | ], 8 | 9 | baseUrl: 'http://localhost/GitHub/drawACatApp/build/' 10 | }; -------------------------------------------------------------------------------- /src/less/variables.less: -------------------------------------------------------------------------------- 1 | /** 2 | * These are the variables used throughout the application. This is where 3 | * overwrites that are not specific to components should be maintained. 4 | */ 5 | 6 | @import '../../vendor/bootstrap/less/variables.less'; 7 | 8 | -------------------------------------------------------------------------------- /src/e2e/tests/index.e2e.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 14/03/14. 3 | */ 4 | 5 | describe('index page', function() { 6 | 7 | it('should have the correct title', function() { 8 | browser.get('#'); 9 | expect(browser.getTitle()).toEqual('Draw A Cat!'); 10 | }); 11 | }); -------------------------------------------------------------------------------- /src/app/cat/directives/commentsLink.tpl.html: -------------------------------------------------------------------------------- 1 |
Mouse coordinates: {{ x }}, {{ y }}
10 |In drag mode?: {{ ball.isInDragMode() }}
11 |Happy: {{ moodValue.happy }}
12 |Angry: {{ moodValue.angry }}
13 |Excited: {{ moodValue.excited }}
14 |Bored: {{ moodValue.bored }}
15 |6 | {{ instruction }} 7 |
8 |25 | Draw your own cat using your mouse or finger! Each part of the cat is drawn separately so that it can come to life when you're done. 26 | Follow the instructions below, and use the blueprint as a guide. 27 |
28 |29 | If your hand slips, you can undo the last line you drew by pressing the 30 | "undo" button in the top right of the canvas. 31 |
32 |
33 | When you've drawn all the parts, you'll be able to save your cat and share it with the world. Have fun!
34 |
35 |
45 | 46 |
47 |69 | 72 |73 | 83 | 84 | 85 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.4.2 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Http; 34 | 35 | class Cookies extends \Slim\Helper\Set 36 | { 37 | /** 38 | * Default cookie settings 39 | * @var array 40 | */ 41 | protected $defaults = array( 42 | 'value' => '', 43 | 'domain' => null, 44 | 'path' => null, 45 | 'expires' => null, 46 | 'secure' => false, 47 | 'httponly' => false 48 | ); 49 | 50 | /** 51 | * Set cookie 52 | * 53 | * The second argument may be a single scalar value, in which case 54 | * it will be merged with the default settings and considered the `value` 55 | * of the merged result. 56 | * 57 | * The second argument may also be an array containing any or all of 58 | * the keys shown in the default settings above. This array will be 59 | * merged with the defaults shown above. 60 | * 61 | * @param string $key Cookie name 62 | * @param mixed $value Cookie settings 63 | */ 64 | public function set($key, $value) 65 | { 66 | if (is_array($value)) { 67 | $cookieSettings = array_replace($this->defaults, $value); 68 | } else { 69 | $cookieSettings = array_replace($this->defaults, array('value' => $value)); 70 | } 71 | parent::set($key, $cookieSettings); 72 | } 73 | 74 | /** 75 | * Remove cookie 76 | * 77 | * Unlike \Slim\Helper\Set, this will actually *set* a cookie with 78 | * an expiration date in the past. This expiration date will force 79 | * the client-side cache to remove its cookie with the given name 80 | * and settings. 81 | * 82 | * @param string $key Cookie name 83 | * @param array $settings Optional cookie settings 84 | */ 85 | public function remove($key, $settings = array()) 86 | { 87 | $settings['value'] = ''; 88 | $settings['expires'] = time() - 86400; 89 | $this->set($key, array_replace($this->defaults, $settings)); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/common/services/serializer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 08/03/14. 3 | */ 4 | angular.module('drawACat.common.services') 5 | 6 | .factory('serializer', function(catFactory, primitives, behaviourFactory) { 7 | 8 | return { 9 | /** 10 | * Converts a Cat object into a JSON string containing all of its properties, so it can be 11 | * stored in a datastore. 12 | * @param cat 13 | * @returns {*} 14 | */ 15 | serializeCat: function(cat) { 16 | var serializable = {}; 17 | 18 | angular.forEach(cat.bodyParts, function(bodyPart, key) { 19 | serializable[key] = { 20 | part: {}, 21 | behaviour: {} 22 | }; 23 | if (bodyPart.part) { 24 | serializable[key].part.path = bodyPart.part.getPath(); 25 | serializable[key].part.name = bodyPart.part.getName(); 26 | if (bodyPart.part.getParent()) { 27 | serializable[key].part.parentName = bodyPart.part.getParent().getName(); 28 | } 29 | } 30 | if (bodyPart.behaviour) { 31 | serializable[key].behaviour = bodyPart.behaviour.toSerializable(); 32 | } 33 | }); 34 | 35 | return serializable; 36 | }, 37 | 38 | /** 39 | * Converts a JSON representation of the cat (as stored in the datastore) back into a Cat() object, 40 | * complete with Part() and Behaviour() objects for each bodyPart. 41 | * @param dataObject 42 | * @returns {*} 43 | */ 44 | unserializeCat: function(dataObject) { 45 | var cat = catFactory.newCat(); 46 | 47 | angular.forEach(dataObject, function(bodyPart, key) { 48 | if (bodyPart.part) { 49 | var newPart = primitives.Part(); 50 | 51 | if (bodyPart.part.path) { 52 | newPart.createFromPath(bodyPart.part.name, bodyPart.part.path); 53 | } 54 | cat.bodyParts[key].part = newPart; 55 | } 56 | if (bodyPart.behaviour) { 57 | var newBehaviour = behaviourFactory.newBehaviour(); 58 | newBehaviour.sensitivity = bodyPart.behaviour.sensitivity; 59 | newBehaviour.range = bodyPart.behaviour.range; 60 | newBehaviour.visible = bodyPart.behaviour.visible; 61 | cat.bodyParts[key].behaviour = newBehaviour; 62 | } 63 | }); 64 | 65 | // now we need to loop through the bodyParts once more to resolve the parent/child relationships 66 | angular.forEach(dataObject, function(bodyPart, key) { 67 | if (bodyPart.part) { 68 | if(bodyPart.part.parentName) { 69 | var parentName = bodyPart.part.parentName; 70 | cat.bodyParts[key].part.setParent(cat.bodyParts[parentName].part); 71 | } 72 | } 73 | }); 74 | 75 | return cat; 76 | } 77 | }; 78 | } 79 | ); -------------------------------------------------------------------------------- /src/app/cat/cat.less: -------------------------------------------------------------------------------- 1 | .header { 2 | a:link, a:visited { 3 | color: #999; 4 | } 5 | a:hover, a:active { 6 | color: #666; 7 | text-decoration: none; 8 | } 9 | .embed-site-link { 10 | font-size: 20px; 11 | font-family: @comic-sans; 12 | a:link, a:visited { 13 | color: @highlight-colour; 14 | } 15 | a:hover, a:active { 16 | color: lighten(@highlight-colour, 5%); 17 | } 18 | } 19 | 20 | .embed-panel { 21 | padding: 5px; 22 | } 23 | .control-panel { 24 | position: relative; 25 | z-index: 300; 26 | background-color: #f7f7f7; 27 | border-bottom: 1px solid #eee; 28 | padding-top: 5px; 29 | i { 30 | font-size: 20px; 31 | color: #999; 32 | @media (min-width: @screen-sm-min) { 33 | font-size: 24px; 34 | } 35 | } 36 | button:focus { 37 | outline: none; 38 | color: #555; 39 | } 40 | a.selected i, button.selected i { 41 | color: @highlight-colour; 42 | } 43 | } 44 | .button-bar { 45 | text-align: right; 46 | list-style-type: none; 47 | padding: 0; 48 | li { 49 | display: inline-block; 50 | padding: 0px 3px; 51 | } 52 | } 53 | .comments-link { 54 | width: 40px; 55 | text-align: center; 56 | &.floating { 57 | position: fixed; 58 | z-index: 500; 59 | top: -15px; 60 | padding-top: 20px; 61 | background-color: #f7f7f7; 62 | border-radius: 5px; 63 | -webkit-box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.15); 64 | -moz-box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.15); 65 | box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.15); 66 | } 67 | } 68 | } 69 | 70 | .settings-panel, .embed-panel { 71 | padding: 10px; 72 | z-index: 210; 73 | margin-top: 55px; 74 | background-color: #fff; 75 | h3 { 76 | margin-top: 0px; 77 | } 78 | .render-quality { 79 | padding-top: 45px; 80 | } 81 | } 82 | 83 | .info-panel-container { 84 | position: absolute; 85 | width: 100%; 86 | left: 0; 87 | border-radius: 5px; 88 | background-color: #f7f7f7; 89 | min-width: 300px; 90 | top: -5px; 91 | z-index: 200; 92 | padding: 10px; 93 | -webkit-box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.15); 94 | -moz-box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.15); 95 | box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.15); 96 | transition: top 0.5s; 97 | h1 { 98 | font-family: @comic-sans; 99 | margin: 2px 0px; 100 | } 101 | p { 102 | font-family: @comic-sans; 103 | } 104 | .extended-info { 105 | padding: 5px 0px; 106 | font-size: 18px; 107 | } 108 | ul.share-buttons { 109 | list-style-type: none; 110 | margin: 0; 111 | padding: 0; 112 | li { 113 | display: table-cell; 114 | padding-right: 5px; 115 | max-height: 20px; 116 | overflow: hidden; 117 | } 118 | } 119 | twitter-button iframe { 120 | margin-bottom: -5px; 121 | } 122 | .rate-cat-button { 123 | border: none; 124 | background: none; 125 | } 126 | button.toggle-info-panel { 127 | margin-top: -20px; 128 | border: none; 129 | background: none; 130 | &:focus { 131 | outline: none; 132 | color: #555; 133 | } 134 | } 135 | } 136 | 137 | #stage-container { 138 | height: 100vh; 139 | left: 0; 140 | top: 0; 141 | width: 100vw; 142 | } 143 | canvas { 144 | background-color: #f5f5f5; 145 | } 146 | #stage { 147 | position: absolute; 148 | top: 0; 149 | left: 0; 150 | z-index: 100; 151 | } -------------------------------------------------------------------------------- /api/Slim/Middleware.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.4.2 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim; 34 | 35 | /** 36 | * Middleware 37 | * 38 | * @package Slim 39 | * @author Josh Lockhart 40 | * @since 1.6.0 41 | */ 42 | abstract class Middleware 43 | { 44 | /** 45 | * @var \Slim\Slim Reference to the primary application instance 46 | */ 47 | protected $app; 48 | 49 | /** 50 | * @var mixed Reference to the next downstream middleware 51 | */ 52 | protected $next; 53 | 54 | /** 55 | * Set application 56 | * 57 | * This method injects the primary Slim application instance into 58 | * this middleware. 59 | * 60 | * @param \Slim\Slim $application 61 | */ 62 | final public function setApplication($application) 63 | { 64 | $this->app = $application; 65 | } 66 | 67 | /** 68 | * Get application 69 | * 70 | * This method retrieves the application previously injected 71 | * into this middleware. 72 | * 73 | * @return \Slim\Slim 74 | */ 75 | final public function getApplication() 76 | { 77 | return $this->app; 78 | } 79 | 80 | /** 81 | * Set next middleware 82 | * 83 | * This method injects the next downstream middleware into 84 | * this middleware so that it may optionally be called 85 | * when appropriate. 86 | * 87 | * @param \Slim|\Slim\Middleware 88 | */ 89 | final public function setNextMiddleware($nextMiddleware) 90 | { 91 | $this->next = $nextMiddleware; 92 | } 93 | 94 | /** 95 | * Get next middleware 96 | * 97 | * This method retrieves the next downstream middleware 98 | * previously injected into this middleware. 99 | * 100 | * @return \Slim\Slim|\Slim\Middleware 101 | */ 102 | final public function getNextMiddleware() 103 | { 104 | return $this->next; 105 | } 106 | 107 | /** 108 | * Call 109 | * 110 | * Perform actions specific to this middleware and optionally 111 | * call the next downstream middleware. 112 | */ 113 | abstract public function call(); 114 | } 115 | -------------------------------------------------------------------------------- /src/app/home/home.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module( 'drawACat.home', [ 3 | 'drawACat.home.filters', 4 | 'drawACat.home.tagSelector', 5 | 'drawACat.home.previewPanel', 6 | 'angularUtils.directives.dirPagination', 7 | 'ui.router' 8 | ]) 9 | 10 | 11 | .config(function config( $stateProvider ) { 12 | $stateProvider.state( 'home', { 13 | url: '/home?page&sort&tags', 14 | views: { 15 | "main": { 16 | controller: 'HomeController', 17 | templateUrl: 'home/home.tpl.html' 18 | } 19 | }, 20 | data:{ pageTitle: 'Draw A Cat!' } 21 | }); 22 | }) 23 | 24 | /** 25 | * And of course we define a controller for our route. 26 | */ 27 | .controller( 'HomeController', function HomeController( $scope, $location, $state, $stateParams, datastore ) { 28 | 29 | // emit an event to update the page metadata 30 | var metaData = { 31 | pageTitle: 'Draw A Cat!', 32 | title: 'Draw A Cat!', 33 | url: $location.absUrl(), 34 | image: 'assets/cat-big.gif' 35 | }; 36 | $scope.$emit('metadata:updated', metaData); 37 | 38 | $scope.sort = $stateParams.sort || "top"; 39 | $scope.currentPage = $stateParams.page || 1; 40 | $scope.tagsArray = $stateParams.tags ? $stateParams.tags.split(' ') : []; 41 | 42 | $scope.tags = []; 43 | datastore.getTags().then(function (data) { 44 | $scope.tags = data.data; 45 | }); 46 | 47 | $scope.searchInput = ""; 48 | 49 | 50 | $scope.$watchCollection('tagsArray', function(tagsArray) { 51 | $scope.setTags(tagsArray); 52 | }); 53 | 54 | $scope.tagLinkClicked = function(tag) { 55 | if ($scope.tagsArray.indexOf(tag) === -1) { 56 | $scope.tagsArray.push(tag); 57 | } 58 | }; 59 | 60 | $scope.pageChanged = function(pageNumber) { 61 | updateQueryString({ page: pageNumber }); 62 | }; 63 | 64 | $scope.sortBy = function(sort) { 65 | updateQueryString({ sort: sort }); 66 | }; 67 | 68 | $scope.setTags = function(tagsArray) { 69 | updateQueryString({tags: tagsArray.join(' ')}); 70 | }; 71 | 72 | $scope.closeOverlay = function() { 73 | $scope.$broadcast('preview-click', true); 74 | }; 75 | 76 | getPage($scope.currentPage, $scope.sort, $scope.tagsArray); 77 | 78 | function updateQueryString(params) { 79 | var options = {}; 80 | options.page = params.page || $scope.currentPage || null; 81 | options.sort = params.sort || $scope.sort || null; 82 | options.tags = params.tags || $scope.tagsArray.join(' ') || null; 83 | 84 | $state.transitionTo('home', options); 85 | } 86 | 87 | function getPage(pageNumber, sort, tagsArray) { 88 | datastore.listCats(pageNumber, sort, tagsArray).success(function(data) { 89 | $scope.cats = data.result; 90 | $scope.totalItems = data.totalCats; 91 | $scope.pageLower = ($scope.currentPage - 1) * 15 + 1; 92 | $scope.pageUpper = Math.min($scope.pageLower + 14, $scope.totalItems); 93 | }); 94 | } 95 | }) 96 | 97 | .filter('startFrom', function() { 98 | return function(input, start) { 99 | if (typeof input !== 'undefined' && 0 < input.length) { 100 | start = +start; //parse to int 101 | return input.slice(start); 102 | } 103 | }; 104 | }); -------------------------------------------------------------------------------- /src/common/services/noiseFactory.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 13/03/14. 3 | */ 4 | 5 | describe('noiseFactory service', function() { 6 | var generator; 7 | var generateLotsOfNoise; 8 | 9 | beforeEach(module('drawACat.common.services')); 10 | 11 | beforeEach(inject(function(_noiseFactory_) { 12 | generator = _noiseFactory_.newGenerator(); 13 | 14 | // generate 10,000 values as a large enough sample to check they fall within the expected bounds. 15 | generateLotsOfNoise = function(generator) { 16 | var noiseFactoryValues = []; 17 | for (var i = 0; i < 10000; i ++ ) { 18 | noiseFactoryValues.push(generator.getVal(i)); 19 | } 20 | return noiseFactoryValues; 21 | }; 22 | 23 | jasmine.addMatchers({ 24 | /** 25 | * Matcher to check that all values in the array fall within the specified range (inclusive of the bounds) 26 | * @returns {{compare: compare}} 27 | */ 28 | toAllBeWithinRange: function() { 29 | return { 30 | compare: function(valuesArray, lowerBound, upperBound) { 31 | var pass = true; 32 | var failingValue = 0; 33 | for (var i = 0; i < valuesArray.length; i ++) { 34 | if (valuesArray[i] < lowerBound || upperBound < valuesArray[i]) { 35 | pass = false; 36 | failingValue = valuesArray[i]; 37 | break; 38 | } 39 | } 40 | 41 | var result = { 42 | pass: pass 43 | }; 44 | if (!result.pass) { 45 | result.message = failingValue + ' is not between ' + lowerBound + ' and ' + upperBound; 46 | } 47 | return result; 48 | } 49 | }; 50 | }, 51 | /** 52 | * Matcher to check if at least one of the values in the array lies in the specified range 53 | * @returns {{compare: compare}} 54 | */ 55 | toIncludeRange: function() { 56 | return { 57 | compare: function(valuesArray, lowerBound, upperBound) { 58 | var pass = false; 59 | 60 | for (var i = 0; i < valuesArray.length; i ++) { 61 | if (valuesArray[i] > lowerBound && upperBound > valuesArray[i]) { 62 | pass = true; 63 | break; 64 | } 65 | } 66 | 67 | var result = { 68 | pass: pass 69 | }; 70 | if (!result.pass) { 71 | result.message = 'Array has no values between ' + lowerBound + ' and ' + upperBound; 72 | } 73 | return result; 74 | } 75 | }; 76 | } 77 | }); 78 | })); 79 | 80 | it('should generate values between 0 and 1 on default settings', function() { 81 | var noiseFactoryValues = generateLotsOfNoise(generator); 82 | expect(noiseFactoryValues).toAllBeWithinRange(0, 1); 83 | }); 84 | 85 | it('should amplify correctly', function() { 86 | generator.setAmplitude(2); 87 | var noiseFactoryValues = generateLotsOfNoise(generator); 88 | 89 | expect(noiseFactoryValues).toAllBeWithinRange(0, 2); 90 | expect(noiseFactoryValues).toIncludeRange(1, 2); 91 | }); 92 | }); -------------------------------------------------------------------------------- /src/common/directives/dirDisqus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A directive to embed a Disqus comments widget on your AngularJS page. 3 | * 4 | * For documentation, see the README.md file in this directory 5 | * 6 | * Created by Michael on 22/01/14. 7 | * Copyright Michael Bromley 2014 8 | * Available under the MIT license. 9 | */ 10 | angular.module('drawACat.common.directives') 11 | 12 | .directive('dirDisqus', function($window) { 13 | return { 14 | restrict: 'E', 15 | scope: { 16 | disqus_shortname: '@disqusShortname', 17 | disqus_identifier: '@disqusIdentifier', 18 | disqus_title: '@disqusTitle', 19 | disqus_url: '@disqusUrl', 20 | disqus_category_id: '@disqusCategoryId', 21 | disqus_disable_mobile: '@disqusDisableMobile', 22 | readyToBind: "@" 23 | }, 24 | template: 'comments powered by Disqus', 25 | link: function(scope) { 26 | 27 | // ensure that the disqus_identifier and disqus_url are both set, otherwise we will run in to identifier conflicts when using URLs with "#" in them 28 | // see http://help.disqus.com/customer/portal/articles/662547-why-are-the-same-comments-showing-up-on-multiple-pages- 29 | if (!scope.disqus_identifier || !scope.disqus_url) { 30 | throw "Please ensure that the `disqus-identifier` and `disqus-url` attributes are both set."; 31 | } 32 | 33 | scope.$watch("readyToBind", function(isReady) { 34 | 35 | // If the directive has been called without the 'ready-to-bind' attribute, we 36 | // set the default to "true" so that Disqus will be loaded straight away. 37 | if ( !angular.isDefined( isReady ) ) { 38 | isReady = "true"; 39 | } 40 | if (scope.$eval(isReady)) { 41 | // put the config variables into separate global vars so that the Disqus script can see them 42 | $window.disqus_shortname = scope.disqus_shortname; 43 | $window.disqus_identifier = scope.disqus_identifier; 44 | $window.disqus_title = scope.disqus_title; 45 | $window.disqus_url = scope.disqus_url; 46 | $window.disqus_category_id = scope.disqus_category_id; 47 | $window.disqus_disable_mobile = scope.disqus_disable_mobile; 48 | 49 | // get the remote Disqus script and insert it into the DOM, but only if it not already loaded (as that will cause warnings) 50 | if (!$window.DISQUS) { 51 | var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; 52 | dsq.src = '//' + scope.disqus_shortname + '.disqus.com/embed.js'; 53 | (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); 54 | } else { 55 | $window.DISQUS.reset({ 56 | reload: true, 57 | config: function () { 58 | this.page.identifier = scope.disqus_identifier; 59 | this.page.url = scope.disqus_url; 60 | this.page.title = scope.disqus_title; 61 | } 62 | }); 63 | } 64 | } 65 | }); 66 | } 67 | }; 68 | }); 69 | -------------------------------------------------------------------------------- /api/Slim/Middleware/MethodOverride.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.4.2 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Middleware; 34 | 35 | /** 36 | * HTTP Method Override 37 | * 38 | * This is middleware for a Slim application that allows traditional 39 | * desktop browsers to submit pseudo PUT and DELETE requests by relying 40 | * on a pre-determined request parameter. Without this middleware, 41 | * desktop browsers are only able to submit GET and POST requests. 42 | * 43 | * This middleware is included automatically! 44 | * 45 | * @package Slim 46 | * @author Josh Lockhart 47 | * @since 1.6.0 48 | */ 49 | class MethodOverride extends \Slim\Middleware 50 | { 51 | /** 52 | * @var array 53 | */ 54 | protected $settings; 55 | 56 | /** 57 | * Constructor 58 | * @param array $settings 59 | */ 60 | public function __construct($settings = array()) 61 | { 62 | $this->settings = array_merge(array('key' => '_METHOD'), $settings); 63 | } 64 | 65 | /** 66 | * Call 67 | * 68 | * Implements Slim middleware interface. This method is invoked and passed 69 | * an array of environment variables. This middleware inspects the environment 70 | * variables for the HTTP method override parameter; if found, this middleware 71 | * modifies the environment settings so downstream middleware and/or the Slim 72 | * application will treat the request with the desired HTTP method. 73 | * 74 | * @return array[status, header, body] 75 | */ 76 | public function call() 77 | { 78 | $env = $this->app->environment(); 79 | if (isset($env['HTTP_X_HTTP_METHOD_OVERRIDE'])) { 80 | // Header commonly used by Backbone.js and others 81 | $env['slim.method_override.original_method'] = $env['REQUEST_METHOD']; 82 | $env['REQUEST_METHOD'] = strtoupper($env['HTTP_X_HTTP_METHOD_OVERRIDE']); 83 | } elseif (isset($env['REQUEST_METHOD']) && $env['REQUEST_METHOD'] === 'POST') { 84 | // HTML Form Override 85 | $req = new \Slim\Http\Request($env); 86 | $method = $req->post($this->settings['key']); 87 | if ($method) { 88 | $env['slim.method_override.original_method'] = $env['REQUEST_METHOD']; 89 | $env['REQUEST_METHOD'] = strtoupper($method); 90 | } 91 | } 92 | $this->next->call(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /api/Slim/Http/Headers.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.4.2 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Http; 34 | 35 | /** 36 | * HTTP Headers 37 | * 38 | * @package Slim 39 | * @author Josh Lockhart 40 | * @since 1.6.0 41 | */ 42 | class Headers extends \Slim\Helper\Set 43 | { 44 | /******************************************************************************** 45 | * Static interface 46 | *******************************************************************************/ 47 | 48 | /** 49 | * Special-case HTTP headers that are otherwise unidentifiable as HTTP headers. 50 | * Typically, HTTP headers in the $_SERVER array will be prefixed with 51 | * `HTTP_` or `X_`. These are not so we list them here for later reference. 52 | * 53 | * @var array 54 | */ 55 | protected static $special = array( 56 | 'CONTENT_TYPE', 57 | 'CONTENT_LENGTH', 58 | 'PHP_AUTH_USER', 59 | 'PHP_AUTH_PW', 60 | 'PHP_AUTH_DIGEST', 61 | 'AUTH_TYPE' 62 | ); 63 | 64 | /** 65 | * Extract HTTP headers from an array of data (e.g. $_SERVER) 66 | * @param array $data 67 | * @return array 68 | */ 69 | public static function extract($data) 70 | { 71 | $results = array(); 72 | foreach ($data as $key => $value) { 73 | $key = strtoupper($key); 74 | if (strpos($key, 'X_') === 0 || strpos($key, 'HTTP_') === 0 || in_array($key, static::$special)) { 75 | if ($key === 'HTTP_CONTENT_LENGTH') { 76 | continue; 77 | } 78 | $results[$key] = $value; 79 | } 80 | } 81 | 82 | return $results; 83 | } 84 | 85 | /******************************************************************************** 86 | * Instance interface 87 | *******************************************************************************/ 88 | 89 | /** 90 | * Transform header name into canonical form 91 | * @param string $key 92 | * @return string 93 | */ 94 | protected function normalizeKey($key) 95 | { 96 | $key = strtolower($key); 97 | $key = str_replace(array('-', '_'), ' ', $key); 98 | $key = preg_replace('#^http #', '', $key); 99 | $key = ucwords($key); 100 | $key = str_replace(' ', '-', $key); 101 | 102 | return $key; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/app/cat/services/catSimplifier.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 06/04/14. 3 | */ 4 | 5 | angular.module('drawACat.cat.services') 6 | 7 | /** 8 | * This service takes a cat object and simplifies its path by removing points. How many points 9 | * get removed depends on the shape of the lines and also the quality setting. Quality ranges between 10 | * 0 and 1, with 1 being best quality. 11 | */ 12 | .service('catSimplifier', function(catFactory, primitives) { 13 | 14 | this.simplifyCat = function(cat, quality) { 15 | var simplifiedCat; 16 | simplifiedCat = catFactory.newCat(); 17 | 18 | angular.forEach(cat.bodyParts, function(bodyPart, partName) { 19 | var partPath = bodyPart.part.getPath(); 20 | var simplifiedPath = simplifyPath(partPath, quality); 21 | 22 | var newPart = primitives.Part(); 23 | newPart.createFromPath(partName, simplifiedPath); 24 | simplifiedCat.bodyParts[partName].part = newPart; 25 | 26 | simplifiedCat.bodyParts[partName].behaviour = cat.bodyParts[partName].behaviour; 27 | }); 28 | 29 | // now we need to loop through the bodyParts once more to resolve the parent/child relationships 30 | angular.forEach(simplifiedCat.bodyParts, function(bodyPart, partName) { 31 | var partShouldHaveParent = cat.bodyParts[partName].part.getParent(); 32 | if(partShouldHaveParent) { 33 | var parentName = cat.bodyParts[partName].part.getParent().getName(); 34 | var partParent = simplifiedCat.bodyParts[parentName].part; 35 | simplifiedCat.bodyParts[partName].part.setParent(partParent); 36 | } 37 | }); 38 | 39 | return simplifiedCat; 40 | }; 41 | 42 | var simplifyPath = function(path, quality) { 43 | return path.map(function(line) { 44 | return removeRedundantPoints(line, quality); 45 | }); 46 | }; 47 | 48 | var removeRedundantPoints = function(line, quality) { 49 | var tolerance = 1 - quality + 0.01; // angle in radians 50 | var pointsToRemove = []; 51 | var keepAngle1 = false; 52 | var angle1; 53 | var angle2; 54 | var dX, dY; 55 | // start with the second point (the first point in the line should never be removed) 56 | for (var i = 1; i < line.length - 2; i ++) { // -2 in order to always preserve the last point of the line 57 | // calculate the angle between x-axis and the line formed from line[0] - line[1] 58 | if (!keepAngle1) { 59 | dX= line[i][0] - line[i - 1][0]; 60 | dY= line[i][1] - line[i - 1][1]; 61 | angle1 = Math.atan2(dY, dX); 62 | } 63 | // move to the next point and get the angle for line[1] - line[2] 64 | dX= line[i + 1][0] - line[i][0]; 65 | dY= line[i + 1][1] - line[i][1]; 66 | angle2 = Math.atan2(dY, dX); 67 | // compare angle 2 to angle 1. 68 | if (Math.abs(angle2 - angle1) < tolerance) { 69 | // if the difference between the two angles is less than a specified delta, remove point line[1] and keep angle 1 70 | pointsToRemove.push(i); 71 | keepAngle1 = true; 72 | } else { 73 | // if it is more, angle 2 becomes angle 1 and we move to the next point to calculate a new angle 2 74 | keepAngle1 = false; 75 | } 76 | } 77 | 78 | return line.filter(function(point, i) { 79 | return pointsToRemove.indexOf(i) === -1; // return true if index not in the pointsToRemove array. 80 | }); 81 | }; 82 | }) 83 | ; -------------------------------------------------------------------------------- /src/common/services/datastore.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 13/03/14. 3 | */ 4 | 5 | xdescribe('datastore service', function() { 6 | 7 | var datastore; 8 | var $httpBackend; 9 | var mockApiUrl = 'http://www.mydomain.com/api/'; 10 | var mockThumbnailsUrl = 'http://www.mydomain.com/api/thumbnails'; 11 | 12 | beforeEach( module( 'drawACat' ) ); 13 | beforeEach( module( 'drawACat.common.services' ) ); 14 | 15 | beforeEach( function() { 16 | var mockCONFIG = { 17 | API_URL: mockApiUrl, 18 | THUMBNAILS_URL: mockThumbnailsUrl 19 | }; 20 | module(function ($provide) { 21 | $provide.value('CONFIG', mockCONFIG); 22 | }); 23 | 24 | inject( function( _$httpBackend_, _datastore_ ) { 25 | datastore = _datastore_; 26 | $httpBackend = _$httpBackend_; 27 | }); 28 | }); 29 | 30 | afterEach(inject(function($rootScope) { 31 | $rootScope.$apply(); 32 | $httpBackend.flush(); 33 | })); 34 | 35 | it('should make the correct api call on load', function() { 36 | $httpBackend.expectGET(mockApiUrl + 'cat/123').respond(200); 37 | datastore.loadCat(123); 38 | }); 39 | 40 | describe('listCats() method', function() { 41 | 42 | var response; 43 | 44 | beforeEach(function() { 45 | var mockResponse = [ 46 | { 47 | name: 'test cat', 48 | created: '123456789', 49 | thumbnail: 'thumb.png' 50 | } 51 | ]; 52 | $httpBackend.whenGET(mockApiUrl + 'cat/').respond(200, angular.toJson(mockResponse)); 53 | datastore.listCats().then(function(data) { 54 | response = data; 55 | }); 56 | }); 57 | 58 | it('should make correct api call on list', function() { 59 | $httpBackend.expectGET(mockApiUrl + 'cat/'); 60 | }); 61 | 62 | it('should transform the response timestamp with an extra 3 zeros', function() { 63 | expect(response.data[0].created).toEqual('123456789000'); 64 | }); 65 | 66 | it('should add the image path to the thumbnail', function() { 67 | expect(response.data[0].thumbnail).toEqual(mockThumbnailsUrl + 'thumb.png'); 68 | }); 69 | }); 70 | 71 | 72 | 73 | describe('saveCat() method', function() { 74 | 75 | var postData; 76 | 77 | beforeEach(function() { 78 | postData = { 79 | name: 'bobby', 80 | description: 'a testing cat', 81 | author: 'Jim Test', 82 | isPublic: true, 83 | tags: 'tag1 tag2', 84 | cat: { catObject: 'mocked!' }, 85 | thumbnail: 'wdawdawdawdawdawd' 86 | }; 87 | }); 88 | 89 | it('should make correct api call on save', function() { 90 | $httpBackend.expectPOST(mockApiUrl + 'cat/', postData).respond(201); 91 | datastore.saveCat(postData); 92 | }); 93 | }); 94 | 95 | describe('getTags() method', function() { 96 | 97 | var $httpCache; 98 | var $cacheFactory; 99 | 100 | beforeEach(inject(function(_$cacheFactory_) { 101 | //$httpCache = _$cacheFactory_.get('$http'); 102 | $cacheFactory = _$cacheFactory_; 103 | })); 104 | 105 | it('should make the correct api call', function() { 106 | $httpBackend.expectGET(mockApiUrl + 'tags/').respond(200, ['tag1', 'tag2']); 107 | datastore.getTags(); 108 | }); 109 | 110 | it('should not call the api the second time it is invoked', function() { 111 | $httpBackend.expectGET(mockApiUrl + 'tags/').respond(200, ['tag1', 'tag2']); 112 | datastore.getTags(); 113 | datastore.getTags(); 114 | }); 115 | }); 116 | }); -------------------------------------------------------------------------------- /src/app/cat/cat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 10/03/14. 3 | */ 4 | 5 | angular.module( 'drawACat.cat', [ 6 | 'drawACat.common.services', 7 | 'ui.router', 8 | 'drawACat.cat.directives', 9 | 'drawACat.cat.services', 10 | 'ezfb', 11 | 'ngAnimate' 12 | ]) 13 | 14 | 15 | .config(function config( $stateProvider ) { 16 | $stateProvider.state( 'cat', { 17 | url: '/cat/{id:[0-9]+}/{name}', 18 | views: { 19 | "main": { 20 | controller: 'CatController', 21 | templateUrl: 'cat/cat.tpl.html', 22 | resolve: { 23 | catPromise: ['$stateParams', 'datastore', function($stateParams, datastore) { 24 | return datastore.loadCat($stateParams.id); 25 | }] 26 | } 27 | } 28 | }, 29 | data: { pageTitle: 'Cat' } 30 | }); 31 | }) 32 | 33 | .config(function (ezfbProvider) { 34 | ezfbProvider.setInitParams({ 35 | appId: '459619084171176' 36 | }); 37 | }) 38 | 39 | /** 40 | * And of course we define a controller for our route. 41 | */ 42 | .controller( 'CatController', function CatController( $scope, $location, CONFIG, serializer, catPromise, Ball, emotion, ratingService, userOptions, audioPlayer ) { 43 | // TODO: refactor all those dependencies above into helper services 44 | $scope.embed = $location.search().embed; 45 | $scope.pageUrl = $location.absUrl(); 46 | $scope.catData = catPromise.data; 47 | $scope.cat = serializer.unserializeCat(catPromise.data.data); 48 | $scope.cat.emotion = emotion; 49 | $scope.cat.emotion.start(); 50 | $scope.ball = [new Ball()]; 51 | $scope.renderQuality = userOptions.getRenderQuality(); 52 | $scope.help = { 53 | show: !userOptions.getHelpHasBeenSeen() 54 | }; 55 | 56 | // emit an event to update the page metadata 57 | var metaData = { 58 | pageTitle:$scope.catData.name + ': Draw A Cat!', 59 | title: $scope.catData.name, 60 | url: $location.absUrl(), 61 | image: CONFIG.THUMBNAILS_URL + $scope.catData.thumbnail 62 | }; 63 | $scope.$emit('metadata:updated', metaData); 64 | 65 | $scope.catHasBeenRated = ratingService.hasUserRatedThisCat($scope.catData.id); 66 | $scope.rateCat = function() { 67 | if (!$scope.catHasBeenRated) { 68 | ratingService.setCatAsRated($scope.catData.id); 69 | $scope.catHasBeenRated = true; 70 | var newRating; 71 | newRating = parseInt($scope.catData.rating, 10) + 1; 72 | $scope.catData.rating = newRating; 73 | } 74 | }; 75 | 76 | $scope.setRenderQuality = function(value) { 77 | userOptions.setRenderQuality(parseInt(value, 10)); 78 | }; 79 | 80 | $scope.addBall = function(e) { 81 | $scope.ball.push(new Ball(e.clientX, e.clientY)); 82 | }; 83 | 84 | $scope.audioSetting = userOptions.getAudioSetting(); 85 | $scope.toggleAudio = function() { 86 | if ($scope.audioSetting === true) { 87 | audioPlayer.setAudio(false); 88 | $scope.audioSetting = false; 89 | userOptions.setAudioSetting(false); 90 | } else { 91 | audioPlayer.setAudio(true); 92 | $scope.audioSetting = true; 93 | userOptions.setAudioSetting(true); 94 | } 95 | }; 96 | 97 | $scope.dismissHelp = function() { 98 | $scope.help.show = false; 99 | userOptions.setHelpHasBeenSeen(); 100 | }; 101 | 102 | $scope.$on('$destroy', function() { 103 | emotion.reset(); 104 | }); 105 | }) 106 | 107 | ; -------------------------------------------------------------------------------- /src/app/draw/draw.less: -------------------------------------------------------------------------------- 1 | .control-panel { 2 | h1 { 3 | font-family: @comic-sans; 4 | margin-top: 5px; 5 | } 6 | } 7 | 8 | .draw-container { 9 | padding-top: 40px; 10 | } 11 | .canvas-container { 12 | position: relative; 13 | width: 540px; 14 | height: 540px; 15 | } 16 | #canvas { 17 | position: absolute; 18 | top: 0px; 19 | left: 0px; 20 | z-index: 10; 21 | background-color: rgba(255,255,255,0.5); 22 | cursor: crosshair; 23 | } 24 | #canvas-frame { 25 | position: absolute; 26 | top: 20px; 27 | left: 20px; 28 | width: 500px; 29 | height: 500px; 30 | border: 1px solid #333; 31 | &.filled { 32 | background-color: #f5f5f5; 33 | } 34 | z-index: 9; 35 | } 36 | #draw-guide { 37 | position: absolute; 38 | top: 20px; 39 | left: 20px; 40 | width: 500px; 41 | height: 500px; 42 | z-index: 8; 43 | .lighten { 44 | opacity: 0.7; 45 | } 46 | } 47 | .undo-button { 48 | position: absolute; 49 | right: 22px; 50 | top: 22px; 51 | z-index: 15; 52 | } 53 | .draw-instructions { 54 | margin: auto; 55 | max-width: 400px; 56 | position: relative; 57 | background: #fff; 58 | border: 4px solid @highlight-colour; 59 | border-radius: 5px; 60 | z-index: 20; 61 | margin-bottom: 20px; 62 | h2 { 63 | font-family: @comic-sans; 64 | } 65 | .steps-list ul { 66 | border-top: 1px solid lighten(@highlight-colour, 20%); 67 | background: lighten(@highlight-colour, 50%); 68 | padding: 10px; 69 | margin-bottom: 0; 70 | margin-top: 10px; 71 | font-size: 0.85em; 72 | list-style-type: none; 73 | color: #999; 74 | li { 75 | display: inline-block; 76 | margin: 3px 3px; 77 | padding: 2px 3px; 78 | background-color: #efefef; 79 | border-radius: 3px; 80 | border: 1px solid #fff; 81 | } 82 | .selected { 83 | color: #333; 84 | background-color: #f5f5f5; 85 | border-color: #395c34; 86 | } 87 | .done { 88 | color: #393; 89 | background-color: #c2e8b7; 90 | } 91 | } 92 | 93 | .instruction-container { 94 | min-height: 100px; 95 | padding: 10px; 96 | h2 { 97 | margin-top: 2px; 98 | margin-bottom: 2px; 99 | } 100 | .instruction { 101 | color: #666; 102 | min-height: 60px; 103 | } 104 | } 105 | .checkbox { 106 | padding-left: 30px; 107 | } 108 | .nav-buttons { 109 | padding: 10px 110 | } 111 | } 112 | 113 | @media (max-width: @screen-xs-max) { 114 | 115 | .draw-instructions:after, .draw-instructions:before { 116 | bottom: 100%; 117 | left: 50%; 118 | border: solid transparent; 119 | content: " "; 120 | height: 0; 121 | width: 0; 122 | position: absolute; 123 | pointer-events: none; 124 | } 125 | 126 | .draw-instructions:after { 127 | border-color: rgba(255, 255, 255, 0); 128 | border-bottom-color: #fff; 129 | border-width: 30px; 130 | margin-left: -30px; 131 | } 132 | .draw-instructions:before { 133 | border-color: rgba(153, 153, 153, 0); 134 | border-bottom-color: @highlight-colour; 135 | border-width: 36px; 136 | margin-left: -36px; 137 | } 138 | } 139 | 140 | @media (min-width: @screen-sm-min) { 141 | .draw-instructions { 142 | margin-left: -20px; 143 | margin-top: 20px; 144 | } 145 | .draw-instructions:after, .draw-instructions:before { 146 | right: 100%; 147 | top: 50%; 148 | border: solid transparent; 149 | content: " "; 150 | height: 0; 151 | width: 0; 152 | position: absolute; 153 | pointer-events: none; 154 | } 155 | 156 | .draw-instructions:after { 157 | border-color: rgba(255, 255, 255, 0); 158 | border-right-color: #fff; 159 | border-width: 30px; 160 | margin-top: -30px; 161 | } 162 | .draw-instructions:before { 163 | border-color: rgba(153, 153, 153, 0); 164 | border-right-color: @highlight-colour; 165 | border-width: 36px; 166 | margin-top: -36px; 167 | } 168 | } 169 | 170 | .save-button { 171 | background-color: @highlight-colour; 172 | color: #fff; 173 | } 174 | 175 | .suggestions-container { 176 | background-color: rgba(255,255,255,0.95); 177 | border: 1px solid #999; 178 | -webkit-box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.15); 179 | -moz-box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.15); 180 | box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.15); 181 | } 182 | .suggestion { 183 | padding: 3px 10px; 184 | font-size: 1.1em; 185 | &.selected, &:hover { 186 | background-color: #00b3ee; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /api/Slim/Middleware/PrettyExceptions.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.4.2 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Middleware; 34 | 35 | /** 36 | * Pretty Exceptions 37 | * 38 | * This middleware catches any Exception thrown by the surrounded 39 | * application and displays a developer-friendly diagnostic screen. 40 | * 41 | * @package Slim 42 | * @author Josh Lockhart 43 | * @since 1.0.0 44 | */ 45 | class PrettyExceptions extends \Slim\Middleware 46 | { 47 | /** 48 | * @var array 49 | */ 50 | protected $settings; 51 | 52 | /** 53 | * Constructor 54 | * @param array $settings 55 | */ 56 | public function __construct($settings = array()) 57 | { 58 | $this->settings = $settings; 59 | } 60 | 61 | /** 62 | * Call 63 | */ 64 | public function call() 65 | { 66 | try { 67 | $this->next->call(); 68 | } catch (\Exception $e) { 69 | $log = $this->app->getLog(); // Force Slim to append log to env if not already 70 | $env = $this->app->environment(); 71 | $env['slim.log'] = $log; 72 | $env['slim.log']->error($e); 73 | $this->app->contentType('text/html'); 74 | $this->app->response()->status(500); 75 | $this->app->response()->body($this->renderBody($env, $e)); 76 | } 77 | } 78 | 79 | /** 80 | * Render response body 81 | * @param array $env 82 | * @param \Exception $exception 83 | * @return string 84 | */ 85 | protected function renderBody(&$env, $exception) 86 | { 87 | $title = 'Slim Application Error'; 88 | $code = $exception->getCode(); 89 | $message = $exception->getMessage(); 90 | $file = $exception->getFile(); 91 | $line = $exception->getLine(); 92 | $trace = $exception->getTraceAsString(); 93 | $html = sprintf('
The application could not run because of the following error:
'; 95 | $html .= '%s', $trace); 112 | } 113 | 114 | return sprintf("
44 | If the animation is not smooth, try lowering the render quality. More complex cats consist of more lines and points, and can require faster 45 | hardware to run at full speed. 46 |
47 |{{ catData.description }}
67 | 68 |