├── app ├── robots.txt ├── lib │ ├── angular │ │ ├── version.txt │ │ ├── angular-cookies.min.js │ │ ├── angular-loader.min.js │ │ ├── angular-resource.min.js │ │ └── angular-sanitize.min.js │ ├── jquery.cookie.js │ ├── respond.min.js │ ├── modernizr.custom.07116.js │ └── underscore.min.js ├── favicon.ico ├── img │ ├── led.gif │ ├── noise.png │ ├── title.png │ ├── bg-mount.png │ ├── sun-mask.png │ ├── title@2x.png │ ├── glyphicons.png │ ├── apple-touch-icon.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-startup-image.png │ ├── apple-touch-startup-image-748x1024.png │ └── apple-touch-startup-image-768x1004.png ├── css │ ├── sg.css │ ├── prism.css │ └── app.css ├── js │ ├── filters.js │ ├── app.js │ ├── testing.js │ ├── directives.js │ ├── dom.js │ ├── sg.js │ ├── services.js │ ├── prism.js │ └── controllers.js ├── partials │ ├── lobby.html │ └── room.html ├── index.ejs └── styleguide.ejs ├── test ├── lib │ ├── angular │ │ ├── version.txt │ │ └── jstd-scenario-adapter.js │ ├── jasmine │ │ ├── version.txt │ │ ├── jasmine_favicon.png │ │ ├── MIT.LICENSE │ │ ├── jasmine.css │ │ ├── index.js │ │ └── jasmine-html.js │ ├── jstestdriver │ │ ├── version.txt │ │ └── JsTestDriver.jar │ └── jasmine-jstd-adapter │ │ ├── version.txt │ │ └── JasmineAdapter.js ├── e2e │ ├── runner.html │ └── scenarios.js └── unit │ ├── servicesSpec.js │ ├── filtersSpec.js │ ├── controllersSpec.js │ └── directivesSpec.js ├── Procfile ├── resources ├── bg-mount.psd └── glyphicons.psd ├── Dockerfile ├── config ├── jstd-scenario-adapter-config.js ├── jsTestDriver-scenario.conf └── jsTestDriver.conf ├── docker-compose.yml ├── .gitignore ├── scripts ├── test.sh ├── e2e-test.sh ├── e2e-test.bat ├── test.bat ├── test-server.sh ├── e2e-test-server.sh ├── watchr.rb ├── e2e-test-server.bat ├── test-server.bat └── web-server.js ├── config.js ├── package.json ├── README.md ├── LICENCE ├── lib ├── lobby.js └── room.js └── server.js /app/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * -------------------------------------------------------------------------------- /app/lib/angular/version.txt: -------------------------------------------------------------------------------- 1 | 1.0.1 2 | -------------------------------------------------------------------------------- /test/lib/angular/version.txt: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /test/lib/jasmine/version.txt: -------------------------------------------------------------------------------- 1 | 1.1.0 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: app_port=$PORT node server.js 2 | -------------------------------------------------------------------------------- /test/lib/jstestdriver/version.txt: -------------------------------------------------------------------------------- 1 | 1.3.3d 2 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /app/img/led.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/led.gif -------------------------------------------------------------------------------- /app/img/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/noise.png -------------------------------------------------------------------------------- /app/img/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/title.png -------------------------------------------------------------------------------- /app/img/bg-mount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/bg-mount.png -------------------------------------------------------------------------------- /app/img/sun-mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/sun-mask.png -------------------------------------------------------------------------------- /app/img/title@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/title@2x.png -------------------------------------------------------------------------------- /app/img/glyphicons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/glyphicons.png -------------------------------------------------------------------------------- /resources/bg-mount.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/resources/bg-mount.psd -------------------------------------------------------------------------------- /resources/glyphicons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/resources/glyphicons.psd -------------------------------------------------------------------------------- /test/lib/jasmine-jstd-adapter/version.txt: -------------------------------------------------------------------------------- 1 | f6b1cf6cac90932c72c4349df8847e0ffd9acbc3 @ 2012-04-29 2 | -------------------------------------------------------------------------------- /app/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/apple-touch-icon.png -------------------------------------------------------------------------------- /app/img/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /app/img/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /app/img/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /test/lib/jasmine/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/test/lib/jasmine/jasmine_favicon.png -------------------------------------------------------------------------------- /app/img/apple-touch-startup-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/apple-touch-startup-image.png -------------------------------------------------------------------------------- /test/lib/jstestdriver/JsTestDriver.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/test/lib/jstestdriver/JsTestDriver.jar -------------------------------------------------------------------------------- /app/img/apple-touch-startup-image-748x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/apple-touch-startup-image-748x1024.png -------------------------------------------------------------------------------- /app/img/apple-touch-startup-image-768x1004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richarcher/Hatjitsu/HEAD/app/img/apple-touch-startup-image-768x1004.png -------------------------------------------------------------------------------- /app/css/sg.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 16px/26px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; 3 | color: #111; 4 | } 5 | h1 { 6 | line-height: 36px; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | 3 | ENV instDir /Hatjitsu 4 | WORKDIR ${instDir} 5 | COPY . . 6 | RUN npm install -d 7 | 8 | EXPOSE 5000 9 | 10 | CMD node server 11 | -------------------------------------------------------------------------------- /config/jstd-scenario-adapter-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration for jstd scenario adapter 3 | */ 4 | var jstdScenarioAdapter = { 5 | relativeUrlPrefix: '/test/e2e/' 6 | }; 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | poker: 4 | build: . 5 | ports: 6 | - "5000:5000" 7 | volumes: 8 | - /Hatjitsu/node_modules 9 | - .:/Hatjitsu 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | app/generated 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_DIR=`dirname $0` 4 | 5 | java -jar "$BASE_DIR/../test/lib/jstestdriver/JsTestDriver.jar" \ 6 | --config "$BASE_DIR/../config/jsTestDriver.conf" \ 7 | --basePath "$BASE_DIR/.." \ 8 | --tests all 9 | -------------------------------------------------------------------------------- /scripts/e2e-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_DIR=`dirname $0` 4 | 5 | java -jar "$BASE_DIR/../test/lib/jstestdriver/JsTestDriver.jar" \ 6 | --config "$BASE_DIR/../config/jsTestDriver-scenario.conf" \ 7 | --basePath "$BASE_DIR/.." \ 8 | --tests all --reset 9 | -------------------------------------------------------------------------------- /test/e2e/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |First person to create the room is the moderator. Share the url or room number with other team members to join the room.
4 | 5 | 16 | 17 |')
41 | , indentation = html.split("\n")
42 | , whitespace = lastIndentationSize( indentation )
43 | , newhtml = html;
44 |
45 | if ( whitespace.length ) {
46 | newhtml = reIndent( html, whitespace);
47 | }
48 |
49 | codeblock.find('code').append( escapeHTML(newhtml) );
50 |
51 | if(target) {
52 | $(target).append(codeblock);
53 | } else {
54 | $(this).after(codeblock);
55 | }
56 | });
57 |
58 | };
59 |
60 | // Self Execute!!
61 | $.fn.dataCodeBlock();
62 | })(jQuery);
63 |
64 |
65 |
66 | $(document).ready(function(){
67 | $('#toggleFlipper01').click(function(){
68 | $('#toggleFlippee01').toggleClass('flipped');
69 | });
70 | $('#toggleFlipper02').click(function(){
71 | $('#toggleFlippee02').toggleClass('flipped');
72 | });
73 | $('#toggleFlipper03').click(function(){
74 | $('#toggleFlippee03').toggleClass('flipped');
75 | });
76 | $('#toggleFlipper04').click(function(){
77 | $('#toggleFlippee04').toggleClass('flipped');
78 | });
79 | $('#toggleFlipper05').click(function(){
80 | $('#toggleFlippee05').toggleClass('flipped-stagger');
81 | });
82 | });
--------------------------------------------------------------------------------
/test/lib/jasmine/jasmine.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
3 | }
4 |
5 |
6 | .jasmine_reporter a:visited, .jasmine_reporter a {
7 | color: #303;
8 | }
9 |
10 | .jasmine_reporter a:hover, .jasmine_reporter a:active {
11 | color: blue;
12 | }
13 |
14 | .run_spec {
15 | float:right;
16 | padding-right: 5px;
17 | font-size: .8em;
18 | text-decoration: none;
19 | }
20 |
21 | .jasmine_reporter {
22 | margin: 0 5px;
23 | }
24 |
25 | .banner {
26 | color: #303;
27 | background-color: #fef;
28 | padding: 5px;
29 | }
30 |
31 | .logo {
32 | float: left;
33 | font-size: 1.1em;
34 | padding-left: 5px;
35 | }
36 |
37 | .logo .version {
38 | font-size: .6em;
39 | padding-left: 1em;
40 | }
41 |
42 | .runner.running {
43 | background-color: yellow;
44 | }
45 |
46 |
47 | .options {
48 | text-align: right;
49 | font-size: .8em;
50 | }
51 |
52 |
53 |
54 |
55 | .suite {
56 | border: 1px outset gray;
57 | margin: 5px 0;
58 | padding-left: 1em;
59 | }
60 |
61 | .suite .suite {
62 | margin: 5px;
63 | }
64 |
65 | .suite.passed {
66 | background-color: #dfd;
67 | }
68 |
69 | .suite.failed {
70 | background-color: #fdd;
71 | }
72 |
73 | .spec {
74 | margin: 5px;
75 | padding-left: 1em;
76 | clear: both;
77 | }
78 |
79 | .spec.failed, .spec.passed, .spec.skipped {
80 | padding-bottom: 5px;
81 | border: 1px solid gray;
82 | }
83 |
84 | .spec.failed {
85 | background-color: #fbb;
86 | border-color: red;
87 | }
88 |
89 | .spec.passed {
90 | background-color: #bfb;
91 | border-color: green;
92 | }
93 |
94 | .spec.skipped {
95 | background-color: #bbb;
96 | }
97 |
98 | .messages {
99 | border-left: 1px dashed gray;
100 | padding-left: 1em;
101 | padding-right: 1em;
102 | }
103 |
104 | .passed {
105 | background-color: #cfc;
106 | display: none;
107 | }
108 |
109 | .failed {
110 | background-color: #fbb;
111 | }
112 |
113 | .skipped {
114 | color: #777;
115 | background-color: #eee;
116 | display: none;
117 | }
118 |
119 |
120 | /*.resultMessage {*/
121 | /*white-space: pre;*/
122 | /*}*/
123 |
124 | .resultMessage span.result {
125 | display: block;
126 | line-height: 2em;
127 | color: black;
128 | }
129 |
130 | .resultMessage .mismatch {
131 | color: black;
132 | }
133 |
134 | .stackTrace {
135 | white-space: pre;
136 | font-size: .8em;
137 | margin-left: 10px;
138 | max-height: 5em;
139 | overflow: auto;
140 | border: 1px inset red;
141 | padding: 1em;
142 | background: #eef;
143 | }
144 |
145 | .finished-at {
146 | padding-left: 1em;
147 | font-size: .6em;
148 | }
149 |
150 | .show-passed .passed,
151 | .show-skipped .skipped {
152 | display: block;
153 | }
154 |
155 |
156 | #jasmine_content {
157 | position:fixed;
158 | right: 100%;
159 | }
160 |
161 | .runner {
162 | border: 1px solid gray;
163 | display: block;
164 | margin: 5px 0;
165 | padding: 2px 0 2px 10px;
166 | }
167 |
--------------------------------------------------------------------------------
/app/lib/angular/angular-sanitize.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | AngularJS v1.0.1
3 | (c) 2010-2012 Google, Inc. http://angularjs.org
4 | License: MIT
5 | */
6 | (function(I,g){'use strict';function i(a){var d={},a=a.split(","),b;for(b=0;b=0;e--)if(f[e]==b)break;if(e>=0){for(c=f.length-1;c>=e;c--)d.end&&d.end(f[c]);f.length=
7 | e}}var c,h,f=[],j=a;for(f.last=function(){return f[f.length-1]};a;){h=!0;if(!f.last()||!q[f.last()]){if(a.indexOf("<\!--")===0)c=a.indexOf("--\>"),c>=0&&(d.comment&&d.comment(a.substring(4,c)),a=a.substring(c+3),h=!1);else if(B.test(a)){if(c=a.match(r))a=a.substring(c[0].length),c[0].replace(r,e),h=!1}else if(C.test(a)&&(c=a.match(s)))a=a.substring(c[0].length),c[0].replace(s,b),h=!1;h&&(c=a.indexOf("<"),h=c<0?a:a.substring(0,c),a=c<0?"":a.substring(c),d.chars&&d.chars(k(h)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+
8 | f.last()+"[^>]*>","i"),function(b,a){a=a.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(a));return""}),e("",f.last());if(a==j)throw"Parse Error: "+a;j=a}e()}function k(a){l.innerHTML=a.replace(//g,">")}function u(a){var d=!1,b=g.bind(a,a.push);return{start:function(a,c,h){a=g.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]==
9 | !0&&(b("<"),b(a),g.forEach(c,function(a,c){var e=g.lowercase(c);if(G[e]==!0&&(w[e]!==!0||a.match(H)))b(" "),b(c),b('="'),b(t(a)),b('"')}),b(h?"/>":">"))},end:function(a){a=g.lowercase(a);!d&&v[a]==!0&&(b(""),b(a),b(">"));a==d&&(d=!1)},chars:function(a){d||b(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^,B=/^<\s*\//,D=/<\!--(.*?)--\>/g,
10 | E=//g,H=/^((ftp|https?):\/\/|mailto:|#)/,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=g.extend({},y,x),m=g.extend({},x,i("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),n=g.extend({},y,i("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),
11 | q=i("script,style"),v=g.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=g.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");g.module("ngSanitize",[]).value("$sanitize",function(a){var d=[];
12 | z(a,u(d));return d.join("")});g.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,b,e){b.addClass("ng-binding").data("$binding",e.ngBindHtml);d.$watch(e.ngBindHtml,function(c){c=a(c);b.html(c||"")})}}]);g.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(b){if(!b)return b;for(var e=b,c=[],h=u(c),f,g;b=e.match(a);)f=b[0],b[2]==b[3]&&(f="mailto:"+f),g=b.index,
13 | h.chars(e.substr(0,g)),h.start("a",{href:f}),h.chars(b[0].replace(d,"")),h.end("a"),e=e.substring(g+b[0].length);h.chars(e);return c.join("")}})})(window,window.angular);
--------------------------------------------------------------------------------
/app/lib/respond.min.js:
--------------------------------------------------------------------------------
1 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
2 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */
3 | window.matchMedia=window.matchMedia||(function(e,f){var c,a=e.documentElement,b=a.firstElementChild||a.firstChild,d=e.createElement("body"),g=e.createElement("div");g.id="mq-test-1";g.style.cssText="position:absolute;top:-100em";d.style.background="none";d.appendChild(g);return function(h){g.innerHTML='';a.insertBefore(d,b);c=g.offsetWidth==42;a.removeChild(d);return{matches:c,media:h}}})(document);
4 |
5 | /*! Respond.js v1.1.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */
6 | (function(e){e.respond={};respond.update=function(){};respond.mediaQueriesSupported=e.matchMedia&&e.matchMedia("only all").matches;if(respond.mediaQueriesSupported){return}var w=e.document,s=w.documentElement,i=[],k=[],q=[],o={},h=30,f=w.getElementsByTagName("head")[0]||s,g=w.getElementsByTagName("base")[0],b=f.getElementsByTagName("link"),d=[],a=function(){var D=b,y=D.length,B=0,A,z,C,x;for(;B-1,minw:F.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:F.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}}j()},l,r,v=function(){var z,A=w.createElement("div"),x=w.body,y=false;A.style.cssText="position:absolute;font-size:1em;width:1em";if(!x){x=y=w.createElement("body");x.style.background="none"}x.appendChild(A);s.insertBefore(x,s.firstChild);z=A.offsetWidth;if(y){s.removeChild(x)}else{x.removeChild(A)}z=p=parseFloat(z);return z},p,j=function(I){var x="clientWidth",B=s[x],H=w.compatMode==="CSS1Compat"&&B||w.body[x]||B,D={},G=b[b.length-1],z=(new Date()).getTime();if(I&&l&&z-l-1?(p||v()):1)}if(!!J){J=parseFloat(J)*(J.indexOf(y)>-1?(p||v()):1)}if(!K.hasquery||(!A||!L)&&(A||H>=C)&&(L||H<=J)){if(!D[K.media]){D[K.media]=[]}D[K.media].push(k[K.rules])}}for(var E in q){if(q[E]&&q[E].parentNode===f){f.removeChild(q[E])}}for(var E in D){var M=w.createElement("style"),F=D[E].join("\n");M.type="text/css";M.media=E;f.insertBefore(M,G.nextSibling);if(M.styleSheet){M.styleSheet.cssText=F}else{M.appendChild(w.createTextNode(F))}q.push(M)}},n=function(x,z){var y=c();if(!y){return}y.open("GET",x,true);y.onreadystatechange=function(){if(y.readyState!=4||y.status!=200&&y.status!=304){return}z(y.responseText)};if(y.readyState==4){return}y.send(null)},c=(function(){var x=false;try{x=new XMLHttpRequest()}catch(y){x=new ActiveXObject("Microsoft.XMLHTTP")}return function(){return x}})();a();respond.update=a;function t(){j(true)}if(e.addEventListener){e.addEventListener("resize",t,false)}else{if(e.attachEvent){e.attachEvent("onresize",t)}}})(this);
--------------------------------------------------------------------------------
/app/partials/room.html:
--------------------------------------------------------------------------------
1 |
2 | Room: {{roomId}}
3 |
4 |
5 |
29 |
30 |
31 |
32 |
33 | {{card}}
34 |
35 |
36 | No cards found
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | You haven't estimated yet
59 | Your current estimate: {{myVote}}
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | x
69 | {{vote.visibleVote}}
70 |
71 |
72 |
73 |
74 |
75 | Average: {{votingAverage}} (StdDev = {{votingStandardDeviation}})
76 |
77 |
78 |
79 |
80 |
81 |
82 |
85 |
88 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/app/js/services.js:
--------------------------------------------------------------------------------
1 | /*jslint indent: 2, browser: true */
2 | /*global angular, Sock, io, $ */
3 |
4 | 'use strict';
5 |
6 | /* Services */
7 |
8 |
9 | // Demonstrate how to register services
10 | // In this case it is a simple value service.
11 | var pokerAppServices = angular.module('pokerApp.services', []);
12 |
13 | pokerAppServices.value('version', '0.1');
14 |
15 | pokerAppServices.service('socket', ['$rootScope', '$timeout', function ($rootScope) {
16 | var sock = new Sock($rootScope);
17 | return sock;
18 | }]);
19 |
20 | pokerAppServices.factory('socket', ['$rootScope', function ($rootScope) {
21 | var socket = io.connect(location.protocol + '//' + location.hostname, {
22 | 'port': location.port,
23 | 'reconnect': true,
24 | 'reconnection delay': 500,
25 | 'max reconnection attempts': 10,
26 | 'try multiple transports': true,
27 | 'transports': ['websocket', 'htmlfile', 'xhr-polling', 'jsonp-polling']
28 | });
29 |
30 | $rootScope.socketMessage = null;
31 | $rootScope.activity = false;
32 | $rootScope.sessionId = null;
33 |
34 | socket.on('error', function (reason) {
35 | // console.log('service: on error', reason);
36 | $rootScope.$apply(function () {
37 | $rootScope.socketMessage = ":-( Error = " + reason;
38 | });
39 | // console.log(reason);
40 | });
41 | socket.on('connect_failed', function (reason) {
42 | // console.log('service: on connect failed', reason);
43 | $rootScope.$apply(function () {
44 | $rootScope.socketMessage = ":-( Connect failed";
45 | });
46 | // console.log(reason);
47 | });
48 | socket.on('disconnect', function () {
49 | // console.log('service: on disconnect');
50 | $rootScope.$apply(function () {
51 | $rootScope.socketMessage = ":-( Disconnected";
52 | });
53 | // console.log('disconnected');
54 | });
55 | socket.on('connecting', function () {
56 | // console.log('service: on connecting');
57 | $rootScope.$apply(function () {
58 | $rootScope.socketMessage = "Connecting...";
59 | });
60 | // console.log('disconnected');
61 | });
62 | socket.on('reconnecting', function () {
63 | // console.log('service: on reconnecting');
64 | $rootScope.$apply(function () {
65 | $rootScope.socketMessage = "Reconnecting...";
66 | });
67 | // console.log('disconnected');
68 | });
69 | socket.on('reconnect', function () {
70 | // console.log('service: on reconnect');
71 | $rootScope.$apply(function () {
72 | $rootScope.socketMessage = null;
73 | });
74 | // console.log('disconnected');
75 | });
76 | socket.on('reconnect_failed', function () {
77 | // console.log('service: on reconnect_failed');
78 | $rootScope.$apply(function () {
79 | $rootScope.socketMessage = ":-( Reconnect failed";
80 | });
81 | // console.log('disconnected');
82 | });
83 | socket.on('connect', function () {
84 | var sessionId = this.socket.sessionid;
85 | // console.log('service: on connect');
86 | $rootScope.$apply(function () {
87 | $rootScope.socketMessage = null;
88 | // console.log("new session id = " + sessionId);
89 | if (!$.cookie("sessionId")) {
90 | $.cookie("sessionId", sessionId);
91 | }
92 | $rootScope.sessionId = $.cookie("sessionId");
93 | // console.log("session id = " + that.rootScope.sessionId);
94 | });
95 | });
96 |
97 | return {
98 | on: function (eventName, callback) {
99 | $rootScope.socketMessage = null;
100 | socket.on(eventName, function () {
101 | var args = arguments;
102 | $rootScope.$apply(function () {
103 | callback.apply(socket, args);
104 | });
105 | });
106 | },
107 | emit: function (eventName, data, callback) {
108 | $rootScope.activity = true;
109 | socket.emit(eventName, data, function () {
110 | var args = arguments;
111 | $rootScope.$apply(function () {
112 | $rootScope.activity = false;
113 | if (callback) {
114 | callback.apply(socket, args);
115 | }
116 | });
117 | });
118 | }
119 | };
120 | }]);
--------------------------------------------------------------------------------
/lib/room.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore')._;
2 | var util = require('util');
3 |
4 | var Room = function(io, roomUrl) {
5 | this.io = io;
6 | this.roomUrl = roomUrl;
7 | this.createdAt = calcTime(2);
8 | this.createAdmin = true;
9 | this.hasAdmin = false;
10 | this.cardPack = 'goat';
11 | this.connections = {}; // we collect the votes in here
12 | this.forcedReveal = false;
13 | this.alreadySorted = false;
14 | };
15 |
16 | Room.prototype.info = function() {
17 | this.createAdmin = this.hasAdmin === false;
18 | this.hasAdmin = true;
19 | // console.log("room info = ", this.json());
20 | return this.json();
21 | };
22 |
23 | Room.prototype.enter = function(socket, data) {
24 | // console.log("room entered as " + socket.id);
25 | if (this.connections[data.sessionId]) {
26 | this.connections[data.sessionId].socketId = socket.id;
27 | } else {
28 | this.connections[data.sessionId] = { sessionId: data.sessionId, socketId: socket.id, vote: null, voter: true };
29 | }
30 | }
31 |
32 | Room.prototype.leave = function(socket) {
33 | var connection = _.find(this.connections, function(c) { return c.socketId === socket.id } );
34 | if (connection && connection.sessionId) {
35 | connection.socketId = null;
36 | }
37 | }
38 |
39 | Room.prototype.setCardPack = function(data) {
40 | this.cardPack = data.cardPack;
41 | this.io.sockets.in(this.roomUrl).emit('card pack set');
42 | // console.log('card pack set');
43 | }
44 |
45 | Room.prototype.toggleVoter = function(data) {
46 | if (this.connections[data.sessionId]) {
47 | this.connections[data.sessionId]['voter'] = data.voter;
48 | if (!data.voter) {
49 | this.connections[data.sessionId]['vote'] = null;
50 | }
51 | // console.log("voter set to " + data.voter + " for " + data.sessionId);
52 | }
53 | this.io.sockets.in(this.roomUrl).emit('voter status changed');
54 | }
55 |
56 | Room.prototype.recordVote = function(socket, data) {
57 | if (this.connections[data.sessionId]) {
58 | this.connections[data.sessionId]['vote'] = data.vote;
59 | }
60 | socket.broadcast.to(this.roomUrl).emit('voted');
61 | // this.io.sockets.in(this.roomUrl).emit('voted');
62 | }
63 |
64 | Room.prototype.destroyVote = function(socket, data) {
65 | if (this.connections[data.sessionId]) {
66 | this.connections[data.sessionId]['vote'] = null;
67 | }
68 | socket.broadcast.to(this.roomUrl).emit('unvoted');
69 | // this.io.sockets.in(this.roomUrl).emit('unvoted');
70 | }
71 |
72 | Room.prototype.resetVote = function() {
73 | _.forEach(this.connections, function(c) {
74 | c.vote = null;
75 | })
76 | this.forcedReveal = false;
77 | this.alreadySorted = false;
78 | this.io.sockets.in(this.roomUrl).emit('vote reset');
79 | }
80 |
81 | Room.prototype.forceReveal = function() {
82 | this.forcedReveal = true;
83 | this.alreadySorted = false;
84 | this.io.sockets.in(this.roomUrl).emit('reveal');
85 | }
86 |
87 | Room.prototype.sortVotes = function() {
88 | this.alreadySorted = true;
89 | this.io.sockets.in(this.roomUrl).emit('reveal');
90 | }
91 |
92 | Room.prototype.getClientCount = function() {
93 | return _.filter(this.connections, function(c) { return c.socketId }).length;
94 | }
95 |
96 | Room.prototype.json = function() {
97 | return {
98 | roomUrl: this.roomUrl,
99 | createdAt: this.createdAt,
100 | createAdmin: this.createAdmin,
101 | hasAdmin: this.hasAdmin,
102 | cardPack: this.cardPack,
103 | forcedReveal: this.forcedReveal,
104 | alreadySorted: this.alreadySorted,
105 | connections: _.filter(this.connections, function(c) { return c.socketId })
106 | };
107 | }
108 |
109 |
110 | function calcTime(offset) {
111 | // create Date object for current location
112 | d = new Date();
113 |
114 | // convert to msec
115 | // add local time zone offset
116 | // get UTC time in msec
117 | utc = d.getTime() + (d.getTimezoneOffset() * 60000);
118 |
119 | // create new Date object for different place
120 | // using supplied offset
121 | nd = new Date(utc + (3600000*offset));
122 |
123 | // return time as a string
124 | return nd.toLocaleString();
125 | }
126 |
127 |
128 | exports.Room = Room;
--------------------------------------------------------------------------------
/app/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 |
19 | Hatjitsu :: Online Scrum Planning Poker for Agile Projects
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | <%- CDN([ '/lib/modernizr.custom.07116.js', '/js/testing.js' ]) %>
35 |
36 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | Hatjitsu
57 |
58 |
61 |
62 |
63 |
64 |
65 | Activity…
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
78 |
79 |
80 |
81 |
82 |
87 |
88 | <%- CDN([ '/lib/underscore.min.js', '/lib/jquery.cookie.js' ]) %>
89 | <%- CDN([ '/js/app.js', '/js/controllers.js', '/js/directives.js', '/js/filters.js', '/js/services.js', '/js/dom.js']) %>
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/test/lib/jasmine/index.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var sys = require('sys');
3 | var path = require('path');
4 |
5 | var filename = __dirname + '/jasmine.js';
6 | global.window = {
7 | setTimeout: setTimeout,
8 | clearTimeout: clearTimeout,
9 | setInterval: setInterval,
10 | clearInterval: clearInterval
11 | };
12 | var src = fs.readFileSync(filename);
13 | var jasmine = process.compile(src + '\njasmine;', filename);
14 | delete global.window;
15 |
16 | function noop(){}
17 |
18 | jasmine.executeSpecsInFolder = function(folder, done, isVerbose, showColors, matcher){
19 | var log = [];
20 | var columnCounter = 0;
21 | var start = 0;
22 | var elapsed = 0;
23 | var verbose = isVerbose || false;
24 | var fileMatcher = new RegExp(matcher || "\.js$");
25 | var colors = showColors || false;
26 | var specs = jasmine.getAllSpecFiles(folder, fileMatcher);
27 |
28 | var ansi = {
29 | green: '\033[32m',
30 | red: '\033[31m',
31 | yellow: '\033[33m',
32 | none: '\033[0m'
33 | };
34 |
35 | for (var i = 0, len = specs.length; i < len; ++i){
36 | var filename = specs[i];
37 | require(filename.replace(/\.*$/, ""));
38 | }
39 |
40 | var jasmineEnv = jasmine.getEnv();
41 | jasmineEnv.reporter = {
42 | log: function(str){
43 | },
44 |
45 | reportSpecStarting: function(runner) {
46 | },
47 |
48 | reportRunnerStarting: function(runner) {
49 | sys.puts('Started');
50 | start = Number(new Date);
51 | },
52 |
53 | reportSuiteResults: function(suite) {
54 | var specResults = suite.results();
55 | var path = [];
56 | while(suite) {
57 | path.unshift(suite.description);
58 | suite = suite.parentSuite;
59 | }
60 | var description = path.join(' ');
61 |
62 | if (verbose)
63 | log.push('Spec ' + description);
64 |
65 | specResults.items_.forEach(function(spec){
66 | if (spec.failedCount > 0 && spec.description) {
67 | if (!verbose)
68 | log.push(description);
69 | log.push(' it ' + spec.description);
70 | spec.items_.forEach(function(result){
71 | log.push(' ' + result.trace.stack + '\n');
72 | });
73 | }
74 | });
75 | },
76 |
77 | reportSpecResults: function(spec) {
78 | var result = spec.results();
79 | var msg = '';
80 | if (result.passed())
81 | {
82 | msg = (colors) ? (ansi.green + '.' + ansi.none) : '.';
83 | // } else if (result.skipped) { TODO: Research why "result.skipped" returns false when "xit" is called on a spec?
84 | // msg = (colors) ? (ansi.yellow + '*' + ansi.none) : '*';
85 | } else {
86 | msg = (colors) ? (ansi.red + 'F' + ansi.none) : 'F';
87 | }
88 | sys.print(msg);
89 | if (columnCounter++ < 50) return;
90 | columnCounter = 0;
91 | sys.print('\n');
92 | },
93 |
94 |
95 | reportRunnerResults: function(runner) {
96 | elapsed = (Number(new Date) - start) / 1000;
97 | sys.puts('\n');
98 | log.forEach(function(log){
99 | sys.puts(log);
100 | });
101 | sys.puts('Finished in ' + elapsed + ' seconds');
102 |
103 | var summary = jasmine.printRunnerResults(runner);
104 | if(colors)
105 | {
106 | if(runner.results().failedCount === 0 )
107 | sys.puts(ansi.green + summary + ansi.none);
108 | else
109 | sys.puts(ansi.red + summary + ansi.none);
110 | } else {
111 | sys.puts(summary);
112 | }
113 | (done||noop)(runner, log);
114 | }
115 | };
116 | jasmineEnv.execute();
117 | };
118 |
119 | jasmine.getAllSpecFiles = function(dir, matcher){
120 | var specs = [];
121 |
122 | if (fs.statSync(dir).isFile() && dir.match(matcher)) {
123 | specs.push(dir);
124 | } else {
125 | var files = fs.readdirSync(dir);
126 | for (var i = 0, len = files.length; i < len; ++i){
127 | var filename = dir + '/' + files[i];
128 | if (fs.statSync(filename).isFile() && filename.match(matcher)){
129 | specs.push(filename);
130 | }else if (fs.statSync(filename).isDirectory()){
131 | var subfiles = this.getAllSpecFiles(filename, matcher);
132 | subfiles.forEach(function(result){
133 | specs.push(result);
134 | });
135 | }
136 | }
137 | }
138 |
139 | return specs;
140 | };
141 |
142 | jasmine.printRunnerResults = function(runner){
143 | var results = runner.results();
144 | var suites = runner.suites();
145 | var msg = '';
146 | msg += suites.length + ' test' + ((suites.length === 1) ? '' : 's') + ', ';
147 | msg += results.totalCount + ' assertion' + ((results.totalCount === 1) ? '' : 's') + ', ';
148 | msg += results.failedCount + ' failure' + ((results.failedCount === 1) ? '' : 's') + '\n';
149 | return msg;
150 | };
151 |
152 | function now(){
153 | return new Date().getTime();
154 | }
155 |
156 | jasmine.asyncSpecWait = function(){
157 | var wait = jasmine.asyncSpecWait;
158 | wait.start = now();
159 | wait.done = false;
160 | (function innerWait(){
161 | waits(10);
162 | runs(function() {
163 | if (wait.start + wait.timeout < now()) {
164 | expect('timeout waiting for spec').toBeNull();
165 | } else if (wait.done) {
166 | wait.done = false;
167 | } else {
168 | innerWait();
169 | }
170 | });
171 | })();
172 | };
173 | jasmine.asyncSpecWait.timeout = 4 * 1000;
174 | jasmine.asyncSpecDone = function(){
175 | jasmine.asyncSpecWait.done = true;
176 | };
177 |
178 | for ( var key in jasmine) {
179 | exports[key] = jasmine[key];
180 | }
--------------------------------------------------------------------------------
/app/js/prism.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Prism: Lightweight, robust, elegant syntax highlighting
3 | * MIT license http://www.opensource.org/licenses/mit-license.php/
4 | * @author Lea Verou http://lea.verou.me
5 | */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(//g,">").replace(/\u00a0/g," ");var l={element:r,language:o,grammar:u,code:f};t.hooks.run("before-highlight",l);if(i&&self.Worker){var c=new Worker(t.filename);c.onmessage=function(e){l.highlightedCode=n.stringify(JSON.parse(e.data));l.element.innerHTML=l.highlightedCode;s&&s.call(l.element);t.hooks.run("after-highlight",l)};c.postMessage(JSON.stringify({language:l.language,code:l.code}))}else{l.highlightedCode=t.highlight(l.code,l.grammar);l.element.innerHTML=l.highlightedCode;s&&s.call(r);t.hooks.run("after-highlight",l)}},highlight:function(e,r){return n.stringify(t.tokenize(e,r))},tokenize:function(e,n){var r=t.Token,i=[e],s=n.rest;if(s){for(var o in s)n[o]=s[o];delete n.rest}e:for(var o in n){if(!n.hasOwnProperty(o)||!n[o])continue;var u=n[o],a=u.inside,f=!!u.lookbehind||0;u=u.pattern||u;for(var l=0;le.length)break e;if(c instanceof r)continue;u.lastIndex=0;var h=u.exec(c);if(h){f&&(f=h[1].length);var p=h.index-1+f,h=h[0].slice(f),d=h.length,v=p+d,m=c.slice(0,p+1),g=c.slice(v+1),y=[l,1];m&&y.push(m);var b=new r(o,a?t.tokenize(h,a):h);y.push(b);g&&y.push(g);Array.prototype.splice.apply(i,y)}}}return i},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]"){for(var r=0;r"+i.content+""+i.tag+">"};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();;
6 | Prism.languages.markup={comment:/<!--[\w\W]*?--(>|>)/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]+?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});;
7 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//g,atrule:/@[\w-]+?(\s+[^;{]+)?(?=\s*{|\s*;)/gi,url:/url\((["']?).*?\1\)/gi,selector:/[^\{\}\s][^\{\}]*(?=\s*\{)/g,property:/(\b|\B)[a-z-]+(?=\s*:)/ig,string:/("|')(\\?.)*?\1/g,important:/\B!important\b/gi,ignore:/&(lt|gt|amp);/gi,punctuation:/[\{\};:]/g};Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<|<)style[\w\W]*?(>|>)[\w\W]*?(<|<)\/style(>|>)/ig,inside:{tag:{pattern:/(<|<)style[\w\W]*?(>|>)|(<|<)\/style(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css}}});;
8 | Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,number:/\b-?(0x)?\d*\.?[\da-f]+\b/g,operator:/[-+]{1,2}|!|=?<|=?>|={1,2}|(&){1,2}|\|?\||\?|\*|\//g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};;
9 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|new|with|typeof|try|catch|finally|null|break|continue)\b/g,number:/\b(-?(0x)?\d*\.?[\da-f]+|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});;
10 |
--------------------------------------------------------------------------------
/test/lib/angular/jstd-scenario-adapter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license AngularJS v1.0.0rc1
3 | * (c) 2010-2011 AngularJS http://angularjs.org
4 | * License: MIT
5 | */
6 | (function(window) {
7 | 'use strict';
8 |
9 | /**
10 | * JSTestDriver adapter for angular scenario tests
11 | *
12 | * Example of jsTestDriver.conf for running scenario tests with JSTD:
13 |
14 | server: http://localhost:9877
15 |
16 | load:
17 | - lib/angular-scenario.js
18 | - lib/jstd-scenario-adapter-config.js
19 | - lib/jstd-scenario-adapter.js
20 | # your test files go here #
21 |
22 | proxy:
23 | - {matcher: "/your-prefix/*", server: "http://localhost:8000/"}
24 |
25 | *
26 | * For more information on how to configure jstd proxy, see {@link http://code.google.com/p/js-test-driver/wiki/Proxy}
27 | * Note the order of files - it's important !
28 | *
29 | * Example of jstd-scenario-adapter-config.js
30 |
31 | var jstdScenarioAdapter = {
32 | relativeUrlPrefix: '/your-prefix/'
33 | };
34 |
35 | *
36 | * Whenever you use browser().navigateTo('relativeUrl') in your scenario test, the relativeUrlPrefix will be prepended.
37 | * You have to configure this to work together with JSTD proxy.
38 | *
39 | * Let's assume you are using the above configuration (jsTestDriver.conf and jstd-scenario-adapter-config.js):
40 | * Now, when you call browser().navigateTo('index.html') in your scenario test, the browser will open /your-prefix/index.html.
41 | * That matches the proxy, so JSTD will proxy this request to http://localhost:8000/index.html.
42 | */
43 |
44 | /**
45 | * Custom type of test case
46 | *
47 | * @const
48 | * @see jstestdriver.TestCaseInfo
49 | */
50 | var SCENARIO_TYPE = 'scenario';
51 |
52 | /**
53 | * Plugin for JSTestDriver
54 | * Connection point between scenario's jstd output and jstestdriver.
55 | *
56 | * @see jstestdriver.PluginRegistrar
57 | */
58 | function JstdPlugin() {
59 | var nop = function() {};
60 |
61 | this.reportResult = nop;
62 | this.reportEnd = nop;
63 | this.runScenario = nop;
64 |
65 | this.name = 'Angular Scenario Adapter';
66 |
67 | /**
68 | * Called for each JSTD TestCase
69 | *
70 | * Handles only SCENARIO_TYPE test cases. There should be only one fake TestCase.
71 | * Runs all scenario tests (under one fake TestCase) and report all results to JSTD.
72 | *
73 | * @param {jstestdriver.TestRunConfiguration} configuration
74 | * @param {Function} onTestDone
75 | * @param {Function} onAllTestsComplete
76 | * @returns {boolean} True if this type of test is handled by this plugin, false otherwise
77 | */
78 | this.runTestConfiguration = function(configuration, onTestDone, onAllTestsComplete) {
79 | if (configuration.getTestCaseInfo().getType() != SCENARIO_TYPE) return false;
80 |
81 | this.reportResult = onTestDone;
82 | this.reportEnd = onAllTestsComplete;
83 | this.runScenario();
84 |
85 | return true;
86 | };
87 |
88 | this.getTestRunsConfigurationFor = function(testCaseInfos, expressions, testRunsConfiguration) {
89 | testRunsConfiguration.push(
90 | new jstestdriver.TestRunConfiguration(
91 | new jstestdriver.TestCaseInfo(
92 | 'Angular Scenario Tests', function() {}, SCENARIO_TYPE), []));
93 |
94 | return true;
95 | };
96 | }
97 |
98 | /**
99 | * Singleton instance of the plugin
100 | * Accessed using closure by:
101 | * - jstd output (reports to this plugin)
102 | * - initScenarioAdapter (register the plugin to jstd)
103 | */
104 | var plugin = new JstdPlugin();
105 |
106 | /**
107 | * Initialise scenario jstd-adapter
108 | * (only if jstestdriver is defined)
109 | *
110 | * @param {Object} jstestdriver Undefined when run from browser (without jstd)
111 | * @param {Function} initScenarioAndRun Function that inits scenario and runs all the tests
112 | * @param {Object=} config Configuration object, supported properties:
113 | * - relativeUrlPrefix: prefix for all relative links when navigateTo()
114 | */
115 | function initScenarioAdapter(jstestdriver, initScenarioAndRun, config) {
116 | if (jstestdriver) {
117 | // create and register ScenarioPlugin
118 | jstestdriver.pluginRegistrar.register(plugin);
119 | plugin.runScenario = initScenarioAndRun;
120 |
121 | /**
122 | * HACK (angular.scenario.Application.navigateTo)
123 | *
124 | * We need to navigate to relative urls when running from browser (without JSTD),
125 | * because we want to allow running scenario tests without creating its own virtual host.
126 | * For example: http://angular.local/build/docs/docs-scenario.html
127 | *
128 | * On the other hand, when running with JSTD, we need to navigate to absolute urls,
129 | * because of JSTD proxy. (proxy, because of same domain policy)
130 | *
131 | * So this hack is applied only if running with JSTD and change all relative urls to absolute.
132 | */
133 | var appProto = angular.scenario.Application.prototype,
134 | navigateTo = appProto.navigateTo,
135 | relativeUrlPrefix = config && config.relativeUrlPrefix || '/';
136 |
137 | appProto.navigateTo = function(url, loadFn, errorFn) {
138 | if (url.charAt(0) != '/' && url.charAt(0) != '#' &&
139 | url != 'about:blank' && !url.match(/^https?/)) {
140 | url = relativeUrlPrefix + url;
141 | }
142 |
143 | return navigateTo.call(this, url, loadFn, errorFn);
144 | };
145 | }
146 | }
147 |
148 | /**
149 | * Builds proper TestResult object from given model spec
150 | *
151 | * TODO(vojta) report error details
152 | *
153 | * @param {angular.scenario.ObjectModel.Spec} spec
154 | * @returns {jstestdriver.TestResult}
155 | */
156 | function createTestResultFromSpec(spec) {
157 | var map = {
158 | success: 'PASSED',
159 | error: 'ERROR',
160 | failure: 'FAILED'
161 | };
162 |
163 | return new jstestdriver.TestResult(
164 | spec.fullDefinitionName,
165 | spec.name,
166 | jstestdriver.TestResult.RESULT[map[spec.status]],
167 | spec.error || '',
168 | spec.line || '',
169 | spec.duration);
170 | }
171 |
172 | /**
173 | * Generates JSTD output (jstestdriver.TestResult)
174 | */
175 | angular.scenario.output('jstd', function(context, runner, model) {
176 | model.on('SpecEnd', function(spec) {
177 | plugin.reportResult(createTestResultFromSpec(spec));
178 | });
179 |
180 | model.on('RunnerEnd', function() {
181 | plugin.reportEnd();
182 | });
183 | });
184 | initScenarioAdapter(window.jstestdriver, angular.scenario.setUpAndRun, window.jstdScenarioAdapter);
185 | })(window);
186 |
--------------------------------------------------------------------------------
/test/lib/jasmine-jstd-adapter/JasmineAdapter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Jasmine JsTestDriver Adapter.
3 | * @author misko@hevery.com (Misko Hevery)
4 | */
5 | (function(window) {
6 | var rootDescribes = new Describes(window);
7 | rootDescribes.collectMode();
8 |
9 | var JASMINE_TYPE = 'jasmine test case';
10 | TestCase('Jasmine Adapter Tests', null, JASMINE_TYPE);
11 |
12 | var jasminePlugin = {
13 | name:'jasmine',
14 |
15 | getTestRunsConfigurationFor: function(testCaseInfos, expressions, testRunsConfiguration) {
16 | for (var i = 0; i < testCaseInfos.length; i++) {
17 | if (testCaseInfos[i].getType() == JASMINE_TYPE) {
18 | testRunsConfiguration.push(new jstestdriver.TestRunConfiguration(testCaseInfos[i], []));
19 | }
20 | }
21 | return false;
22 | },
23 |
24 | runTestConfiguration: function(testRunConfiguration, onTestDone, onTestRunConfigurationComplete) {
25 | if (testRunConfiguration.getTestCaseInfo().getType() != JASMINE_TYPE) return false;
26 |
27 | var jasmineEnv = jasmine.currentEnv_ = new jasmine.Env();
28 | rootDescribes.playback();
29 | var specLog = jstestdriver.console.log_ = [];
30 | var start;
31 | jasmineEnv.specFilter = function(spec) {
32 | return rootDescribes.isExclusive(spec);
33 | };
34 | jasmineEnv.reporter = {
35 | log: function(str) {
36 | specLog.push(str);
37 | },
38 |
39 | reportRunnerStarting: function(runner) { },
40 |
41 | reportSpecStarting: function(spec) {
42 | specLog = jstestdriver.console.log_ = [];
43 | start = new Date().getTime();
44 | },
45 |
46 | reportSpecResults: function(spec) {
47 | var suite = spec.suite;
48 | var results = spec.results();
49 | if (results.skipped) return;
50 | var end = new Date().getTime();
51 | var messages = [];
52 | var resultItems = results.getItems();
53 | var state = 'passed';
54 | for ( var i = 0; i < resultItems.length; i++) {
55 | if (!resultItems[i].passed()) {
56 | state = resultItems[i].message.match(/AssertionError:/) ? 'error' : 'failed';
57 | messages.push({
58 | message: resultItems[i].toString(),
59 | name: resultItems[i].trace.name,
60 | stack: formatStack(resultItems[i].trace.stack)
61 | });
62 | }
63 | }
64 | onTestDone(
65 | new jstestdriver.TestResult(
66 | suite.getFullName(),
67 | spec.description,
68 | state,
69 | jstestdriver.angular.toJson(messages),
70 | specLog.join('\n'),
71 | end - start));
72 | },
73 |
74 | reportSuiteResults: function(suite) {},
75 |
76 | reportRunnerResults: function(runner) {
77 | onTestRunConfigurationComplete();
78 | }
79 | };
80 | jasmineEnv.execute();
81 | return true;
82 | },
83 |
84 | onTestsFinish: function() {
85 | jasmine.currentEnv_ = null;
86 | rootDescribes.collectMode();
87 | }
88 | };
89 | jstestdriver.pluginRegistrar.register(jasminePlugin);
90 |
91 | function formatStack(stack) {
92 | var lines = (stack||'').split(/\r?\n/);
93 | var frames = [];
94 | for (var i = 0; i < lines.length; i++) {
95 | if (!lines[i].match(/\/jasmine[\.-]/)) {
96 | frames.push(lines[i].replace(/https?:\/\/\w+(:\d+)?\/test\//, '').replace(/^\s*/, ' '));
97 | }
98 | }
99 | return frames.join('\n');
100 | }
101 |
102 | function noop() {}
103 | function Describes(window) {
104 | var describes = {};
105 | var beforeEachs = {};
106 | var afterEachs = {};
107 | // Here we store:
108 | // 0: everyone runs
109 | // 1: run everything under ddescribe
110 | // 2: run only iits (ignore ddescribe)
111 | var exclusive = 0;
112 | var collectMode = true;
113 | intercept('describe', describes);
114 | intercept('xdescribe', describes);
115 | intercept('beforeEach', beforeEachs);
116 | intercept('afterEach', afterEachs);
117 |
118 | function intercept(functionName, collection) {
119 | window[functionName] = function(desc, fn) {
120 | if (collectMode) {
121 | collection[desc] = function() {
122 | jasmine.getEnv()[functionName](desc, fn);
123 | };
124 | } else {
125 | jasmine.getEnv()[functionName](desc, fn);
126 | }
127 | };
128 | }
129 | window.ddescribe = function(name, fn) {
130 | if (exclusive < 1) {
131 | exclusive = 1; // run ddescribe only
132 | }
133 | window.describe(name, function() {
134 | var oldIt = window.it;
135 | window.it = function(name, fn) {
136 | fn.exclusive = 1; // run anything under ddescribe
137 | jasmine.getEnv().it(name, fn);
138 | };
139 | try {
140 | fn.call(this);
141 | } finally {
142 | window.it = oldIt;
143 | };
144 | });
145 | };
146 | window.iit = function(name, fn) {
147 | exclusive = fn.exclusive = 2; // run only iits
148 | jasmine.getEnv().it(name, fn);
149 | };
150 |
151 |
152 | this.collectMode = function() {
153 | collectMode = true;
154 | exclusive = 0; // run everything
155 | };
156 | this.playback = function() {
157 | collectMode = false;
158 | playback(beforeEachs);
159 | playback(afterEachs);
160 | playback(describes);
161 |
162 | function playback(set) {
163 | for ( var name in set) {
164 | set[name]();
165 | }
166 | }
167 | };
168 |
169 | this.isExclusive = function(spec) {
170 | if (exclusive) {
171 | var blocks = spec.queue.blocks;
172 | for ( var i = 0; i < blocks.length; i++) {
173 | if (blocks[i].func.exclusive >= exclusive) {
174 | return true;
175 | }
176 | }
177 | return false;
178 | }
179 | return true;
180 | };
181 | }
182 |
183 | })(window);
184 |
185 | // Patch Jasmine for proper stack traces
186 | jasmine.Spec.prototype.fail = function (e) {
187 | var expectationResult = new jasmine.ExpectationResult({
188 | passed: false,
189 | message: e ? jasmine.util.formatException(e) : 'Exception'
190 | });
191 | // PATCH
192 | if (e) {
193 | expectationResult.trace = e;
194 | }
195 | this.results_.addResult(expectationResult);
196 | };
197 |
--------------------------------------------------------------------------------
/scripts/web-server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var sys = require('sys'),
4 | http = require('http'),
5 | fs = require('fs'),
6 | url = require('url'),
7 | events = require('events');
8 |
9 | var DEFAULT_PORT = 8000;
10 |
11 | function main(argv) {
12 | new HttpServer({
13 | 'GET': createServlet(StaticServlet),
14 | 'HEAD': createServlet(StaticServlet)
15 | }).start(Number(argv[2]) || DEFAULT_PORT);
16 | }
17 |
18 | function escapeHtml(value) {
19 | return value.toString().
20 | replace('<', '<').
21 | replace('>', '>').
22 | replace('"', '"');
23 | }
24 |
25 | function createServlet(Class) {
26 | var servlet = new Class();
27 | return servlet.handleRequest.bind(servlet);
28 | }
29 |
30 | /**
31 | * An Http server implementation that uses a map of methods to decide
32 | * action routing.
33 | *
34 | * @param {Object} Map of method => Handler function
35 | */
36 | function HttpServer(handlers) {
37 | this.handlers = handlers;
38 | this.server = http.createServer(this.handleRequest_.bind(this));
39 | }
40 |
41 | HttpServer.prototype.start = function(port) {
42 | this.port = port;
43 | this.server.listen(port);
44 | sys.puts('Http Server running at http://localhost:' + port + '/');
45 | };
46 |
47 | HttpServer.prototype.parseUrl_ = function(urlString) {
48 | var parsed = url.parse(urlString);
49 | parsed.pathname = url.resolve('/', parsed.pathname);
50 | return url.parse(url.format(parsed), true);
51 | };
52 |
53 | HttpServer.prototype.handleRequest_ = function(req, res) {
54 | var logEntry = req.method + ' ' + req.url;
55 | if (req.headers['user-agent']) {
56 | logEntry += ' ' + req.headers['user-agent'];
57 | }
58 | sys.puts(logEntry);
59 | req.url = this.parseUrl_(req.url);
60 | var handler = this.handlers[req.method];
61 | if (!handler) {
62 | res.writeHead(501);
63 | res.end();
64 | } else {
65 | handler.call(this, req, res);
66 | }
67 | };
68 |
69 | /**
70 | * Handles static content.
71 | */
72 | function StaticServlet() {}
73 |
74 | StaticServlet.MimeMap = {
75 | 'txt': 'text/plain',
76 | 'html': 'text/html',
77 | 'css': 'text/css',
78 | 'xml': 'application/xml',
79 | 'json': 'application/json',
80 | 'js': 'application/javascript',
81 | 'jpg': 'image/jpeg',
82 | 'jpeg': 'image/jpeg',
83 | 'gif': 'image/gif',
84 | 'png': 'image/png'
85 | };
86 |
87 | StaticServlet.prototype.handleRequest = function(req, res) {
88 | var self = this;
89 | var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/, function(match, hex){
90 | return String.fromCharCode(parseInt(hex, 16));
91 | });
92 | var parts = path.split('/');
93 | if (parts[parts.length-1].charAt(0) === '.')
94 | return self.sendForbidden_(req, res, path);
95 | fs.stat(path, function(err, stat) {
96 | if (err)
97 | return self.sendMissing_(req, res, path);
98 | if (stat.isDirectory())
99 | return self.sendDirectory_(req, res, path);
100 | return self.sendFile_(req, res, path);
101 | });
102 | }
103 |
104 | StaticServlet.prototype.sendError_ = function(req, res, error) {
105 | res.writeHead(500, {
106 | 'Content-Type': 'text/html'
107 | });
108 | res.write('\n');
109 | res.write('Internal Server Error \n');
110 | res.write('Internal Server Error
');
111 | res.write('' + escapeHtml(sys.inspect(error)) + '
');
112 | sys.puts('500 Internal Server Error');
113 | sys.puts(sys.inspect(error));
114 | };
115 |
116 | StaticServlet.prototype.sendMissing_ = function(req, res, path) {
117 | path = path.substring(1);
118 | res.writeHead(404, {
119 | 'Content-Type': 'text/html'
120 | });
121 | res.write('\n');
122 | res.write('404 Not Found \n');
123 | res.write('Not Found
');
124 | res.write(
125 | 'The requested URL ' +
126 | escapeHtml(path) +
127 | ' was not found on this server.
'
128 | );
129 | res.end();
130 | sys.puts('404 Not Found: ' + path);
131 | };
132 |
133 | StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
134 | path = path.substring(1);
135 | res.writeHead(403, {
136 | 'Content-Type': 'text/html'
137 | });
138 | res.write('\n');
139 | res.write('403 Forbidden \n');
140 | res.write('Forbidden
');
141 | res.write(
142 | 'You do not have permission to access ' +
143 | escapeHtml(path) + ' on this server.
'
144 | );
145 | res.end();
146 | sys.puts('403 Forbidden: ' + path);
147 | };
148 |
149 | StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
150 | res.writeHead(301, {
151 | 'Content-Type': 'text/html',
152 | 'Location': redirectUrl
153 | });
154 | res.write('\n');
155 | res.write('301 Moved Permanently \n');
156 | res.write('Moved Permanently
');
157 | res.write(
158 | 'The document has moved here.
'
161 | );
162 | res.end();
163 | sys.puts('301 Moved Permanently: ' + redirectUrl);
164 | };
165 |
166 | StaticServlet.prototype.sendFile_ = function(req, res, path) {
167 | var self = this;
168 | var file = fs.createReadStream(path);
169 | res.writeHead(200, {
170 | 'Content-Type': StaticServlet.
171 | MimeMap[path.split('.').pop()] || 'text/plain'
172 | });
173 | if (req.method === 'HEAD') {
174 | res.end();
175 | } else {
176 | file.on('data', res.write.bind(res));
177 | file.on('close', function() {
178 | res.end();
179 | });
180 | file.on('error', function(error) {
181 | self.sendError_(req, res, error);
182 | });
183 | }
184 | };
185 |
186 | StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
187 | var self = this;
188 | if (path.match(/[^\/]$/)) {
189 | req.url.pathname += '/';
190 | var redirectUrl = url.format(url.parse(url.format(req.url)));
191 | return self.sendRedirect_(req, res, redirectUrl);
192 | }
193 | fs.readdir(path, function(err, files) {
194 | if (err)
195 | return self.sendError_(req, res, error);
196 |
197 | if (!files.length)
198 | return self.writeDirectoryIndex_(req, res, path, []);
199 |
200 | var remaining = files.length;
201 | files.forEach(function(fileName, index) {
202 | fs.stat(path + '/' + fileName, function(err, stat) {
203 | if (err)
204 | return self.sendError_(req, res, err);
205 | if (stat.isDirectory()) {
206 | files[index] = fileName + '/';
207 | }
208 | if (!(--remaining))
209 | return self.writeDirectoryIndex_(req, res, path, files);
210 | });
211 | });
212 | });
213 | };
214 |
215 | StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
216 | path = path.substring(1);
217 | res.writeHead(200, {
218 | 'Content-Type': 'text/html'
219 | });
220 | if (req.method === 'HEAD') {
221 | res.end();
222 | return;
223 | }
224 | res.write('\n');
225 | res.write('' + escapeHtml(path) + ' \n');
226 | res.write('\n');
229 | res.write('Directory: ' + escapeHtml(path) + '
');
230 | res.write('');
231 | files.forEach(function(fileName) {
232 | if (fileName.charAt(0) !== '.') {
233 | res.write('- ' +
235 | escapeHtml(fileName) + '
');
236 | }
237 | });
238 | res.write('
');
239 | res.end();
240 | };
241 |
242 | // Must be last,
243 | main(process.argv);
244 |
--------------------------------------------------------------------------------
/test/lib/jasmine/jasmine-html.js:
--------------------------------------------------------------------------------
1 | jasmine.TrivialReporter = function(doc) {
2 | this.document = doc || document;
3 | this.suiteDivs = {};
4 | this.logRunningSpecs = false;
5 | };
6 |
7 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
8 | var el = document.createElement(type);
9 |
10 | for (var i = 2; i < arguments.length; i++) {
11 | var child = arguments[i];
12 |
13 | if (typeof child === 'string') {
14 | el.appendChild(document.createTextNode(child));
15 | } else {
16 | if (child) { el.appendChild(child); }
17 | }
18 | }
19 |
20 | for (var attr in attrs) {
21 | if (attr == "className") {
22 | el[attr] = attrs[attr];
23 | } else {
24 | el.setAttribute(attr, attrs[attr]);
25 | }
26 | }
27 |
28 | return el;
29 | };
30 |
31 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
32 | var showPassed, showSkipped;
33 |
34 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
35 | this.createDom('div', { className: 'banner' },
36 | this.createDom('div', { className: 'logo' },
37 | this.createDom('span', { className: 'title' }, "Jasmine"),
38 | this.createDom('span', { className: 'version' }, runner.env.versionString())),
39 | this.createDom('div', { className: 'options' },
40 | "Show ",
41 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
42 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
43 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
45 | )
46 | ),
47 |
48 | this.runnerDiv = this.createDom('div', { className: 'runner running' },
49 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
50 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
51 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
52 | );
53 |
54 | this.document.body.appendChild(this.outerDiv);
55 |
56 | var suites = runner.suites();
57 | for (var i = 0; i < suites.length; i++) {
58 | var suite = suites[i];
59 | var suiteDiv = this.createDom('div', { className: 'suite' },
60 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
61 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
62 | this.suiteDivs[suite.id] = suiteDiv;
63 | var parentDiv = this.outerDiv;
64 | if (suite.parentSuite) {
65 | parentDiv = this.suiteDivs[suite.parentSuite.id];
66 | }
67 | parentDiv.appendChild(suiteDiv);
68 | }
69 |
70 | this.startedAt = new Date();
71 |
72 | var self = this;
73 | showPassed.onclick = function(evt) {
74 | if (showPassed.checked) {
75 | self.outerDiv.className += ' show-passed';
76 | } else {
77 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
78 | }
79 | };
80 |
81 | showSkipped.onclick = function(evt) {
82 | if (showSkipped.checked) {
83 | self.outerDiv.className += ' show-skipped';
84 | } else {
85 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
86 | }
87 | };
88 | };
89 |
90 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
91 | var results = runner.results();
92 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
93 | this.runnerDiv.setAttribute("class", className);
94 | //do it twice for IE
95 | this.runnerDiv.setAttribute("className", className);
96 | var specs = runner.specs();
97 | var specCount = 0;
98 | for (var i = 0; i < specs.length; i++) {
99 | if (this.specFilter(specs[i])) {
100 | specCount++;
101 | }
102 | }
103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
106 |
107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
108 | };
109 |
110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
111 | var results = suite.results();
112 | var status = results.passed() ? 'passed' : 'failed';
113 | if (results.totalCount === 0) { // todo: change this to check results.skipped
114 | status = 'skipped';
115 | }
116 | this.suiteDivs[suite.id].className += " " + status;
117 | };
118 |
119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
120 | if (this.logRunningSpecs) {
121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
122 | }
123 | };
124 |
125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
126 | var results = spec.results();
127 | var status = results.passed() ? 'passed' : 'failed';
128 | if (results.skipped) {
129 | status = 'skipped';
130 | }
131 | var specDiv = this.createDom('div', { className: 'spec ' + status },
132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
133 | this.createDom('a', {
134 | className: 'description',
135 | href: '?spec=' + encodeURIComponent(spec.getFullName()),
136 | title: spec.getFullName()
137 | }, spec.description));
138 |
139 |
140 | var resultItems = results.getItems();
141 | var messagesDiv = this.createDom('div', { className: 'messages' });
142 | for (var i = 0; i < resultItems.length; i++) {
143 | var result = resultItems[i];
144 |
145 | if (result.type == 'log') {
146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
147 | } else if (result.type == 'expect' && result.passed && !result.passed()) {
148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
149 |
150 | if (result.trace.stack) {
151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
152 | }
153 | }
154 | }
155 |
156 | if (messagesDiv.childNodes.length > 0) {
157 | specDiv.appendChild(messagesDiv);
158 | }
159 |
160 | this.suiteDivs[spec.suite.id].appendChild(specDiv);
161 | };
162 |
163 | jasmine.TrivialReporter.prototype.log = function() {
164 | var console = jasmine.getGlobal().console;
165 | if (console && console.log) {
166 | if (console.log.apply) {
167 | console.log.apply(console, arguments);
168 | } else {
169 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
170 | }
171 | }
172 | };
173 |
174 | jasmine.TrivialReporter.prototype.getLocation = function() {
175 | return this.document.location;
176 | };
177 |
178 | jasmine.TrivialReporter.prototype.specFilter = function(spec) {
179 | var paramMap = {};
180 | var params = this.getLocation().search.substring(1).split('&');
181 | for (var i = 0; i < params.length; i++) {
182 | var p = params[i].split('=');
183 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
184 | }
185 |
186 | if (!paramMap.spec) {
187 | return true;
188 | }
189 | return spec.getFullName().indexOf(paramMap.spec) === 0;
190 | };
191 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 | var _ = require('underscore')._;
6 |
7 | var env = process.env.NODE_ENV || 'development';
8 |
9 | var express = require('express'),
10 | fs = require('fs');
11 |
12 | var app = module.exports = express.createServer();
13 | var io = require('socket.io').listen(app);
14 | var lobbyClass = require('./lib/lobby.js');
15 | var config = require('./config.js')[env];
16 | var path = require('path');
17 |
18 | var gzippo = require('gzippo');
19 |
20 | var lobby = new lobbyClass.Lobby(io);
21 |
22 | var statsConnectionCount = 0;
23 | var statsDisconnectCount = 0;
24 | var statsSocketCount = 0;
25 | var statsSocketMessagesReceived = 0;
26 |
27 | // Configuration
28 |
29 | // Set the CDN options
30 | var options = {
31 | publicDir : path.join(__dirname, 'app')
32 | , viewsDir : path.join(__dirname, 'app')
33 | , domain : 'dkb4nwmyziz71.cloudfront.net'
34 | , bucket : 'hatchetapp'
35 | , key : 'AKIAIS3XCFXFKWXGKK7Q'
36 | , secret : '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'
37 | , hostname : config.hostname
38 | , port : config.port
39 | , ssl : false
40 | , production : config.packAssets
41 | };
42 |
43 | // Initialize the CDN magic
44 | var CDN = require('express-cdn')(app, options);
45 |
46 | app.configure(function(){
47 | app.set('views', __dirname + '/app');
48 | app.set('view engine', 'ejs');
49 | app.set('view options', {
50 | layout: false
51 | });
52 | app.use(express.logger());
53 | app.use(express.bodyParser());
54 | app.use(express.methodOverride());
55 | app.use(express.staticCache());
56 | });
57 |
58 | app.configure('development', function(){
59 | app.use(express.static(__dirname + '/app'));
60 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
61 | });
62 |
63 | app.configure('production', function(){
64 | var oneDay = 86400000;
65 | // app.use(assetsManagerMiddleware);
66 | app.use(gzippo.staticGzip(__dirname + '/app'));
67 | app.use(express.errorHandler());
68 | });
69 |
70 | // Add the dynamic view helper
71 | app.dynamicHelpers({ CDN: CDN });
72 |
73 | app.get('/', function(req, res) {
74 | res.render('index.ejs');
75 | });
76 |
77 | app.get('/debug_state', function(req, res) {
78 | res.json({
79 | "stats": {
80 | "connectionCount": statsConnectionCount,
81 | "disconnectCount": statsDisconnectCount,
82 | "currentSocketCount": statsSocketCount,
83 | "socketMessagesReceived": statsSocketMessagesReceived
84 | },
85 | "rooms": _.map(lobby.rooms, function(room, key) { return room.json() } )
86 | });
87 | });
88 |
89 | app.get('/styleguide', function(req, res) {
90 | res.render('styleguide.ejs');
91 | });
92 |
93 | app.get('/:id', function(req, res) {
94 | if (req.params.id in lobby.rooms) {
95 | res.render('index.ejs');
96 | } else {
97 | res.redirect('/');
98 | }
99 | });
100 |
101 |
102 | io.configure(function () {
103 | io.set('transports', ['websocket', 'htmlfile', 'xhr-polling', 'jsonp-polling']);
104 | });
105 |
106 | io.configure('production', function(){
107 | io.enable('browser client minification');
108 | io.enable('browser client etag');
109 | io.enable('browser client gzip');
110 | io.set("polling duration", 10);
111 | io.set('log level', 1);
112 | });
113 | io.configure('development', function(){
114 | io.set('log level', 2);
115 | });
116 |
117 | var port = process.env.app_port || 5000; // Use the port that Heroku provides or default to 5000
118 | app.listen(port, function() {
119 | console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
120 | });
121 |
122 |
123 |
124 |
125 | /* EVENT LISTENERS */
126 |
127 | io.sockets.on('connection', function (socket) {
128 |
129 | statsConnectionCount++;
130 | statsSocketCount++;
131 |
132 | // console.log("On connect", socket.id);
133 |
134 | socket.on('disconnect', function () {
135 | statsDisconnectCount++;
136 | statsSocketCount--;
137 | // console.log("On disconnect", socket.id);
138 | lobby.broadcastDisconnect(socket);
139 | });
140 |
141 | socket.on('create room', function (data, callback) {
142 | statsSocketMessagesReceived++;
143 | // console.log("on create room", socket.id, data);
144 | callback(lobby.createRoom());
145 | });
146 |
147 | socket.on('join room', function (data, callback) {
148 | statsSocketMessagesReceived++;
149 | // console.log("on join room " + data.roomUrl, socket.id, data);
150 | var room = lobby.joinRoom(socket, data);
151 | if(room.error) {
152 | callback( { error: room.error } );
153 | } else {
154 | callback(room.info());
155 | }
156 | });
157 |
158 | socket.on('room info', function (data, callback) {
159 | statsSocketMessagesReceived++;
160 | // console.log("on room info for " + data.roomUrl, socket.id, data);
161 | var room = lobby.getRoom(data.roomUrl);
162 | // room = { error: "there was an error" };
163 | if (room.error) {
164 | callback( { error: room.error } );
165 | } else {
166 | callback(room.info());
167 | }
168 | });
169 |
170 | socket.on('set card pack', function (data, cardPack) {
171 | statsSocketMessagesReceived++;
172 | // console.log("on set card pack " + data.cardPack + " for " + data.roomUrl, socket.id, data);
173 | var room = lobby.getRoom(data.roomUrl);
174 | // console.log("error=" + room.error);
175 | if (!room.error) {
176 | room.setCardPack(data);
177 | }
178 | });
179 |
180 | socket.on('vote', function (data, callback) {
181 | statsSocketMessagesReceived++;
182 | // console.log("on vote " + data.vote + " received for " + data.roomUrl, socket.id, data);
183 | var room = lobby.getRoom(data.roomUrl);
184 | if (room.error) {
185 | callback( { error: room.error });
186 | } else {
187 | room.recordVote(socket, data);
188 | callback( {} );
189 | }
190 | });
191 |
192 | socket.on('unvote', function (data, callback) {
193 | statsSocketMessagesReceived++;
194 | // console.log("omn unvote received for " + data.roomUrl, socket.id, data);
195 | var room = lobby.getRoom(data.roomUrl);
196 | if (room.error) {
197 | callback( { error: room.error });
198 | } else {
199 | room.destroyVote(socket, data);
200 | callback( {} );
201 | }
202 | });
203 |
204 | socket.on('reset vote', function (data, callback) {
205 | statsSocketMessagesReceived++;
206 | // console.log("on reset vote received for " + data.roomUrl, socket.id, data);
207 | var room = lobby.getRoom(data.roomUrl);
208 | if (room.error) {
209 | callback( { error: room.error });
210 | } else {
211 | room.resetVote();
212 | callback( {} );
213 | }
214 | });
215 |
216 | socket.on('force reveal', function (data, callback) {
217 | statsSocketMessagesReceived++;
218 | var room = lobby.getRoom(data.roomUrl);
219 | if (room.error) {
220 | callback( { error: room.error });
221 | } else {
222 | room.forceReveal();
223 | callback( {} );
224 | }
225 | });
226 |
227 | socket.on('sort votes', function (data, callback) {
228 | statsSocketMessagesReceived++;
229 | var room = lobby.getRoom(data.roomUrl);
230 | if (room.error) {
231 | callback( { error: room.error });
232 | } else {
233 | room.sortVotes();
234 | callback( {} );
235 | }
236 | });
237 |
238 | socket.on('toggle voter', function (data, callback) {
239 | statsSocketMessagesReceived++;
240 | // console.log("on toggle voter for " + data.roomUrl, socket.id, data);
241 | var room = lobby.getRoom(data.roomUrl);
242 | if (room.error) {
243 | callback( { error: room.error });
244 | } else {
245 | room.toggleVoter(data);
246 | callback( {} );
247 | }
248 | });
249 |
250 | });
--------------------------------------------------------------------------------
/app/lib/modernizr.custom.07116.js:
--------------------------------------------------------------------------------
1 | /* Modernizr 2.6.2 (Custom Build) | MIT & BSD
2 | * Build: http://modernizr.com/download/#-generatedcontent-csstransforms-csstransforms3d-csstransitions-websockets-svg-printshiv-mq-cssclasses-addtest-teststyles-testprop-testallprops-prefixes-domprefixes-load
3 | */
4 | ;window.Modernizr=function(a,b,c){function C(a){j.cssText=a}function D(a,b){return C(n.join(a+";")+(b||""))}function E(a,b){return typeof a===b}function F(a,b){return!!~(""+a).indexOf(b)}function G(a,b){for(var d in a){var e=a[d];if(!F(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function H(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:E(f,"function")?f.bind(d||b):f}return!1}function I(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return E(b,"string")||E(b,"undefined")?G(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),H(e,b,c))}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l=":)",m={}.toString,n=" -webkit- -moz- -o- -ms- ".split(" "),o="Webkit Moz O ms",p=o.split(" "),q=o.toLowerCase().split(" "),r={svg:"http://www.w3.org/2000/svg"},s={},t={},u={},v=[],w=v.slice,x,y=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["",'"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A={}.hasOwnProperty,B;!E(A,"undefined")&&!E(A.call,"undefined")?B=function(a,b){return A.call(a,b)}:B=function(a,b){return b in a&&E(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.csstransforms=function(){return!!I("transform")},s.csstransforms3d=function(){var a=!!I("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return I("transition")},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect};for(var J in s)B(s,J)&&(x=J.toLowerCase(),e[x]=s[J](),v.push((e[x]?"":"no-")+x));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)B(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},C(""),i=k=null,e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.testProp=function(a){return G([a])},e.testAllProps=I,e.testStyles=y,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e+~])("+l().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),f="$1"+t+"\\:$2";while(d--)b=c[d]=c[d].split("}"),b[b.length-1]=b[b.length-1].replace(e,f),c[d]=b.join("}");return c.join("{")}function y(a){var b=a.length;while(b--)a[b].removeNode()}function z(a){function g(){clearTimeout(d._removeSheetTimer),b&&b.removeNode(!0),b=null}var b,c,d=m(a),e=a.namespaces,f=a.parentWindow;return!u||a.printShived?a:(typeof e[t]=="undefined"&&e.add(t),f.attachEvent("onbeforeprint",function(){g();var d,e,f,h=a.styleSheets,i=[],j=h.length,l=Array(j);while(j--)l[j]=h[j];while(f=l.pop())if(!f.disabled&&s.test(f.media)){try{d=f.imports,e=d.length}catch(m){e=0}for(j=0;j",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b);var s=/^$|\b(?:all|print)\b/,t="html5shiv",u=!j&&function(){var c=b.documentElement;return typeof b.namespaces!="undefined"&&typeof b.parentWindow!="undefined"&&typeof c.applyElement!="undefined"&&typeof c.removeNode!="undefined"&&typeof a.attachEvent!="undefined"}();r.type+=" print",r.shivPrint=z,z(b)}(this,document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f2;a==null&&(a=[]);if(A&&
12 | a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
13 | c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
14 | a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
17 | j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
20 | i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
25 | c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
26 | function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
27 | b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
28 | b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
29 | function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
30 | u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
31 | b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
32 | this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
--------------------------------------------------------------------------------
/app/js/controllers.js:
--------------------------------------------------------------------------------
1 | /*jslint indent: 2, browser: true */
2 | /*global angular, _, $, ScrollIntoView, DropDown */
3 |
4 | 'use strict';
5 |
6 | /* Controllers */
7 |
8 | function MainCtrl($scope, $timeout) {
9 | $scope.logoState = '';
10 | $scope.errorMessage = null;
11 | $scope.message = null;
12 |
13 | $scope.$on('$routeChangeSuccess', function () {
14 | $scope.logoState = '';
15 | $scope.bodyState = '';
16 | });
17 | $scope.$on('unanimous vote', function () {
18 | $scope.logoState = ' header__logo--green';
19 | $scope.bodyState = ' body--green';
20 | });
21 | $scope.$on('not unanimous vote', function () {
22 | $scope.logoState = ' header__logo--yellow';
23 | $scope.bodyState = ' body--yellow';
24 | });
25 | $scope.$on('problem vote', function () {
26 | $scope.logoState = ' header__logo--red';
27 | $scope.bodyState = ' body--red';
28 | });
29 | $scope.$on('unfinished vote', function () {
30 | $scope.logoState = '';
31 | $scope.bodyState = '';
32 | });
33 |
34 | $scope.$on('show message', function (evnt, msg) {
35 | $scope.message = msg;
36 | $timeout(function () {
37 | $scope.message = null;
38 | }, 4000);
39 | });
40 | $scope.$on('show error', function (evnt, msg) {
41 | $scope.errorMessage = msg;
42 | $timeout(function () {
43 | $scope.errorMessage = null;
44 | }, 3000);
45 | });
46 | }
47 |
48 | MainCtrl.$inject = ['$scope', '$timeout'];
49 |
50 | function LobbyCtrl($scope, $location, socket) {
51 | $scope.disableButtons = false;
52 | $scope.createRoom = function () {
53 | // console.log('createRoom: emit create room');
54 | $scope.disableButtons = true;
55 | socket.emit('create room', {}, function (roomUrl) {
56 | $location.path(roomUrl);
57 | });
58 | };
59 | $scope.enterRoom = function (room) {
60 | // console.log('enterRoom: room info');
61 | $scope.disableButtons = true;
62 | socket.emit('room info', { roomUrl: room }, function (response) {
63 | if (response.error) {
64 | $scope.disableButtons = false;
65 | $scope.$emit('show error', response.error);
66 | } else {
67 | // console.log("going to enter room " + response.roomUrl);
68 | $location.path(response.roomUrl);
69 | }
70 | });
71 | };
72 | }
73 |
74 | LobbyCtrl.$inject = ['$scope', '$location', 'socket'];
75 |
76 | function standardDeviation(values, avg){
77 | var squareDiffs = values.map(function(value){
78 | var diff = value - avg;
79 | var sqrDiff = diff * diff;
80 | return sqrDiff;
81 | });
82 |
83 | var avgSquareDiff = average(squareDiffs);
84 |
85 | var stdDev = Math.sqrt(avgSquareDiff);
86 | return stdDev;
87 | }
88 |
89 | function average(data){
90 | var sum = data.reduce(function(sum, value){
91 | return sum + value;
92 | }, 0);
93 |
94 | var avg = sum / data.length;
95 | return avg;
96 | }
97 |
98 | function cardValue(vote){
99 | if (vote.match(/^[0-9]+$/)) {
100 | return parseFloat(vote);
101 | } else if (vote == '\u00BD') {
102 | return 0.5;
103 | } else if (vote == 'A\u2660') {
104 | return 1;
105 | } else if (vote == '\u2654') {
106 | return 13;
107 | }
108 | }
109 |
110 | function RoomCtrl($scope, $routeParams, $timeout, socket) {
111 |
112 | var processMessage = function (response, process) {
113 | // console.log("processMessage: response:", response)
114 | if (response.error) {
115 | $scope.$emit('show error', response.error);
116 | } else {
117 | (process || angular.noop)(response);
118 | }
119 | };
120 |
121 | var sumOfTwo = function (a, b) {
122 | return a + b;
123 | };
124 |
125 | // wipe out vote if voting state is not yet finished to prevent cheating.
126 | // if it has already been set - use the actual vote. This works for unvoting - so that
127 | // before the flip occurs - we don't display 'oi'
128 | var processVotes = function () {
129 | var voteCount = $scope.votes.length;
130 | _.each($scope.votes, function (v) {
131 | v.visibleVote = v.visibleVote === undefined && (!$scope.forcedReveal && voteCount < $scope.voterCount) ? 'oi!' : v.vote;
132 | });
133 | var voteArr = [];
134 | voteArr.length = $scope.voterCount - voteCount;
135 | $scope.placeholderVotes = voteArr;
136 |
137 | var cardValues = _.filter(_.map(_.pluck($scope.votes, 'vote'), cardValue), _.isNumber);
138 | $scope.votingAverage = average(cardValues);
139 | $scope.votingStandardDeviation = standardDeviation(cardValues, $scope.votingAverage).toFixed(2);
140 | $scope.showAverage = voteArr.length === 0 && cardValues.length > 0;
141 |
142 | $scope.forceRevealDisable = (!$scope.forcedReveal && ($scope.votes.length < $scope.voterCount || $scope.voterCount === 0)) ? false : true;
143 | console.log("forceRevealDisable", $scope.forceRevealDisable)
144 | console.log("alreadySorted;", $scope.alreadySorted)
145 | $scope.sortVotesDisable = !$scope.forceRevealDisable || $scope.alreadySorted;
146 | console.log("sortVotesDisable", $scope.sortVotesDisable)
147 |
148 | if ($scope.votes.length === $scope.voterCount || $scope.forcedReveal) {
149 | if ($scope.alreadySorted) {
150 | $scope.votes = $scope.votes.sort(function(el1, el2) {
151 | return $scope.cards.indexOf(el1.vote) - $scope.cards.indexOf(el2.vote);
152 | });
153 | }
154 |
155 | var uniqVotes = _.chain($scope.votes).pluck('vote').uniq().value().length;
156 | if (uniqVotes === 1) {
157 | $scope.$emit('unanimous vote');
158 | } else if (uniqVotes === $scope.voterCount) {
159 | $scope.$emit('problem vote');
160 | } else if ($scope.voterCount > 3 && uniqVotes === ($scope.voterCount - 1)) {
161 | $scope.$emit('problem vote');
162 | } else {
163 | $scope.$emit('not unanimous vote');
164 | }
165 | } else {
166 | $scope.$emit('unfinished vote');
167 | }
168 | };
169 |
170 | var myConnectionHash = function () {
171 | return _.find($scope.connections, function (c) { return c.sessionId === $scope.sessionId; });
172 | };
173 |
174 | var myVoteHash = function () {
175 | return _.find($scope.votes, function (c) { return c.sessionId === $scope.sessionId; });
176 | };
177 |
178 | var haveIVoted = function () {
179 | if ($scope.myVote === 'undefined' || $scope.myVote === null) {
180 | return false;
181 | }
182 | return true;
183 | };
184 |
185 | var votingFinished = function () {
186 | return $scope.forcedReveal || $scope.votes.length === $scope.voterCount;
187 | };
188 |
189 | var setVotingState = function () {
190 | $scope.cardsState = votingFinished() || !$scope.voter ? ' card--disabled' : '';
191 | $scope.votingState = votingFinished() ? ' flipped-stagger' : '';
192 | };
193 |
194 | var setLocalVote = function (vote) {
195 | var voteHash = myVoteHash();
196 | $scope.myVote = vote;
197 | $scope.voted = haveIVoted();
198 | if (!voteHash) {
199 | // initialize connections array with my first vote. (just to speed up UI)
200 | $scope.votes.push({ sessionId: $scope.sessionId, vote: vote });
201 | } else {
202 | if (vote) {
203 | voteHash.vote = vote;
204 | } else {
205 | // we're unvoting - lets remove it from the votes.
206 | $scope.votes = _.filter($scope.votes, function (v) {
207 | return v.sessionId !== $scope.sessionId;
208 | });
209 | // the above works - but causes an error in the UI.
210 | }
211 | }
212 | processVotes();
213 | setVotingState();
214 | $scope.scrollToSelectedCards.now();
215 | };
216 |
217 | var chooseCardPack = function (val) {
218 | var fib = ['0', '1', '2', '3', '5', '8', '13', '21', '34', '55', '89', '?'];
219 | var goat = ['0', '\u00BD', '1', '2', '3', '5', '8', '13', '20', '40', '100', '?', '\u2615'];
220 | var seq = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '?'];
221 | var play = ['A\u2660', '2', '3', '5', '8', '\u2654'];
222 | var tshirt = ['XL', 'L', 'M', 'S', 'XS', '?'];
223 | switch (val) {
224 | case ('fib'):
225 | return fib;
226 | case ('goat'):
227 | return goat;
228 | case ('seq'):
229 | return seq;
230 | case ('play'):
231 | return play;
232 | case ('tshirt'):
233 | return tshirt;
234 | default:
235 | return [];
236 | }
237 | };
238 |
239 | var refreshRoomInfo = function (roomObj) {
240 | // console.log("refreshRoomInfo: roomObj:", roomObj)
241 | if (roomObj.createAdmin) {
242 | $.cookie("admin-" + roomObj.roomUrl, true);
243 | }
244 | if ($.cookie("admin-" + roomObj.roomUrl)) {
245 | $scope.showAdmin = true;
246 | }
247 |
248 | $scope.connections = roomObj.connections;
249 | $scope.humanCount = $scope.connections.length;
250 | $scope.cardPack = roomObj.cardPack;
251 | $scope.forcedReveal = roomObj.forcedReveal;
252 | $scope.alreadySorted = roomObj.alreadySorted;
253 | $scope.cards = chooseCardPack($scope.cardPack);
254 |
255 | $scope.votes = _.chain($scope.connections).filter(function (c) {
256 | return c.vote;
257 | }).values().value();
258 | $scope.voterCount = _.filter($scope.connections, function (c) {
259 | return c.voter;
260 | }).length;
261 |
262 | var connection = myConnectionHash();
263 |
264 | if (connection) {
265 | $scope.voter = connection.voter;
266 | $scope.myVote = connection.vote;
267 | $scope.voted = haveIVoted();
268 | }
269 |
270 | processVotes();
271 |
272 | // we first want the cards to be displayed as hidden, and then apply the finished state
273 | // if voting has finished - which then actions the transition.
274 | $timeout(function () {
275 | setVotingState();
276 | }, 100);
277 |
278 | };
279 |
280 | $scope.configureRoom = function () {
281 |
282 | socket.on('room joined', function () {
283 | // console.log("on room joined");
284 | // console.log("emit room info", { roomUrl: $scope.roomId });
285 | this.emit('room info', { roomUrl: $scope.roomId }, function (response) {
286 | processMessage(response, refreshRoomInfo);
287 | });
288 | });
289 | socket.on('room left', function () {
290 | // console.log("on room left");
291 | // console.log("emit room info", { roomUrl: $scope.roomId });
292 | this.emit('room info', { roomUrl: $scope.roomId }, function (response) {
293 | processMessage(response, refreshRoomInfo);
294 | });
295 | });
296 | socket.on('card pack set', function () {
297 | $scope.$emit('show message', 'Card pack changed...');
298 | // console.log("on card pack set");
299 | // console.log("emit room info", { roomUrl: $scope.roomId });
300 | this.emit('room info', { roomUrl: $scope.roomId }, function (response) {
301 | processMessage(response, refreshRoomInfo);
302 | });
303 | });
304 | socket.on('voter status changed', function () {
305 | // console.log("on voter status changed");
306 | // console.log("emit room info", { roomUrl: $scope.roomId });
307 | this.emit('room info', { roomUrl: $scope.roomId }, function (response) {
308 | processMessage(response, refreshRoomInfo);
309 | });
310 | });
311 | socket.on('voted', function () {
312 | // console.log("on voted");
313 | // console.log("emit room info", { roomUrl: $scope.roomId });
314 | this.emit('room info', { roomUrl: $scope.roomId }, function (response) {
315 | processMessage(response, refreshRoomInfo);
316 | });
317 | });
318 | socket.on('unvoted', function () {
319 | // console.log("on unvoted");
320 | // console.log("emit room info", { roomUrl: $scope.roomId });
321 | this.emit('room info', { roomUrl: $scope.roomId }, function (response) {
322 | processMessage(response, refreshRoomInfo);
323 | });
324 | });
325 | socket.on('vote reset', function () {
326 | // console.log("on vote reset");
327 | // console.log("emit room info", { roomUrl: $scope.roomId });
328 | this.emit('room info', { roomUrl: $scope.roomId }, function (response) {
329 | processMessage(response, refreshRoomInfo);
330 | });
331 | });
332 |
333 | socket.on('reveal', function () {
334 | // console.log("reveal event received");
335 | // setLocalVote(null);
336 | this.emit('room info', { roomUrl: $scope.roomId }, function (response) {
337 | processMessage(response, refreshRoomInfo);
338 | });
339 | });
340 |
341 | socket.on('connect', function () {
342 | // console.log("on connect");
343 | var sessionId = this.socket.sessionid;
344 | // console.log("new socket id = " + sessionId);
345 | if (!$.cookie("sessionId")) {
346 | $.cookie("sessionId", sessionId);
347 | }
348 | $scope.sessionId = $.cookie("sessionId");
349 | // console.log("session id = " + $scope.sessionId);
350 | // console.log("emit join room", { roomUrl: $scope.roomId, sessionId: $scope.sessionId });
351 | socket.emit('join room', { roomUrl: $scope.roomId, sessionId: $scope.sessionId }, function (response) {
352 | processMessage(response, refreshRoomInfo);
353 | });
354 | });
355 | socket.on('disconnect', function () {
356 | // console.log("on disconnect");
357 | });
358 |
359 | // console.log("emit join room", { roomUrl: $scope.roomId, sessionId: $scope.sessionId });
360 | socket.emit('join room', { roomUrl: $scope.roomId, sessionId: $scope.sessionId }, function (response) {
361 | processMessage(response, refreshRoomInfo);
362 | });
363 | };
364 |
365 | $scope.setCardPack = function (cardPack) {
366 | $scope.cardPack = cardPack;
367 | $scope.resetVote();
368 |
369 | // console.log("set card pack", { roomUrl: $scope.roomId, cardPack: cardPack });
370 | socket.emit('set card pack', { roomUrl: $scope.roomId, cardPack: cardPack });
371 | };
372 |
373 | $scope.vote = function (vote) {
374 | if ($scope.myVote !== vote) {
375 | if (!votingFinished() && $scope.voter) {
376 | setLocalVote(vote);
377 |
378 | // console.log("emit vote", { roomUrl: $scope.roomId, vote: vote, sessionId: $scope.sessionId });
379 | socket.emit('vote', { roomUrl: $scope.roomId, vote: vote, sessionId: $scope.sessionId }, function (response) {
380 | processMessage(response);
381 | });
382 | }
383 | }
384 | };
385 |
386 | $scope.unvote = function (sessionId) {
387 | if (sessionId === $scope.sessionId) {
388 | if (!votingFinished()) {
389 | setLocalVote(undefined);
390 |
391 | // console.log("emit unvote", { roomUrl: $scope.roomId, sessionId: $scope.sessionId });
392 | socket.emit('unvote', { roomUrl: $scope.roomId, sessionId: $scope.sessionId }, function (response) {
393 | processMessage(response);
394 | });
395 | }
396 | }
397 | };
398 |
399 | $scope.resetVote = function () {
400 | // console.log("emit reset vote", { roomUrl: $scope.roomId });
401 | socket.emit('reset vote', { roomUrl: $scope.roomId }, function (response) {
402 | processMessage(response);
403 | });
404 | };
405 |
406 | $scope.forceReveal = function () {
407 | // console.log("emit force reveal", { roomUrl: $scope.roomId });
408 | $scope.forceRevealDisable = true;
409 | socket.emit('force reveal', { roomUrl: $scope.roomId }, function (response) {
410 | processMessage(response);
411 | });
412 | };
413 |
414 | $scope.sortVotes = function () {
415 | // console.log("emit sort votes", { roomUrl: $scope.roomId });
416 | $scope.sortVotesDisable = true;
417 | socket.emit('sort votes', { roomUrl: $scope.roomId }, function (response) {
418 | processMessage(response);
419 | });
420 | };
421 |
422 | $scope.toggleVoter = function () {
423 | // console.log("emit toggle voter", { roomUrl: $scope.roomId, voter: $scope.voter, sessionId: $scope.sessionId });
424 | socket.emit('toggle voter', { roomUrl: $scope.roomId, voter: $scope.voter, sessionId: $scope.sessionId }, function (response) {
425 | processMessage(response);
426 | });
427 | };
428 |
429 | $scope.roomId = $routeParams.roomId;
430 | $scope.humanCount = 0;
431 | $scope.voterCount = 0;
432 | $scope.showAdmin = false;
433 | $scope.voter = true;
434 | $scope.connections = {};
435 | $scope.votes = [];
436 | $scope.cardPack = '';
437 | $scope.myVote = undefined;
438 | $scope.voted = haveIVoted();
439 | $scope.votingState = "";
440 | $scope.forcedReveal = false;
441 | $scope.forceRevealDisable = true;
442 | $scope.sortVotesDisable = true;
443 | $scope.scrollToSelectedCards = new ScrollIntoView($('#chosenCards'));
444 |
445 | $scope.dropDown = new DropDown('#dd');
446 | $scope.votingAverage = 0;
447 | }
448 |
449 | RoomCtrl.$inject = ['$scope', '$routeParams', '$timeout', 'socket'];
450 |
--------------------------------------------------------------------------------
/app/styleguide.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hat.jit.su style guide
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Hat.jit.su styleguide: a cut-out-and-keep reference for all the family
18 |
19 |
20 | Header/Logo
21 | First thing's first; the header. The glorious introduction to the majesterial wonder that is HatJitSu. Here's an example of it in action;
22 |
23 |
24 |
25 | Hatjitsu
26 |
27 |
28 |
29 |
30 | Pretty awesome, hey? Have a look at the source code to see how that was done. We've used header tags and such, but that's not too important.
31 | Neither's the a wrapped around it. That's purely optional; if you only want the logo and the header without a link, just take it out.
32 |
33 |
34 | Hatjitsu
35 |
36 | Actually, it's pretty much the same deal with the logo. Just remove the .header__text.
37 |
38 |
39 |
40 | Just remember to put wrap it in a container classed with .header.
41 | With the .header__text class, you can add multiple elements, allowing for multiline headings:
42 |
43 |
44 |
45 | hatjitsu
46 |
47 |
48 | It's also possible to change the colour of the logo dependant upon a status, simply add a second class of .header__logo--[colour] to the existing .header__logo element.
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Footer
58 | The footer of the page is a fairly simple center alignment issue. Just add in the .footer class and let the typographical styles do the rest.
59 |
62 |
63 |
64 |
65 | Body
66 | The .body--[colour] applies new styling based upon the consensus of voting, similar to the header logo.
67 |
68 | Green
69 | Yellow
70 | Red
71 |
72 |
73 |
74 |
75 | Cards
76 | 1 sided cards
77 | Cards are obviously central to this app, and form a large part of the look. They all follow a fairly standard markup pattern.
78 | 1
79 | Each card item is tagged with .card.
80 | If there are multiple cards, the parent wrapper is tagged with .cards.
81 |
82 | 1
83 | 2
84 | 3
85 | 4
86 | 5
87 | 6
88 |
89 | If a card is selected or active, add a class of .card--selected to the .card element in question.
90 | Conversely, .card--disabled will mark it as disabled.
91 | Finally, .card--placeholder is used to visually represent where a card will be placed.
92 | These 3 classes can be mixed and matched as needed.
93 |
94 |
95 | 1
96 | 2
97 | 3
98 | 4
99 | ∅
100 | ∅
101 |
102 | 2 sided cards
103 | 2-sided cards are available to those with fancy JavaScriptin' and CSS3 3D-Transforms on their calculatin' machines. The basic markup is a little more involved, but should make sense:
104 |
105 |
106 | F
107 | B
108 |
109 |
110 | O
111 | A
112 |
113 |
114 | O
115 | R
116 |
117 |
118 |
119 |
120 | By the way; we all understand that not all browsers support CSS 3D Transforms, right? Those browsers will simply swap cards around.
121 | ..and adding .card--selected or .code-disabled will still perform as before.
122 |
123 |
124 |
125 | R
126 | L
127 |
128 |
129 | O
130 | M
131 |
132 |
133 | F
134 | A
135 |
136 |
137 | L
138 | O
139 |
140 | ♠
141 |
142 |
143 |
144 |
145 | or selected only on one side...
146 |
147 |
148 | W
149 | L
150 |
151 |
152 | T
153 | O
154 |
155 |
156 | F
157 | L
158 |
159 |
160 |
161 |
162 | Or toggle an individual card, simple add .flipped to the .card in question...
163 |
164 |
165 | 1
166 | 1
167 |
168 |
169 | 2
170 | 2
171 |
172 |
173 | 3
174 | 3
175 |
176 |
177 |
178 |
179 | Or to stagger the animation of cards, simply add .flipped-stagger to .cards... ( note staggering only works up to a maximum of 8 votes, any more will be flipped at the same time)
180 |
181 |
182 | 1
183 | A
184 |
185 |
186 | 2
187 | B
188 |
189 |
190 | 3
191 | C
192 |
193 |
194 | 4
195 | D
196 |
197 |
198 | 5
199 | E
200 |
201 |
202 | 6
203 | F
204 |
205 |
206 | 7
207 | G
208 |
209 |
210 | 8
211 | H
212 |
213 |
214 | 9
215 | I
216 |
217 |
218 | 10
219 | J
220 |
221 |
222 | 11
223 | K
224 |
225 |
226 | 12
227 | L
228 |
229 |
230 | 13
231 | M
232 |
233 |
234 | 14
235 | N
236 |
237 |
238 | 15
239 | O
240 |
241 | ♥
242 |
243 | 16
244 | P
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | Buttons
253 | Basic setup: button.btn
254 | Use the .icon to assign a space, and then either .icon-refresh or .icon-exclamation-sign for the various icons to use.
255 |
256 |
257 |
258 |
259 |
260 | 2-way toggle
261 | Basic setup:
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 | Dropdown
280 | Javascript is required to use this customised drop down menu. The #dd selector is customisable to whatever jQuery selector you would prefer.
281 |
282 |
283 | Card pack:
284 |
285 | - Mountain Goat
286 | - Fibonacci
287 | - Sequential
288 | - Playing Cards
289 | - T-Shirt
290 |
291 |
292 |
295 |
296 |
297 |
298 |
299 |
300 | Typography
301 | TBC
302 |
303 |
304 |
305 | Concertina
section
306 | TBC
307 |
308 |
309 |
310 | Panels
311 | .cardPanel
312 | The card panel typically contains all available vote combinations, and has a responsive breakpoint before viewport width of 28ems that centrally aligns the cards. Afterwards, the design will revert back to the left. Otherwise, it is also responsible for some simple styling.
313 |
314 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Illo et reprehenderit architecto eum delectus molestias ipsa corrupti perferendis aperiam suscipit repellat blanditiis rerum ea tempore doloribus provident quisquam impedit vel.
315 |
316 |
317 |
318 |
319 | Surgical classes
320 |
321 | .bg
322 | Applies a transparent background texture.
323 |
324 |
325 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatum neque excepturi facilis consequuntur illo rem ipsa ad aspernatur veniam eos dicta minus vero mollitia perferendis quibusdam iure laudantium animi sed.
326 |
327 |
328 |
329 |
330 | .no-js-hide
331 | With JavaScript disabled, this section will not be visible at all.
332 | Think of it as an opposite to <noscript> Add a .no-js-hide class to those elements you want to remove.
333 |
334 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
--------------------------------------------------------------------------------
/app/css/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | }
4 | body {
5 | margin: 0;
6 | }
7 | hr {
8 | clear: both;
9 | }
10 | /*
11 | Links
12 |
13 | */
14 | a {
15 | -webkit-transition: color .1s ease-out;
16 | -moz-transition: color .1s ease-out;
17 | -ms-transition: color .1s ease-out;
18 | -o-transition: color .1s ease-out;
19 | transition: color .1s ease-out;
20 | }
21 | a,
22 | a:visited,
23 | a:active {
24 | color: #493e27;
25 | }
26 | a:hover,
27 | a:focus {
28 | color: rgb(189, 27, 27);
29 | }
30 | /*
31 | Body
32 |
33 | */
34 |
35 | .body {
36 | font: 16px/1.3em 'Helvetica neue', 'Helvetica', 'Arial', sans-serif;
37 | background-color: #c5bdab;
38 | color: #3d3938;
39 | text-shadow: 0px 0px 1px #f4f0e9;
40 | min-height: 100%;
41 | -webkit-transition: .4s background-color ease;
42 | -moz-transition: .4s background-color ease;
43 | -ms-transition: .4s background-color ease;
44 | -o-transition: .4s background-color ease;
45 | transition: .4s background-color ease;
46 | }
47 | .body--green { background-color: #c5c2ad; }
48 | .body--yellow { background-color: #c5bca1; }
49 | .body--red { background-color: #d1bdad; }
50 |
51 | /*
52 | Header
53 |
54 | */
55 | .header {
56 | position: relative;
57 | z-index: 3;
58 | padding-top: 8px;
59 | padding-bottom: 67px; /* allow for absolutely positioned mountain */
60 | zoom: 1;
61 | border: 1px solid #B9AAA7;
62 | border-width: 0 1px;
63 | }
64 | .header:before,
65 | .header:after {
66 | content: "\0020";
67 | display: block;
68 | height: 0;
69 | overflow: hidden;
70 | }
71 | .header:after {
72 | clear: both;
73 | }
74 | .header a {
75 | display: block;
76 | overflow: hidden;
77 | text-decoration: none;
78 | position: relative;
79 | }
80 | .header__text {
81 | line-height: 1;
82 | font-size: 3em;
83 | margin: 0.5em 0 0 0.5em;
84 | position: relative;
85 | z-index: 1;
86 | text-align: left;
87 | height: 56px;
88 | width: 155px;
89 | text-indent: -1000px;
90 | background-image: url(/img/title.png);
91 | background-repeat: no-repeat;
92 | }
93 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
94 | background-image: url(/img/title@2x.png);
95 | }
96 | @media screen and (min-width: 17em) {
97 | .header__text {
98 | position: relative;
99 | margin-left: 2.5em;
100 | right: auto;
101 | width: auto;
102 | }
103 | }
104 | .header__text > * {
105 | font-size: 1em;
106 | }
107 | .header__logo {
108 | float: left;
109 | display: block;
110 | margin-left: 1em;
111 | width: 6em;
112 | height: 6em;
113 | border-radius: 50%;
114 | background-size: 100%;
115 | -webkit-transition: .3s background-color ease;
116 | -moz-transition: .3s background-color ease;
117 | -ms-transition: .3s background-color ease;
118 | -o-transition: .3s background-color ease;
119 | transition: .3s background-color ease;
120 | z-index: 1;
121 | background-color: rgb(226, 222, 212);
122 | background-color: rgba(226, 222, 212, 0.8);
123 | }
124 | .header__logo--green {
125 | background-color: rgb(209, 229, 181);
126 | background-color: rgba(209, 229, 181, 0.9);
127 | }
128 | .header__logo--yellow {
129 | background-color: rgb(237, 226, 177);
130 | background-color: rgba(237, 226, 177, 0.9);
131 | }
132 | .header__logo--red {
133 | background-color: rgb(224, 157, 154);
134 | background-color: rgba(224, 157, 154, 0.9);
135 | }
136 |
137 | .no-svg .header__logo {
138 | position: relative;
139 | }
140 | .no-svg .header__logo:after {
141 | content: " ";
142 | position: absolute;
143 | top: 0;
144 | left: 0;
145 | display: block;
146 | width: 100%;
147 | height: 100%;
148 | background: url(/img/sun-mask.png) no-repeat center;
149 | }
150 |
151 | .roomNumber {
152 | position: absolute;
153 | z-index: 4;
154 | top: 4.3em;
155 | left: 7.7em;
156 | }
157 |
158 | /*
159 |
160 | Footer
161 |
162 | */
163 | .footer {
164 | overflow: hidden;
165 | text-align: center;
166 | position: relative;
167 | border: 1px solid #b9aaa7;
168 | border-width: 0 1px;
169 | }
170 |
171 | /*
172 |
173 | Panel layouts
174 |
175 | */
176 |
177 | .lobby {
178 | padding: 0 8px;
179 | position: relative;
180 | overflow: hidden;
181 | border: 1px solid #b9aaa7;
182 | border-width: 0 1px;
183 | }
184 | .lobby p,
185 | .lobby .subheading,
186 | .lobby label {
187 | padding-left: 0.5em;
188 | padding-right: 0.5em;
189 | }
190 | .votePanel {
191 | overflow: hidden;
192 | padding: 1em 8px 0;
193 | position: relative;
194 | border: 1px solid #b9aaa7;
195 | border-width: 0 1px;
196 | }
197 | .cardPanel {
198 | padding: 0 8px;
199 | text-align: center;
200 | position: relative;
201 | -webkit-box-shadow: inset 0px 1px 1px rgba(0,0,0,0.3);
202 | -moz-box-shadow: inset 0px 1px 1px rgba(0,0,0,0.3);
203 | box-shadow: inset 0px 1px 1px rgba(0,0,0,0.3);
204 | -webkit-transition: all .4s ease-in-out;
205 | -moz-transition: all .4s ease-in-out;
206 | -ms-transition: all .4s ease-in-out;
207 | -o-transition: all .4s ease-in-out;
208 | transition: all .4s ease-in-out;
209 |
210 | }
211 | .cardPanel:before {
212 | content: "";
213 | display: block;
214 | position: absolute;
215 | width: 100%;
216 | height: 100%;
217 | background-color: rgba(0,0,0,0.1);
218 | top: 0;
219 | left: 0;
220 | }
221 | .cardPanel p {
222 | text-align: left;
223 | }
224 | @media screen and (min-width: 28em) {
225 | .cardPanel {
226 | text-align: left;
227 | }
228 | }
229 | .cardPanel-meta {
230 | padding: 1em 0;
231 | }
232 | .container {
233 | margin: 0 auto;
234 | max-width: 55em;
235 | overflow: hidden;
236 | position: relative;
237 | }
238 |
239 | /*
240 |
241 | Grid
242 |
243 | */
244 | .row {
245 | width: 100%;
246 | display: block;
247 | text-align: left;
248 | }
249 | .row:before,
250 | .row:after {
251 | content: "\0020";
252 | display: block;
253 | height: 0;
254 | overflow: hidden;
255 | }
256 | .row:after {
257 | clear: both;
258 | }
259 | .row .span2, .row .span1 {
260 | display: block;
261 | width: 100%
262 | }
263 | @media screen and (min-width: 16em) {
264 | .row .span1 {
265 | width: 16em;
266 | }
267 | .row .span2 {
268 | display: inline-block;
269 | width: 16em;
270 | }
271 | }
272 |
273 | /*
274 |
275 | Cards
276 |
277 | */
278 | .cards {
279 | overflow: hidden;
280 | }
281 | .cards .card {
282 | margin-bottom: 0.5em;
283 | margin-right: 0.5em;
284 | }
285 | .card {
286 | display: inline-block;
287 | width: 2em;
288 | height: 3em;
289 | margin: 0;
290 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
291 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
292 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
293 | border: 1px solid transparent;
294 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
295 | border-bottom-color: #A2A2A2;
296 | position: relative;
297 | -webkit-border-radius: 0.2em;
298 | border-radius: 0.2em;
299 |
300 | color: #25201c;
301 | font-size: 1.5em;
302 | text-align: center;
303 | text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
304 | line-height: 3em;
305 | cursor: pointer;
306 |
307 | background-color: rgb(224, 217, 207);
308 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(rgb(224, 217, 207)), to(rgb(216, 208, 197)));
309 | background-image: -webkit-linear-gradient(rgb(224, 217, 207), rgb(216, 208, 197));
310 | background-image: -moz-linear-gradient(rgb(224, 217, 207), rgb(216, 208, 197));
311 | background-image: -o-linear-gradient(rgb(224, 217, 207), rgb(216, 208, 197));
312 | background-image: linear-gradient(rgb(224, 217, 207), rgb(216, 208, 197));
313 | background-repeat: repeat-x;
314 | }
315 | .card:hover,
316 | .card:focus {
317 | background-color: rgb(216, 208, 197);
318 | background-position: 0 -30px;
319 | border-color: transparent;
320 | -webkit-transition: background-position 0.1s ease;
321 | -moz-transition: background-position 0.1s ease;
322 | -ms-transition: background-position 0.1s ease;
323 | -o-transition: background-position 0.1s ease;
324 | transition: background-position 0.1s ease;
325 | }
326 | .card--2-sided {
327 | -webkit-box-shadow: none;
328 | -moz-box-shadow: none;
329 | box-shadow: none;
330 | background-color: transparent;
331 | background-image: none;
332 | border: none;
333 | float: left;
334 | }
335 | .card--selected {
336 | background-color: gold;
337 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(gold), to(gold));
338 | background-image: -webkit-linear-gradient(top, gold, gold);
339 | background-image: -moz-linear-gradient(top, gold, gold);
340 | background-image: -o-linear-gradient(top, gold, gold);
341 | background-image: linear-gradient(to bottom, gold, gold);
342 | background-repeat: repeat-x;
343 | cursor: default;
344 | }
345 | .card--selected:hover,
346 | .card--selected:focus {
347 | background-position: 0 0;
348 | background-color: gold;
349 | }
350 | .card--2-sided > * {
351 | position: absolute;
352 | display: inline-block;
353 | width: 100%;
354 | top: 0;
355 | left: 0;
356 | -webkit-border-radius: 0.2em;
357 | border-radius: 0.2em;
358 | -webkit-transform-style: preserve-3d;
359 | -moz-transform-style: preserve-3d;
360 | -ms-transform-style: preserve-3d;
361 | -o-transform-style: preserve-3d;
362 | transform-style: preserve-3d;
363 | -webkit-backface-visibility: hidden;
364 | -moz-backface-visibility: hidden;
365 | -ms-backface-visibility: hidden;
366 | -o-backface-visibility: hidden;
367 | backface-visibility: hidden;
368 | -webkit-transition: -webkit-transform .4s ease-in-out;
369 | -moz-transition: -moz-transform .4s ease-in-out;
370 | -ms-transition: -ms-transform .4s ease-in-out;
371 | -o-transition: -o-transform .4s ease-in-out;
372 | transition: transform .4s ease-in-out;
373 | -webkit-transform-style: preserve-3d;
374 | -moz-transform-style: preserve-3d;
375 | -ms-transform-style: preserve-3d;
376 | -o-transform-style: preserve-3d;
377 | transform-style: preserve-3d;
378 | background-color: #E0D9CF;
379 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(rgb(224, 217, 207)), to(rgb(216, 208, 197)));
380 | background-image: -webkit-linear-gradient(top, rgb(224, 217, 207), rgb(216, 208, 197));
381 | background-image: -moz-linear-gradient(top, rgb(224, 217, 207), rgb(216, 208, 197));
382 | background-image: -o-linear-gradient(top, rgb(224, 217, 207), rgb(216, 208, 197));
383 | background-image: linear-gradient(to bottom, rgb(224, 217, 207), rgb(216, 208, 197));
384 | background-repeat: repeat-x;
385 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
386 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
387 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
388 | border: 1px solid #BBB;
389 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
390 | border-bottom-color: #A2A2A2;
391 | }
392 | .card--2-sided > *:hover {
393 | border-color: transparent;
394 | }
395 | .card--2-sided.card--selected {
396 | background-color: transparent;
397 | background-image: none;
398 | }
399 | .card--2-sided > .card--selected,
400 | .card--2-sided.card--selected > * {
401 | background-color: gold;
402 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(gold), to(gold));
403 | background-image: -webkit-linear-gradient(top, gold, gold);
404 | background-image: -moz-linear-gradient(top, gold, gold);
405 | background-image: -o-linear-gradient(top, gold, gold);
406 | background-image: linear-gradient(to bottom, gold, gold);
407 | background-repeat: repeat-x;
408 | }
409 | .card--2-sided > .card--disabled,
410 | .card--2-sided.card--disabled {
411 | opacity: 0.5;
412 | background-color: transparent;
413 | color: #AAA;
414 | }
415 | .no-csstransforms3d .card--2-sided > .card--disabled,
416 | .no-csstransforms3d .card--2-sided.card--disabled {
417 | background-color: rgb(224, 217, 207);
418 | }
419 | .card--2-sided.card--disabled > * {
420 | opacity: 0.5;
421 | }
422 | .no-csstransforms3d .card--2-sided.card--disabled > * {
423 | opacity: 1;
424 | }
425 | .card--placeholder {
426 | background-color: #cccccc;
427 | background-color: rgba(0, 0, 0, 0);
428 | -webkit-box-shadow: inset 0px 0px 3px rgba(15, 14, 12, 0.7);
429 | -moz-box-shadow: inset 0px 0px 3px rgba(15, 14, 12, 0.7);
430 | box-shadow: inset 0px 0px 3px rgba(15, 14, 12, 0.7);
431 | background-image: none;
432 | border: none;
433 | border-top: 1px solid transparent;
434 | cursor: default;
435 | }
436 | .card--placeholder:hover,
437 | .card--placeholder:focus {
438 | background-image: none;
439 | background-color: #cccccc;
440 | background-color: rgba(0, 0, 0, 0);
441 | }
442 | .card--disabled,
443 | .card--disabled > * {
444 | cursor: default;
445 | }
446 | .card--disabled {
447 | opacity: 0.5;
448 | }
449 | .card--side-1 { z-index: 2; }
450 | .card--side-2 { z-index: 1; }
451 | /* Step 1: by default, the second side is reversed */
452 | .csstransforms3d .card--side-2{
453 | -webkit-transform: rotateY(-180deg);
454 | -moz-transform: rotateY(-180deg);
455 | -ms-transform: rotateY(-180deg);
456 | -o-transform: rotateY(-180deg);
457 | transform: rotateY(-180deg);
458 | }
459 | /* Step 2: adding .flipped to a parent triggers animation */
460 | .csstransforms3d .flipped .card--side-1 {
461 | -webkit-transform: rotateY(-180deg);
462 | -moz-transform: rotateY(-180deg);
463 | -ms-transform: rotateY(-180deg);
464 | -o-transform: rotateY(-180deg);
465 | transform: rotateY(-180deg);
466 | }
467 | .csstransforms3d .flipped .card--side-2 {
468 | -webkit-transform: rotateX(0deg) rotateY(0deg);
469 | -moz-transform: rotateX(0deg) rotateY(0deg);
470 | -ms-transform: rotateX(0deg) rotateY(0deg);
471 | -o-transform: rotateX(0deg) rotateY(0deg);
472 | transform: rotateX(0deg) rotateY(0deg);
473 | }
474 | .no-csstransforms3d .card--side-1 { z-index: 2; }
475 | .no-csstransforms3d .card--side-2 { z-index: 1; }
476 | .no-csstransforms3d .flipped .card--side-1 { z-index: 1; }
477 | .no-csstransforms3d .flipped .card--side-2 { z-index: 2; }
478 | /* Step 2b: adding .flipped-stagger to a parent triggers staggered animation */
479 | .csstransforms3d .flipped-stagger .card--side-1 {
480 | -webkit-transform: rotateY(180deg);
481 | -moz-transform: rotateY(180deg);
482 | -ms-transform: rotateY(180deg);
483 | -o-transform: rotateY(180deg);
484 | transform: rotateY(180deg);
485 | }
486 | .csstransforms3d .flipped-stagger .card--side-2 {
487 | -webkit-transform: rotateX(0deg) rotateY(0deg);
488 | -moz-transform: rotateX(0deg) rotateY(0deg);
489 | -ms-transform: rotateX(0deg) rotateY(0deg);
490 | -o-transform: rotateX(0deg) rotateY(0deg);
491 | transform: rotateX(0deg) rotateY(0deg);
492 | }
493 | .csstransforms3d .flipped-stagger .card:nth-child(1) > * {
494 | -webkit-transition-delay: 0.1s;
495 | -moz-transition-delay: 0.1s;
496 | -ms-transition-delay: 0.1s;
497 | -o-transition-delay: 0.1s;
498 | transition-delay: 0.1s;
499 | }
500 | .csstransforms3d .flipped-stagger .card:nth-child(2) > * {
501 | -webkit-transition-delay: 0.2s;
502 | -moz-transition-delay: 0.2s;
503 | -ms-transition-delay: 0.2s;
504 | -o-transition-delay: 0.2s;
505 | transition-delay: 0.2s;
506 | }
507 | .csstransforms3d .flipped-stagger .card:nth-child(3) > * {
508 | -webkit-transition-delay: 0.3s;
509 | -moz-transition-delay: 0.3s;
510 | -ms-transition-delay: 0.3s;
511 | -o-transition-delay: 0.3s;
512 | transition-delay: 0.3s;
513 | }
514 | .csstransforms3d .flipped-stagger .card:nth-child(4) > * {
515 | -webkit-transition-delay: 0.4s;
516 | -moz-transition-delay: 0.4s;
517 | -ms-transition-delay: 0.4s;
518 | -o-transition-delay: 0.4s;
519 | transition-delay: 0.4s;
520 | }
521 | .csstransforms3d .flipped-stagger .card:nth-child(5) > *{
522 | -webkit-transition-delay: 0.5s;
523 | -moz-transition-delay: 0.5s;
524 | -ms-transition-delay: 0.5s;
525 | -o-transition-delay: 0.5s;
526 | transition-delay: 0.5s;
527 | }
528 | .csstransforms3d .flipped-stagger .card:nth-child(6) > *{
529 | -webkit-transition-delay: 0.6s;
530 | -moz-transition-delay: 0.6s;
531 | -ms-transition-delay: 0.6s;
532 | -o-transition-delay: 0.6s;
533 | transition-delay: 0.6s;
534 | }
535 | .csstransforms3d .flipped-stagger .card:nth-child(7) > *{
536 | -webkit-transition-delay: 0.7s;
537 | -moz-transition-delay: 0.7s;
538 | -ms-transition-delay: 0.7s;
539 | -o-transition-delay: 0.7s;
540 | transition-delay: 0.7s;
541 | }
542 | .csstransforms3d .flipped-stagger .card:nth-child(8) > *{
543 | -webkit-transition-delay: 0.8s;
544 | -moz-transition-delay: 0.8s;
545 | -ms-transition-delay: 0.8s;
546 | -o-transition-delay: 0.8s;
547 | transition-delay: 0.8s;
548 | }
549 | .csstransforms3d .flipped-stagger .card > *{
550 | -webkit-transition-delay: 1s;
551 | -moz-transition-delay: 1s;
552 | -ms-transition-delay: 1s;
553 | -o-transition-delay: 1s;
554 | transition-delay: 1s;
555 | }
556 | .no-csstransforms3d .flipped-stagger .card--side-1 { z-index: 1; }
557 | .no-csstransforms3d .flipped-stagger .card--side-2 { z-index: 2; }
558 | .vote.card { cursor: default; }
559 | .vote.card--selected { cursor: pointer; }
560 |
561 | /*
562 |
563 | Buttons
564 |
565 | */
566 |
567 | .btn {
568 | padding: 4px 12px;
569 | margin: 2px 0 0;
570 | line-height: 31px;
571 | display: inline-block;
572 | width: 100%;
573 | min-height: 44px;
574 | color: #333;
575 | font-size: 16px;
576 | text-align: left;
577 | text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
578 | vertical-align: middle;
579 | cursor: pointer;
580 | background-color: rgb(216, 208, 197);
581 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(rgb(224, 217, 207)), to(rgb(216, 208, 197)));
582 | background-image: -webkit-linear-gradient(top, rgb(224, 217, 207), rgb(216, 208, 197));
583 | background-image: -moz-linear-gradient(top, rgb(224, 217, 207), rgb(216, 208, 197));
584 | background-image: -o-linear-gradient(top, rgb(224, 217, 207), rgb(216, 208, 197));
585 | background-image: linear-gradient(to bottom, rgb(224, 217, 207), rgb(216, 208, 197));
586 | background-repeat: repeat-x;
587 | border: 1px solid transparent;
588 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
589 | -webkit-box-sizing: border-box;
590 | -moz-box-sizing: border-box;
591 | box-sizing: border-box;
592 | -webkit-border-radius: 4px;
593 | -moz-border-radius: 4px;
594 | border-radius: 4px;
595 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
596 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
597 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
598 | -webkit-transition: background-position 0.1s ease;
599 | -moz-transition: background-position 0.1s ease;
600 | -ms-transition: background-position 0.1s ease;
601 | -o-transition: background-position 0.1s ease;
602 | transition: background-position 0.1s ease;
603 | }
604 | @media screen and (min-width: 16em) {
605 | .btn {
606 | width: 16em;
607 | }
608 | }
609 | .btn:hover,
610 | .btn:focus {
611 | background-position: 0 -40px;
612 | border-color: transparent;
613 | }
614 | .icon {
615 | display: inline-block;
616 | width: 14px;
617 | height: 14px;
618 | margin-top: 2px;
619 | line-height: 14px;
620 | vertical-align: text-top;
621 | background-image: url(/img/glyphicons.png);
622 | background-repeat: no-repeat;
623 | background-position: 14px 14px;
624 | }
625 | .icon-refresh {
626 | background-position: 0px 0px;
627 | }
628 | .icon-exclamation-sign {
629 | background-position: -90px 0px;
630 | }
631 | .icon-sort,
632 | .icon-sort::after,
633 | .icon-sort::before {
634 | box-sizing: border-box;
635 | height: 2px;
636 | border-radius: 4px;
637 | background: currentColor
638 | }
639 | .icon-sort {
640 | display: block;
641 | position: relative;
642 | transform: scale(var(--ggs,1));
643 | width: 8px;
644 | top: 4px;
645 | left: 2px
646 | }
647 | .icon-sort::after,
648 | .icon-sort::before {
649 | content: "";
650 | position: absolute
651 | }
652 | .icon-sort::before {
653 | width: 12px;
654 | top: -4px;
655 | left: -2px
656 | }
657 | .icon-sort::after {
658 | width: 4px;
659 | top: 4px;
660 | left: 2px
661 | }
662 |
663 | .switch {
664 | position: relative;
665 | margin: 2px 0 0;
666 | width: 100%;
667 | }
668 | @media screen and (min-width: 16em) {
669 | .switch {
670 | width: 16em;
671 | }
672 | }
673 |
674 | .switch input {
675 | position: absolute;
676 | width: 100%;
677 | height: 100%;
678 | z-index: 100;
679 | opacity: 0;
680 | cursor: pointer;
681 | margin: 0;
682 | }
683 | .switch .btn {
684 | padding: 0;
685 | text-indent: -100%;
686 | line-height: 40px;
687 | color: #B4573A;
688 | margin: 0;
689 | -webkit-transition: background-position 0.1s ease;
690 | -moz-transition: background-position 0.1s ease;
691 | -ms-transition: background-position 0.1s ease;
692 | -o-transition: background-position 0.1s ease;
693 | transition: background-position 0.1s ease;
694 | }
695 | .switch:hover .btn,
696 | .switch:focus .btn {
697 | background-position: 0 -40px;
698 | border-color: transparent;
699 | }
700 |
701 | .switch .btn:after {
702 | content: attr(data-off);
703 | position: absolute;
704 | z-index: 1;
705 | width: 100%;
706 | height: 100%;
707 | top: 0px;
708 | left: 0px;
709 | text-indent: 31px;
710 | overflow: hidden;
711 | white-space: nowrap;
712 | text-overflow: ellipsis;
713 | line-height: 40px;
714 | }
715 | .switch .btn:before {
716 | content: "";
717 | position: absolute;
718 | z-index: 1;
719 | width: 15px;
720 | height: 13px;
721 | background-image: url(/img/glyphicons.png);
722 | background-position: -58px 0px;
723 | top: 15px;
724 | left: 11px;
725 | }
726 | .switch input:checked ~ .btn {
727 | color: #408F21;
728 | background-position: 0 -40px;
729 | }
730 | .switch input:checked ~ .btn:after {
731 | content: attr(data-on);
732 | }
733 | .switch input:checked ~ .btn:before {
734 | background-position: -28px 0;
735 | }
736 | .no-checked .switch .btn:before,
737 | .no-checked .switch .btn:after {
738 | display: none;
739 | }
740 | .no-checked .switch .btn {
741 | text-indent: 1.5em;
742 | color: #333;
743 | }
744 | .no-checked .switch input {
745 | width: auto;
746 | height: auto;
747 | top: 11px;
748 | left: 2px;
749 | }
750 | .roomUrl {
751 | font-size: 16px;
752 | line-height: 31px;
753 | -webkit-box-sizing: border-box;
754 | -moz-box-sizing: border-box;
755 | box-sizing: border-box;
756 | padding: 4px 12px;
757 | margin-bottom: 0;
758 | vertical-align: middle;
759 | -webkit-border-radius: 4px;
760 | -moz-border-radius: 4px;
761 | border-radius: 4px;
762 | border-width: 0px;
763 | }
764 |
765 | /*
766 |
767 | Typography
768 |
769 | */
770 |
771 | .subheading {
772 | font-size: 1.1em;
773 | font-weight: bold;
774 | letter-spacing: -0.1px;
775 | line-height: 1.2em;
776 | text-align: left;
777 | }
778 |
779 | /*
780 |
781 | Dropdown
782 |
783 | */
784 | .dropdown-wrapper {
785 | position: relative;
786 | max-width: 13em;
787 | padding: 0 1em;
788 | outline: none;
789 | cursor: pointer;
790 | min-height: 34px;
791 | }
792 | .dropdown-wrapper:after {
793 | content: "";
794 | width: 0;
795 | height: 0;
796 | position: absolute;
797 | right: 16px;
798 | top: 50%;
799 | margin-top: -6px;
800 | border-width: 6px 0 6px 6px;
801 | border-style: solid;
802 | border-color: transparent #fff;
803 | }
804 | .dropdown-wrapper .dropdown {
805 | position: absolute;
806 | top: 98%;
807 | left: 0;
808 | right: 0;
809 | background: #fff;
810 | opacity: 0;
811 | pointer-events: none;
812 | margin: 0;
813 | padding: 0;
814 | z-index: 3;
815 | }
816 | .dropdown-wrapper.active .dropdown {
817 | opacity: 1;
818 | pointer-events: auto;
819 | }
820 | .dropdown-wrapper.active:after {
821 | border-width: 6px 6px 0 6px;
822 | border-color: rgb(189, 27, 27) transparent;
823 | margin-top: -3px;
824 | }
825 |
826 | .dropdown li {
827 | list-style-type: none;
828 | }
829 | .dropdown .dropdown__item {
830 | display: block;
831 | text-decoration: none;
832 | padding: 10px 20px;
833 | }
834 |
835 | /*
836 |
837 | Alerts
838 |
839 | */
840 | .alert {
841 | z-index: 3;
842 | top: 0;
843 | left: 0;
844 | width: 100%;
845 | position: fixed;
846 | }
847 | @media screen and (min-width: 55em) {
848 | .alert {
849 | position: absolute;
850 | }
851 | }
852 | .alert .activity {
853 | width: 1em;
854 | height: 1em;
855 | background: url(/img/led.gif);
856 | text-indent: 100%;
857 | margin-top: 0.2em;
858 | margin-bottom: 0.2em;
859 | margin-right: 0.2em;
860 | float: right;
861 | text-align: right;
862 | -webkit-border-radius: 0.5em;
863 | -moz-border-radius: 0.5em;
864 | border-radius: 0.5em;
865 | }
866 | .socketMessage,
867 | .appError,
868 | .message {
869 | padding: 0 2em 0 0.5em;
870 | line-height: 1.5em;
871 | }
872 | .message {
873 | background-color: rgb(255, 255, 111);
874 | background-color: rgba(255, 255, 111, 0.4);
875 | color: rgb(129, 129, 15);
876 | }
877 | .socketMessage {
878 | background-color: rgba(0, 255, 255, 0.2);
879 | }
880 | .appError {
881 | background-color: rgba(255, 0, 0, 0.3);
882 | color: rgb(102, 43, 43);
883 | text-shadow: none;
884 | }
885 | /*
886 |
887 | Surgical classes
888 |
889 | */
890 | .no-js-hide {
891 | display: none;
892 | }
893 |
894 | .pullright {
895 | float: right;
896 | }
897 | /*
898 |
899 | Decorators
900 |
901 | */
902 | .bg {
903 | background-image: url(data:image/png;base64,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);
904 | }
905 |
906 | .bg-mount {
907 | display: block;
908 | background: url(/img/bg-mount.png) no-repeat -100px bottom;
909 | position: absolute;
910 | width: 100%;
911 | height: 135px;
912 | top: 51px;
913 | left: -1%;
914 | }
915 | .no-svg .bg-mount {
916 | z-index: 2;
917 | }
918 | .bg-tree {
919 | display: none;
920 | position: absolute;
921 | width: 320px;
922 | height: 200px;
923 | bottom: 33px;
924 | right: 0;
925 | z-index: 2;
926 | }
927 |
--------------------------------------------------------------------------------