├── Procfile
├── test
├── mocha.opts
├── configTest.js
└── lib
│ ├── routesTest.js
│ └── handlers
│ └── authTest.js
├── latest.dump
├── dist
├── compiled
│ ├── assetManifest.json
│ └── style.522e5d67.css
└── css
│ └── style.css
├── app
├── css
│ ├── style.less
│ ├── typography.less
│ ├── manager.less
│ ├── base.less
│ ├── media_queries.less
│ ├── overlay.less
│ ├── animations.less
│ └── employee.less
├── app.js
├── controllers
│ ├── searchController.js
│ └── orgchartController.js
├── vendor
│ ├── shake.js
│ ├── angular-ellipsis.js
│ └── angular-cache-2.3.4.min.js
├── services.js
└── directives.js
├── server
├── extensions
│ ├── onPreResponse.js
│ └── viewContext.js
├── routes
│ ├── routes.js
│ ├── authRoutes.js
│ ├── apiProxyRoutes.js
│ └── staticRoutes.js
├── views
│ ├── partials
│ │ ├── managerDetail.ejs
│ │ ├── reportsListView.ejs
│ │ ├── searchOverlay.ejs
│ │ ├── phoneSelectorOverlay.ejs
│ │ └── employeeDetail.ejs
│ └── home.ejs
├── cache
│ ├── orgchartDataConfig.js
│ ├── cacheScheduler.js
│ ├── orgchartCacheConfig.js
│ ├── cache.js
│ └── orgchartModel.js
└── handlers
│ ├── orgchartHandler.js
│ ├── searchHandler.js
│ └── authHandler.js
├── .gitignore
├── app.json
├── server.js
├── package.json
├── config.js
├── README.md
├── Gruntfile.js
└── LICENSE
/Procfile:
--------------------------------------------------------------------------------
1 | web: node server.js
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --reporter spec
2 | --recursive
--------------------------------------------------------------------------------
/latest.dump:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyric/Salesforce-OrgChart/master/latest.dump
--------------------------------------------------------------------------------
/dist/compiled/assetManifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "dist/css/style.css": "dist/compiled/style.522e5d67.css",
3 | "dist/js/bundle.js": "dist/compiled/bundle.dd8eb7e3.js"
4 | }
--------------------------------------------------------------------------------
/app/css/style.less:
--------------------------------------------------------------------------------
1 |
2 | @import "base.less";
3 | @import "typography.less";
4 | @import "employee.less";
5 | @import "manager.less";
6 | @import "overlay.less";
7 | @import "animations.less";
8 | @import "media_queries.less";
9 |
--------------------------------------------------------------------------------
/server/extensions/onPreResponse.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Exports all OnPreResponse extensions
3 | * @type {setViewContext|exports} - an array of extension functions
4 | */
5 |
6 | var viewContext = require( './viewContext' );
7 |
8 | module.exports = [ viewContext ];
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | lcov.info
3 | *.seed
4 | *.log
5 | *.csv
6 | *.dat
7 | *.out
8 | *.pid
9 | *.gz
10 |
11 | pids
12 | logs
13 | results
14 | build
15 | .grunt
16 |
17 | node_modules
18 |
19 | .idea/workspace.xml
20 | lib/public/js/
21 | lib/public/css/
22 | .idea/org-chart.iml
23 | .idea
24 | DF15-Org-Chart.iml
--------------------------------------------------------------------------------
/server/routes/routes.js:
--------------------------------------------------------------------------------
1 | var staticRoutes = require( './staticRoutes' ).routes;
2 | var authRoutes = require( './authRoutes' ).routes;
3 | var apiProxyRoutes = require( './apiProxyRoutes' ).routes;
4 |
5 | /**
6 | * Export all routes
7 | * @type {Array}
8 | */
9 | module.exports = staticRoutes.concat( authRoutes ).concat( apiProxyRoutes );
10 |
--------------------------------------------------------------------------------
/server/routes/authRoutes.js:
--------------------------------------------------------------------------------
1 | var Auth = require( '../handlers/authHandler' );
2 |
3 | var routes = [
4 | {
5 | method: 'POST',
6 | path: '/auth/signedrequest',
7 | handler: Auth.signedRequest
8 | },
9 | {
10 | method: 'GET',
11 | path: '/auth/signedrequest',
12 | handler: Auth.badRequest
13 | }
14 | ];
15 |
16 | exports.routes = routes;
--------------------------------------------------------------------------------
/server/routes/apiProxyRoutes.js:
--------------------------------------------------------------------------------
1 | var searchHandler = require( '../handlers/searchHandler' );
2 | var orgchartHandler = require( '../handlers/orgchartHandler' );
3 |
4 | var routes = [
5 | {
6 | method: 'GET',
7 | path: '/services/orgchart/{id}',
8 | handler: orgchartHandler
9 | },
10 | {
11 | method: 'POST',
12 | path: '/services/orgchart',
13 | handler: searchHandler
14 | }
15 | ];
16 |
17 | exports.routes = routes;
18 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Org Chart",
3 | "description": "An SF1 application that gives a graphical representation of an Org's employee hierarchy",
4 | "repository": "https://github.com/salesforce-it/Salesforce-OrgChart",
5 | "keywords": ["Salesforce", "Org", "Chart", "Node"],
6 | "success_url": "/welcome",
7 | "logo": "http://andybean.com/salesforce/Org%20Chart%20Icon%20for%20Heroku.png",
8 | "addons": [
9 | "heroku-postgresql:hobby-dev",
10 | "herokuconnect"
11 | ]
12 | }
--------------------------------------------------------------------------------
/server/views/partials/managerDetail.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
6 |
7 |
{{manager.name}}
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/server/cache/orgchartDataConfig.js:
--------------------------------------------------------------------------------
1 | var orgchartModel = require( './orgchartModel' );
2 | var orgchartDataConfig = {
3 | users: {
4 | tableName: 'salesforce.user',
5 | getData: function ( data ) {
6 | return new orgchartModel( data );
7 | }
8 | },
9 | contacts: {
10 | tableName: 'salesforce.contact',
11 | getData: function ( data ) {
12 | return new orgchartModel( data );
13 | }
14 | }
15 | };
16 |
17 | module.exports = orgchartDataConfig;
--------------------------------------------------------------------------------
/server/routes/staticRoutes.js:
--------------------------------------------------------------------------------
1 | var _ = require( 'lodash' );
2 | var Config = require( '../../config' );
3 |
4 | var route = {
5 | method: 'GET',
6 | path: '/dist/{param*}',
7 | handler: {
8 | directory: {
9 | path: './dist',
10 | lookupCompressed: true
11 | }
12 | }
13 | };
14 |
15 | // add in caching config based on ENV
16 | cacheConfig = Config.getCacheConfig();
17 | if ( cacheConfig ) {
18 | _.merge( route, { config: cacheConfig } );
19 | }
20 |
21 | exports.routes = [ route ];
--------------------------------------------------------------------------------
/app/app.js:
--------------------------------------------------------------------------------
1 | require('./vendor/canvas-all');
2 | require('angular-animate');
3 | require('angular-sanitize');
4 | require('angular-touch');
5 | require('./vendor/shake.js');
6 | require('./vendor/angular-cache-2.3.4.min');
7 |
8 | var orgchart = require('./controllers/orgchartController');
9 | var search = require('./controllers/searchController');
10 | var services = require('./services');
11 | var directives = require('./directives.js');
12 | var ellipsis = require('./vendor/angular-ellipsis.js');
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/css/typography.less:
--------------------------------------------------------------------------------
1 |
2 | // ---------------------------------------------------
3 | // Resets
4 | // ---------------------------------------------------
5 |
6 |
7 | @font-face {
8 | font-family: 'SalesforceSans';
9 | src: url("https://www2.sfdcstatic.com/system/shared/common/assets/fonts/SalesforceSans/SalesforceSans-Regular.woff") format('woff');
10 | font-weight: 300;
11 | font-style: normal;
12 | }
13 |
14 | body {
15 | /* Better Font Rendering =========== */
16 | -webkit-font-smoothing: antialiased;
17 | -moz-osx-font-smoothing: grayscale;
18 | }
19 |
--------------------------------------------------------------------------------
/server/views/partials/reportsListView.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
6 |
7 |
8 |
{{report.name}}
9 |
10 |
{{report.title}}
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/server/extensions/viewContext.js:
--------------------------------------------------------------------------------
1 | var Config = require( './../../config' );
2 | var Boom = require( 'hapi' ).error;
3 |
4 | function setViewContext( request, reply ) {
5 | var assetManifest = require( '../../dist/compiled/assetManifest.json' );
6 |
7 | var response = request.response;
8 | if ( !assetManifest ) {
9 | // 500 error
10 | return reply( Boom.badImplementation( 'Missing assetManifest.json' ) );
11 | }
12 | // Set default view context
13 | if ( response.variety === 'view' ) {
14 | var context = response.source.context;
15 | context.assetManifest = assetManifest;
16 | context.config = Config;
17 | }
18 |
19 | return reply();
20 | }
21 |
22 | module.exports = setViewContext;
23 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var Hapi = require( 'hapi' ),
2 | routes = require( './server/routes/routes.js' ),
3 | config = require( './config.js' ),
4 | util = require( 'util' ),
5 | onPreResponse = require( './server/extensions/onPreResponse' ),
6 | cacheScheduler = require( './server/cache/cacheScheduler' );
7 |
8 | //prime the cache and set up the automatic refresh of data
9 | cacheScheduler.run();
10 |
11 | var server = new Hapi.Server( config.host, config.port, config.server );
12 |
13 | // routes
14 | server.route( routes );
15 |
16 | // extensions
17 | server.ext( 'onPreResponse', onPreResponse );
18 |
19 | // start
20 | server.start();
21 |
22 | server.log( [ 'debug', 'hapi' ], util.format( 'Server running with config: %j facadeClient: %j', config, config.facadeClient() ) );
23 |
--------------------------------------------------------------------------------
/server/views/partials/searchOverlay.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | -
10 | {{searchs.name}}
11 |
12 | -
13 | ...
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/server/handlers/orgchartHandler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by casakawa on 8/25/15.
3 | */
4 | var cache = require( '../cache/cache' );
5 | var _ = require( 'lodash' );
6 |
7 | /**
8 | * first grab the user configuration, then use that config to refer
9 | * either the contactCache or the userCache.
10 | */
11 |
12 | var orgchartHandler = function ( request, reply ) {
13 | //search for a by email.
14 | var requestedUser = request.params.id;
15 |
16 | cache.getOrgchartData( function ( people ) {
17 | var currentUser = null;
18 |
19 | //search for currentUser
20 | for ( var i = 0; i < people.length; i++ ) {
21 | var person = people[ i ];
22 | if ( person.email == requestedUser ) {
23 | currentUser = people[ i ];
24 | break;
25 | }
26 | }
27 |
28 | var neighborhood = currentUser.getNeighborhood( people );
29 |
30 | reply( _.merge( currentUser, neighborhood ) );
31 | } );
32 | };
33 |
34 | module.exports = orgchartHandler;
35 |
--------------------------------------------------------------------------------
/server/handlers/searchHandler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by casakawa on 8/14/15.
3 | */
4 | //this file holds the cached users array
5 | var cache = require( '../cache/cache' );
6 |
7 | var searchHandler = function ( request, reply ) {
8 |
9 | //return array
10 | var returnArray = [];
11 | cache.getOrgchartData( function ( people ) {
12 | //catch the search term and convert to lowercase
13 | var searchTerm = request.payload.search.toLowerCase();
14 |
15 | //loop through all users looking for matches
16 | for ( var i = 0; i < people.length; i++ ) {
17 | //also convert the names in the array to lowercase.
18 | var compareString = people[ i ].name.toLowerCase();
19 |
20 | //adds any matches to the return array, this also handles partial matches
21 | if ( compareString.indexOf( searchTerm ) > -1 ) {
22 | returnArray.push( people[ i ] );
23 | }
24 | }
25 |
26 | reply( returnArray );
27 | } );
28 | };
29 |
30 | module.exports = searchHandler;
31 |
--------------------------------------------------------------------------------
/server/handlers/authHandler.js:
--------------------------------------------------------------------------------
1 | var decode = require( 'salesforce-signed-request' );
2 | var Config = require( '../../config.js' );
3 | var Boom = require( 'hapi' ).error;
4 |
5 | var authHandlers = {
6 | // POST /auth/signedrequest
7 | signedRequest: function ( request, reply ) {
8 |
9 | if ( !Config.appSecret() ) {
10 | return reply( Boom.badImplementation( 'Missing AppSecret configuration' ) );
11 | }
12 | var canvasContext = decode( request.payload.signed_request, Config.appSecret() );
13 |
14 | if ( canvasContext instanceof Error ) {
15 | return reply( Boom.badRequest( canvasContext.message ) );
16 | }
17 |
18 | var data = {
19 | canvasContext: canvasContext,
20 | config: Config
21 | };
22 |
23 | reply.view( 'home', data );
24 | },
25 |
26 | // GET /auth/signedRequest
27 | badRequest: function ( request, reply ) {
28 | Boom.methodNotAllowed( 'signedrequest must be an HTTP POST' );
29 | }
30 | };
31 |
32 | module.exports = authHandlers;
--------------------------------------------------------------------------------
/server/cache/cacheScheduler.js:
--------------------------------------------------------------------------------
1 | var orgConfig = require( './orgchartCacheConfig' );
2 | var cache = require( './cache' );
3 | var schedule = require( 'node-schedule' );
4 | var pg = require( 'pg' );
5 |
6 | function run() {
7 | console.log( 'Running the cache scheduler' );
8 | //only allows for one client connection at a time.
9 | //(herokuConnect free tier only allows one connection at a time)
10 | pg.defaults.poolSize = 1;
11 |
12 | /** this queries the configuration settings, and then uses this information to
13 | * initiate and periodically refresh the cache.
14 | */
15 | orgConfig.getConfig( buildCache );
16 |
17 | //scheduler runs every ten mins
18 | var rule = new schedule.RecurrenceRule();
19 | rule.minute = new schedule.Range( 0, 0, 10 );
20 |
21 | schedule.scheduleJob( rule, function () {
22 | orgConfig.getConfig( buildCache );
23 | } );
24 | }
25 |
26 | //initiate the cache, and flush the other cache if it exists
27 | function buildCache() {
28 |
29 | cache.flush();
30 | cache.getOrgchartData( function () {
31 | } );
32 | }
33 |
34 | module.exports.run = run;
--------------------------------------------------------------------------------
/test/configTest.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var config = require('../config');
3 |
4 |
5 | describe('Config', function(){
6 |
7 | it('should return app secret from ENV var', function(){
8 | process.env.APP_SECRET = 'foo';
9 | expect(config.appSecret()).to.equal('foo');
10 | });
11 |
12 | describe('facadeClient', function(){
13 |
14 | it('should return facade url from ENV var', function () {
15 |
16 | var facadeurl = 'http://test.com';
17 | process.env.FACADE_URL = facadeurl;
18 | expect(config.facadeClient().url).to.equal(facadeurl);
19 | delete process.env.FACADE_URL;
20 | });
21 |
22 | it('should default to dev facade when no ENV var is set', function () {
23 |
24 | delete process.env.FACADE_URL;
25 | expect(config.facadeClient().url).to.equal('http://sfo-eapps-facd-ld1:8080');
26 |
27 | });
28 |
29 | it('should return a proxy URL based on ENV var', function(){
30 |
31 | var proxy = 'http://localhost:8888';
32 | process.env.DEBUG_PROXY = proxy;
33 |
34 | expect(config.facadeClient().proxy).to.equal(proxy);
35 |
36 | delete process.env.DEBUG_PROXY;
37 | });
38 | });
39 |
40 |
41 | });
--------------------------------------------------------------------------------
/server/views/partials/phoneSelectorOverlay.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 | -
11 | {{phone.type}}: {{phone.value}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | It looks like your access token may have expired.
Wanna try refreshing it?
21 |
22 |
23 | -
24 | Yes, please!
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/server/cache/orgchartCacheConfig.js:
--------------------------------------------------------------------------------
1 | var pg = require( 'pg' );
2 | var isUser;
3 | var connectionString = process.env.DATABASE_URL;
4 |
5 | function getConfig( callback ) {
6 | console.log( 'Retrieving the org chart config' );
7 | var hierarchyType = null;
8 | pg.connect( connectionString, function ( err, client, done ) {
9 | if ( err )
10 | console.log( err );
11 |
12 | //creates a query to grab the config for users or contacts
13 | var query = client.query( 'SELECT * FROM salesforce.orgchartconfig__c LIMIT 1' );
14 | query.on( 'row', function ( row ) {
15 | hierarchyType = row.orghierarchytype__c;
16 | } );
17 |
18 | query.on( 'end', function ( result ) {
19 | //true if config is users, or false if contacts. default to users if config is not set
20 | if ( hierarchyType ) {
21 | isUser = hierarchyType != 'Contacts';
22 | } else {
23 | isUser = true;
24 | }
25 |
26 | console.log( 'Org Chart configured for user hierarchy? ', isUser );
27 |
28 | done();
29 | callback();
30 | } );
31 | } );
32 | }
33 |
34 | module.exports = {
35 | getConfig: getConfig,
36 | isUserHierachy: function () {
37 | return isUser;
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/test/lib/routesTest.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var _ = require('lodash');
3 | var routes = require('.././routes.js');
4 | var Auth = require('.././authHandler.js');
5 |
6 | describe('Routes', function(){
7 |
8 | it('should export an array', function(){
9 | expect(routes).to.be.a('array');
10 | });
11 |
12 | describe('/auth/signedrequest', function(){
13 |
14 | var route;
15 |
16 | beforeEach(function(){
17 | route = _.find(routes, {path: '/auth/signedrequest'})
18 | });
19 |
20 | it('should be defined', function()
21 | {
22 | expect(route).to.not.be.undefined;
23 | });
24 |
25 | it('should be configured for POSTs', function(){
26 | expect(route).to.have.property('method', 'POST')
27 | });
28 |
29 | it('should be handled by Auth#signedRequest', function(){
30 | expect(route.handler).to.equal(Auth.signedRequest);
31 | });
32 | });
33 |
34 | describe('/eapps_facade/services/', function(){
35 |
36 | it('should have a /orgchart/{id} route', function(){
37 | var route = _.find(routes, {path: '/eapps_facade/services/orgchart/{id}'});
38 | expect(route).to.exist;
39 | expect(route.method).to.equal('GET');
40 | });
41 |
42 | it('should have /orgchart route', function(){
43 | var route = _.find(routes, {path: '/eapps_facade/services/orgchart'});
44 | expect(route).to.exist;
45 | });
46 |
47 |
48 | });
49 |
50 | });
51 |
52 |
--------------------------------------------------------------------------------
/app/controllers/searchController.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var app = angular.module('orgchart.controllers', []);
4 |
5 | app.controller('searchController', ['$scope', '$timeout', 'Search', '$sce', 'sharedFunctions',
6 | function ($scope, $timeout, Search, $sce, sharedFunctions) {
7 |
8 | $scope.searchEmployee = function (searchString) {
9 |
10 | Search.getSearch({search: searchString}).then(onSuccess, onError);
11 |
12 | function onSuccess(response) {
13 |
14 | if (response.data.length > 0) {
15 | $scope.search = response.data;
16 | } else {
17 | var currSearchString = $sce.trustAsHtml("" + searchString + "");
18 | $scope.search = [
19 | {"id": $scope.employee.id, "name": currSearchString + " is not found"}
20 | ];
21 | }
22 |
23 | sharedFunctions.toggleSearchDisplay("flex");
24 |
25 | }
26 |
27 | function onError(response) {
28 | if (response.status == 401) {
29 | $scope.toggleSearch();
30 | $scope.toggleExpiredTokenOverlay();
31 | }
32 | }
33 | };
34 |
35 | var init = function () {
36 | var searchResultWrapper = document.getElementById('searchResultWrapper');
37 |
38 | searchResultWrapper.addEventListener('touchmove', function (event) {
39 | event.preventDefault();
40 | });
41 | };
42 |
43 | init();
44 |
45 | }
46 | ]);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "OrgChart",
3 | "version": "1.0.0",
4 | "description": "Org Chart",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "mocha",
8 | "prepublish": "./node_modules/grunt-cli/bin/grunt deploy",
9 | "start": "node server.js"
10 | },
11 | "author": {
12 | "name": "PDXBrewHub",
13 | "email": "pdxbrewhub@salesforce.com",
14 | "url": "https://git.soma.salesforce.com/PDXBrewHub"
15 | },
16 | "license": "MIT",
17 | "repository": {
18 | "type": "git",
19 | "url": "https://git.soma.salesforce.com/PDXBrewHub/org-chart-client.git"
20 | },
21 | "browserify-shim": {
22 | "./app/vendor/canvas-all.js": "Sfdc.canvas"
23 | },
24 | "dependencies": {
25 | "angular-animate": "^1.4.4",
26 | "angular-sanitize": "^1.4.4",
27 | "angular-touch": "^1.4.4",
28 | "browserify": "^11.0.1",
29 | "ejs": "^2.3.3",
30 | "hapi": "^4.0.0",
31 | "lodash": "^2.4.1",
32 | "lodash-node": "^3.10.1",
33 | "node-schedule": "^0.2.9",
34 | "pg": "4.x",
35 | "request": "^2.34.0",
36 | "salesforce-signed-request": "0.0.1"
37 | },
38 | "devDependencies": {
39 | "browserify-ngmin": "^0.1.0",
40 | "browserify-shim": "^3.5.0",
41 | "chai": "^1.9.0",
42 | "grunt-autoprefixer": "latest",
43 | "grunt-browserify": "^4.0.0",
44 | "grunt-cli": "latest",
45 | "grunt-contrib-clean": "^0.5.0",
46 | "grunt-contrib-compress": "^0.13.0",
47 | "grunt-contrib-concat": "latest",
48 | "grunt-contrib-cssmin": "latest",
49 | "grunt-contrib-less": "latest",
50 | "grunt-contrib-watch": "latest",
51 | "grunt-filerev": "^2.3.1",
52 | "grunt-filerev-assets": "^0.3.1",
53 | "grunt-purifycss": "^0.1.0",
54 | "mocha": "^1.17.1",
55 | "nock": "^0.27.3",
56 | "uglifyify": "^3.0.1"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/server/cache/cache.js:
--------------------------------------------------------------------------------
1 | var pg = require( 'pg' );
2 | var connectionString = process.env.DATABASE_URL;
3 | var dataConfig = require( './orgchartDataConfig' );
4 | var orgConfig = require( './orgchartCacheConfig' );
5 |
6 | //this file holds the cached data
7 | var people = [];
8 |
9 | /**
10 | * Initializes a temp array to hold the queried data, then
11 | * once the query is complete assigns it to the people cache
12 | */
13 | var prime = function ( config, callback ) {
14 | console.log( 'Populating people cache' );
15 |
16 | var tempUsers = [];
17 |
18 | pg.connect( connectionString, function ( err, client, done ) {
19 | if ( err ) {
20 | console.log( err );
21 | }
22 |
23 | //create a query to grab all contacts from the org
24 | var query = client.query( 'SELECT * FROM ' + config.tableName );
25 | query.on( 'row', function ( row ) {
26 | /** grab all the data **/
27 | var orgChartNode = config.getData( row );
28 |
29 | //one orgChart node is now complete, push orgChartNode into the contacts list
30 | tempUsers.push( orgChartNode );
31 | } );
32 |
33 | query.on( 'end', function ( result ) {
34 | console.log( 'User cache populated, size: ', tempUsers.length );
35 | people = tempUsers;
36 | done(); //call `done()` to release the client back to the pool
37 | callback( people );
38 | } );
39 | } );
40 | };
41 |
42 | module.exports = {
43 | getOrgchartData: function ( callback ) {
44 | if ( people.length > 0 ) {
45 | callback( people );
46 | } else {
47 | var type = orgConfig.isUserHierachy() ? 'users' : 'contacts';
48 | prime( dataConfig[ type ], function ( people ) {
49 | callback( people );
50 | } );
51 | }
52 | },
53 | flush: function () {
54 | console.log( 'Flushed people' );
55 | people = [];
56 | }
57 | };
--------------------------------------------------------------------------------
/app/css/manager.less:
--------------------------------------------------------------------------------
1 | // This section controls the animation sequence
2 | // for the managers on display.
3 | //
4 |
5 | #managerDetail {
6 | background-color: #FFFFFF;
7 | text-align: center;
8 | width: 100%;
9 | overflow-y: scroll;
10 | height: 325px;
11 | height: ~"calc(100% - 180px)"; // has to be escaped to prevent LESS from screwing it up.
12 | position: absolute;
13 | // The following line is key to protect z-order
14 | // for transformed elements
15 | transform: translate3D(0px,0px,-1px) rotateX(180deg);
16 | }
17 |
18 | .manager-image {
19 | height: 60px;
20 | width: 60px;
21 | background-size: 60px;
22 | border: 4px solid #fff;
23 | border-radius: 50%;
24 | display: inline-block;
25 | background-position: center;
26 | background-repeat: no-repeat;
27 | }
28 |
29 | // The spacing of the managers, and
30 | // the lines that connect the li elements
31 | .reporting-structure {
32 | // The following line is key to protect z-order
33 | // for transformed elements
34 | transform: translate3D(0px,0px,-1px) rotateX(180deg);
35 | position: absolute;
36 | width: 100%;
37 | padding-bottom: 50px;
38 | }
39 | .reporting-structure li {
40 | margin: 30px auto;
41 | position: relative;
42 | }
43 |
44 | // add connectors to before every li except for the first
45 | // add a connector bar on the last list item
46 | // the animation fadeIn is located in the animations less file
47 | .reporting-structure li::before,
48 | .reporting-structure li:nth-last-child(1)::after {
49 | animation: fadeIn 5s 0 1 ease normal;
50 | content: '';
51 | position: absolute;
52 | top: -62px;
53 | left: 50%;
54 | width: 2px;
55 | height: 70px;
56 | background: #eee;
57 | z-index: -1;
58 | }
59 | .reporting-structure li:nth-last-child(1)::after{
60 | top: 62px;
61 | }
62 |
63 | // hide the bar on the first item
64 | .reporting-structure li:first-child::before {
65 | display: none;
66 | }
67 |
68 | // The delay sequence for the list elements
69 | // Animation loop
70 | @n: 20; //number of iterations
71 | .loop (@i) when (@i < @n + 1) {
72 |
73 | .reporting-structure li:nth-last-child(@{i}) i,
74 | .reporting-structure li:nth-last-child(@{i}) p { animation-delay: (.04s * (@i - 1)); }
75 | .loop(@i + 1);
76 | }
77 | .loop(1);
78 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | Fs = require( 'fs' );
2 |
3 |
4 | var ssl = function () {
5 | var useSSL = process.env.USE_SSL ? true : false;
6 | if ( useSSL ) {
7 | return {
8 | key: Fs.readFileSync( '/etc/apache2/ssl/host.key' ),
9 | cert: Fs.readFileSync( '/etc/apache2/ssl/server.crt' )
10 |
11 | };
12 | }
13 | };
14 |
15 | var uri = function ( config ) {
16 | return (config.server.tls ? 'https://' : 'http://') + config.host + ':' + config.port
17 | };
18 |
19 | var debug = function ( config ) {
20 | if ( config.isDevelopment() ) {
21 | return {
22 | request: [
23 | 'hapi', // log request,handler,response flow
24 | 'error' // logs tagged with error
25 | ]
26 | }
27 | }
28 | };
29 |
30 | var config = {
31 | host: '0.0.0.0',
32 | port: (parseInt( process.env.PORT, 10 ) || 5150),
33 | appSecret: function () {
34 | return process.env.APP_SECRET
35 | },
36 | env: function () {
37 | return process.env.NODE_ENV || "development";
38 | },
39 | isDevelopment: function () {
40 | return this.env() === 'development';
41 | },
42 | //https://github.com/spumko/hapi/blob/master/docs/Reference.md#server-options
43 | server: {
44 | views: {
45 | engines: {
46 | 'ejs': 'ejs'
47 | },
48 | path: './server/views',
49 | isCached: false
50 | },
51 | tls: ssl()
52 |
53 | },
54 | getCacheConfig: function() {
55 | return !this.isDevelopment() ? { cache: { expiresIn: 31536000000 } } : null
56 | },
57 | facadeClient: function () {
58 | // STAGING URL : https://ngmobile-stage.corp.salesforce.com
59 | var url = process.env.FACADE_URL || 'http://sfo-eapps-facd-ld1:8080';
60 | var proxy = process.env.DEBUG_PROXY || null;
61 | var ca = proxy ? Fs.readFileSync( './charles-proxy-ssl.crt' ) : null;
62 |
63 | return {
64 | url: url,
65 | proxy: proxy,
66 | ca: ca,
67 | strictSSL: true
68 | };
69 |
70 | }
71 |
72 | };
73 |
74 | config.uri = uri( config );
75 | config.server.debug = debug( config );
76 |
77 | module.exports = config;
78 |
79 |
80 |
--------------------------------------------------------------------------------
/app/css/base.less:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------
2 | //variables
3 | // ---------------------------------------------------
4 |
5 | @link: #293f54;
6 |
7 | // ---------------------------------------------------
8 | // Resets
9 | // ---------------------------------------------------
10 |
11 | /* apply a natural box layout model to all elements */
12 | *, *:before, *:after {
13 | -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;
14 | }
15 |
16 | body {
17 | font-family: 'SalesforceSans';
18 | background: #fff;
19 | color: #999999;
20 | /* font-smoothing and tap-highlight */
21 | font-smoothing: antialiased;
22 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
23 | margin: 0;
24 | padding: 0;
25 | }
26 |
27 | // Links
28 | a,
29 | .icon-utility-call {
30 | color: @link;
31 | text-decoration: none;
32 | }
33 |
34 | // lists pattern from S1 guidelines
35 | //TODO: refactor for speed
36 | .list-horizontal {
37 | margin: 0;
38 | padding: 0;
39 | list-style:none;
40 | > li {
41 | &,
42 | > a {
43 | display:inline-block;
44 | *display:inline;
45 | zoom:1;
46 | }
47 | }
48 | }
49 |
50 | .list-plain {
51 | list-style: none;
52 | }
53 |
54 | p {
55 | margin: 0;
56 | padding: 0;
57 | }
58 |
59 | ul {
60 | list-style: none;
61 | margin: 0;
62 | padding: 0;
63 | }
64 | // ---------------------------------------------------
65 | // Miscellaneous
66 | // ---------------------------------------------------
67 |
68 | // box shadow, could be refactored into a mixin.
69 | .shadow {
70 | box-shadow: 0 2px 0 rgba(0,0,0,.1);
71 | }
72 |
73 | .hide {
74 | display: none !important;
75 | }
76 |
77 | .show {
78 | display: block !important;
79 | }
80 |
81 | .fadeIt {
82 | opacity: 0.01;
83 | }
84 |
85 | .unfadeIt {
86 | opacity: 1.0;
87 | }
88 |
89 | .showFlex {
90 | display: flex !important;
91 | }
92 |
93 | .capitalize {
94 | text-transform: capitalize;
95 | }
96 |
97 | .overflowScrolling-touch {
98 | -webkit-overflow-scrolling: touch;
99 | }
100 |
101 | .overflowScrolling-default {
102 | -webkit-overflow-scrolling: auto;
103 | }
104 |
105 | #mainWrapper {
106 | height: 500px;
107 | position: relative;
108 | background-color: #fff;
109 | }
110 |
111 | #detailWrapper {
112 | margin: 0;
113 | padding: 0;
114 | }
115 |
116 |
--------------------------------------------------------------------------------
/app/css/media_queries.less:
--------------------------------------------------------------------------------
1 | /* Iphone 5 in Portrait */
2 | @media only screen
3 | and (min-device-width : 320px)
4 | and (max-device-width : 568px)
5 | and (orientation : portrait) {
6 | // #managerDetail {
7 | // height: 325px;
8 | // }
9 | }
10 |
11 | /* Iphone 4S Portrait */
12 | @media only screen
13 | and (min-device-width : 320px)
14 | and (max-device-width : 480px)
15 | and (orientation : portrait) {
16 | // #managerDetail {
17 | // height: 325px;
18 | // }
19 | }
20 |
21 | /* Retina Ipad Portrait */
22 | @media only screen
23 | and (min-device-width : 768px)
24 | and (max-device-width : 1024px)
25 | and (orientation : portrait)
26 | and (-webkit-min-device-pixel-ratio: 2) {
27 | // #managerDetail {
28 | // height: 855px;
29 | // }
30 | #reportsListView {
31 | div {
32 | width: 90%;
33 | }
34 | .title {
35 | width: initial;
36 | margin: 0;
37 | }
38 | .name {
39 | width: initial;
40 | margin: 0;
41 | }
42 | }
43 | }
44 |
45 | /* Retina Ipad Landscape */
46 | @media only screen
47 | and (min-device-width : 768px)
48 | and (max-device-width : 1024px)
49 | and (orientation : landscape)
50 | and (-webkit-min-device-pixel-ratio: 2) {
51 | // #managerDetail {
52 | // height: 575px;
53 | // }
54 | #reportsListView {
55 | div {
56 | width: 90%;
57 | }
58 | .title {
59 | width: initial;
60 | margin: 0;
61 | }
62 | .name {
63 | width: initial;
64 | margin: 0;
65 | }
66 | }
67 | }
68 |
69 | /* Prior Gen Ipad Portrait */
70 | @media only screen
71 | and (min-device-width : 768px)
72 | and (max-device-width : 1024px)
73 | and (orientation : portrait) {
74 | // #managerDetail {
75 | // height: 795px;
76 | // }
77 | #reportsListView {
78 | div {
79 | width: 90%;
80 | }
81 | .title {
82 | width: initial;
83 | margin: 0;
84 | }
85 | .name {
86 | width: initial;
87 | margin: 0;
88 | }
89 | }
90 | }
91 |
92 | /* Prior Gen Ipad Landscape */
93 | @media only screen
94 | and (min-device-width : 768px)
95 | and (max-device-width : 1024px)
96 | and (orientation : landscape) {
97 | // #managerDetail {
98 | // height: 535px;
99 | // }
100 | #reportsListView {
101 | div {
102 | width: 90%;
103 | }
104 | .title {
105 | width: initial;
106 | margin: 0;
107 | }
108 | .name {
109 | width: initial;
110 | margin: 0;
111 | }
112 | }
113 | }
--------------------------------------------------------------------------------
/server/views/home.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Salesforce OrgChart
6 |
7 |
8 |
9 |
10 |
11 |
12 | <% if(config.isDevelopment()){ %>
13 |
14 | <% } else { var path = assetManifest[ 'dist/css/style.css' ]; %>
15 |
16 | <% } %>
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
30 |
31 | <% include partials/searchOverlay %>
32 |
33 | <% include partials/phoneSelectorOverlay %>
34 |
35 |
36 |
37 | <% include partials/managerDetail %>
38 |
39 | <% include partials/employeeDetail %>
40 |
41 | <% include partials/reportsListView %>
42 |
43 | <% if(typeof canvasContext !== 'undefined') { %>
44 |
48 | <% } %>
49 |
50 |
51 |
52 | <% if(config.isDevelopment()){ %>
53 |
54 | <% } else { var jsPath = assetManifest[ 'dist/js/bundle.js' ]; %>
55 |
56 | <% } %>
57 |
58 | <% // WORKAROUND FOR SF1 KEYBOARD BUG %>
59 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/app/css/overlay.less:
--------------------------------------------------------------------------------
1 | // styles for the overlay and search wrappers
2 |
3 | #searchResultWrapper {
4 | z-index: 14;
5 | position: absolute;
6 | top: 48px;
7 | left: 0;
8 | width: 100%;
9 | height: 100%;
10 | display: -webkit-box;
11 | display: -moz-box;
12 | display: -ms-flexbox;
13 | display: -webkit-flex;
14 | align-items: center;
15 | justify-content: center;
16 | }
17 |
18 | #searchResultWrapper_row {
19 | width: 100%;
20 | height: 100%;
21 | }
22 |
23 | #searchResultWrapper ul {
24 | /* margin: 0 42px 0 15px; */
25 | height: 100%;
26 | padding: 5px 0;
27 | background-color: white;
28 | /* border-bottom-right-radius:5px;
29 | border-bottom-left-radius:5px; */
30 | }
31 |
32 | #searchResultWrapper ul li {
33 | cursor: pointer;
34 | padding: 0;
35 | margin: 10px 10px;
36 | }
37 |
38 | #searchBoxWrapper {
39 | position: absolute;
40 | top: 5px;
41 | left: 50%;
42 | z-index: 15;
43 | width: 86%;
44 | margin-left: -47%;
45 | }
46 |
47 | #searchBox {
48 | width: 100%;
49 | height: 36px;
50 | padding: 6px 10px;
51 | border-radius: 5px;
52 | border: 1px solid #85929F;
53 | font-size: 16px;
54 | }
55 |
56 | #searchToggle {
57 | padding: 0;
58 | position: fixed;
59 | right: 0;
60 | top: 0;
61 | z-index: 999;
62 | height: 44px;
63 | width: 44px;
64 | background: 0;
65 | border: 0;
66 | outline: 0;
67 | }
68 |
69 | .icon-utility-search {
70 | color: #999;
71 | }
72 |
73 | .unknownSearch {
74 | color: black;
75 | }
76 |
77 | .icon-utility-close {
78 | color: white;
79 | }
80 |
81 | .icon-utility-close, .icon-utility-search {
82 | font-size: 15px;
83 | }
84 |
85 | #closeSelector {
86 | margin: 0;
87 | padding: 0;
88 | z-index: 15;
89 | position: absolute;
90 | top: -44px;
91 | right: -4px;
92 | }
93 |
94 | #phoneSelector {
95 | z-index: 15;
96 | position: absolute;
97 | height: 100%;
98 | width: 100%;
99 | padding: 0;
100 | margin: 0;
101 | display: -webkit-box;
102 | display: -moz-box;
103 | display: -ms-flexbox;
104 | display: -webkit-flex;
105 | display: flex;
106 | align-items: center;
107 | justify-content: center;
108 | }
109 |
110 | #expiredTokenOverlay {
111 | z-index: 15;
112 | position: absolute;
113 | height: 100%;
114 | width: 100%;
115 | padding: 0;
116 | margin: 0;
117 | display: -webkit-box;
118 | display: -moz-box;
119 | display: -ms-flexbox;
120 | display: -webkit-flex;
121 | display: flex;
122 | align-items: center;
123 | justify-content: center;
124 | }
125 |
126 | #expiredTokenMessage {
127 | width: 100%;
128 | padding: 10px;
129 | text-align: center;
130 | }
131 |
132 | #selectorRow {
133 | width: 260px;
134 | border-radius: 5px;
135 | background-color: white;
136 | padding: 14px;
137 | position: relative;
138 | }
139 |
140 | .selection li {
141 | border: 1px solid #dfe0e1;
142 | color: #5c7995;
143 | font-size: 16px;
144 | font-weight: 700;
145 | width: 100%;
146 | padding: 7px 0;
147 | margin: 0 auto 14px auto;
148 | text-align: center;
149 | cursor: pointer;
150 | border-radius: 5px;
151 | &:last-child {
152 | margin: 0 !important;
153 | }
154 | }
155 |
156 | #overlay {
157 | opacity: 0.5;
158 | background: #344A5F;
159 | width: 100%;
160 | height: 100%;
161 | z-index: 10;
162 | top: 0;
163 | left: 0;
164 | position: fixed;
165 | }
--------------------------------------------------------------------------------
/server/cache/orgchartModel.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var OrgchartModel = function ( data ) {
4 | this.id = data.sfid;
5 | this.name = data.name;
6 | this.firstName = data.firstname;
7 | this.lastName = data.lastname;
8 | this.title = data.title;
9 | this.phones = buildPhoneArray( {
10 | 'desk': data.phone || null,
11 | 'mobilePhone': data.mobilephone || null,
12 | 'otherPhone': data.otherPhone || null
13 | } );
14 | this.email = data.email;
15 | this.photo = {
16 | largePhotoUrl: data.photourl || data.fullphotourl || null,
17 | smallPhotoUrl: data.photourl || data.smallphotourl || null
18 | };
19 | this.reportsToId = data.reportstoid || data.managerid;
20 | this.peers = [];
21 | this.directReports = [];
22 | this.reportsTo = [];
23 | };
24 |
25 | function buildPhoneArray( phones ) {
26 | var phoneArray = [];
27 |
28 | for ( var i in phones ) {
29 | if ( phones[ i ] != null ) {
30 | var phoneObject = {
31 | type: i,
32 | value: phones[ i ]
33 | };
34 | phoneArray.push( phoneObject );
35 | }
36 | }
37 |
38 | return phoneArray;
39 | }
40 |
41 | function getManagerChain( currentPerson, orgchartData ) {
42 | var reportsTo = [];
43 | if ( !currentPerson.reportsTo ) {
44 | return;
45 | }
46 |
47 | function getManager( current, orgData ) {
48 | for ( var i = 0; i < orgData.length; i++ ) {
49 | var person = orgData[ i ];
50 | if ( current.reportsToId === person.id ) {
51 | reportsTo.push( person );
52 | getManager( person, orgData )
53 | } else {
54 | clearNeighborhood( person );
55 | }
56 | }
57 | }
58 |
59 | getManager( currentPerson, orgchartData );
60 |
61 | return reportsTo.reverse();
62 | }
63 |
64 | function getPeers( currentPerson, orgchartData ) {
65 | var peers = [];
66 |
67 | for ( var i = 0; i < orgchartData.length; i++ ) {
68 | var person = orgchartData[ i ];
69 | //build peersArray (not sorted), all share the same boss.
70 | //The top of the hierarchy has no peers.
71 | if ( currentPerson.reportsToId != null && currentPerson.reportsToId === person.reportsToId && currentPerson.email != person.email ) {
72 | clearNeighborhood( person );
73 | peers.push( person );
74 | }
75 | }
76 |
77 | return peers;
78 | }
79 |
80 | function getDirectReports( currentPerson, orgchartData ) {
81 | var directReports = [];
82 |
83 | //build directReports list (not sorted), these are ones minions
84 | for ( var i = 0; i < orgchartData.length; i++ ) {
85 | var person = orgchartData[ i ];
86 |
87 | if ( currentPerson.id === person.reportsToId ) {
88 | clearNeighborhood( person );
89 | directReports.push( person );
90 | }
91 | }
92 |
93 | return directReports;
94 | }
95 |
96 | function clearNeighborhood( person ) {
97 | person.peers = [];
98 | person.directReports = [];
99 | person.reportsTo = [];
100 | }
101 |
102 | OrgchartModel.prototype.getNeighborhood = function ( orgchartData ) {
103 | return {
104 | reportsTo: getManagerChain( this, orgchartData ),
105 | peers: getPeers( this, orgchartData ),
106 | directReports: getDirectReports( this, orgchartData )
107 | }
108 | };
109 |
110 | module.exports = OrgchartModel;
--------------------------------------------------------------------------------
/app/vendor/shake.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Find more about this plugin by visiting
4 | * http://alxgbsn.co.uk/
5 | *
6 | * Copyright (c) 2010-2012 Alex Gibson
7 | * Released under MIT license
8 | *
9 | */
10 |
11 | (function (window, document) {
12 |
13 | function Shake() {
14 |
15 | //feature detect
16 | this.hasDeviceMotion = 'ondevicemotion' in window;
17 |
18 | //default velocity threshold for shake to register
19 | this.threshold = 15;
20 |
21 | //use date to prevent multiple shakes firing
22 | this.lastTime = new Date();
23 |
24 | //accelerometer values
25 | this.lastX = null;
26 | this.lastY = null;
27 | this.lastZ = null;
28 |
29 | //create custom event
30 | if (typeof document.CustomEvent === "function") {
31 | this.event = new document.CustomEvent('shake', {
32 | bubbles: true,
33 | cancelable: true
34 | });
35 | } else if (typeof document.createEvent === "function") {
36 | this.event = document.createEvent('Event');
37 | this.event.initEvent('shake', true, true);
38 | } else {
39 | return false;
40 | }
41 | }
42 |
43 | //reset timer values
44 | Shake.prototype.reset = function () {
45 | this.lastTime = new Date();
46 | this.lastX = null;
47 | this.lastY = null;
48 | this.lastZ = null;
49 | };
50 |
51 | //start listening for devicemotion
52 | Shake.prototype.start = function () {
53 | this.reset();
54 | if (this.hasDeviceMotion) { window.addEventListener('devicemotion', this, false); }
55 | };
56 |
57 | //stop listening for devicemotion
58 | Shake.prototype.stop = function () {
59 |
60 | if (this.hasDeviceMotion) { window.removeEventListener('devicemotion', this, false); }
61 | this.reset();
62 | };
63 |
64 | //calculates if shake did occur
65 | Shake.prototype.devicemotion = function (e) {
66 |
67 | var current = e.accelerationIncludingGravity,
68 | currentTime,
69 | timeDifference,
70 | deltaX = 0,
71 | deltaY = 0,
72 | deltaZ = 0;
73 |
74 | if ((this.lastX === null) && (this.lastY === null) && (this.lastZ === null)) {
75 | this.lastX = current.x;
76 | this.lastY = current.y;
77 | this.lastZ = current.z;
78 | return;
79 | }
80 |
81 | deltaX = Math.abs(this.lastX - current.x);
82 | deltaY = Math.abs(this.lastY - current.y);
83 | deltaZ = Math.abs(this.lastZ - current.z);
84 |
85 | if (((deltaX > this.threshold) && (deltaY > this.threshold)) || ((deltaX > this.threshold) && (deltaZ > this.threshold)) || ((deltaY > this.threshold) && (deltaZ > this.threshold))) {
86 | //calculate time in milliseconds since last shake registered
87 | currentTime = new Date();
88 | timeDifference = currentTime.getTime() - this.lastTime.getTime();
89 |
90 | if (timeDifference > 1000) {
91 | window.dispatchEvent(this.event);
92 | this.lastTime = new Date();
93 | }
94 | }
95 |
96 | this.lastX = current.x;
97 | this.lastY = current.y;
98 | this.lastZ = current.z;
99 |
100 | };
101 |
102 | //event handler
103 | Shake.prototype.handleEvent = function (e) {
104 |
105 | if (typeof (this[e.type]) === 'function') {
106 | return this[e.type](e);
107 | }
108 | };
109 |
110 | //create a new instance of shake.js.
111 | var myShakeEvent = new Shake();
112 | myShakeEvent && myShakeEvent.start();
113 |
114 | }(window, document));
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Salesforce Org Chart: Local Set-up
2 | ###### Salesforce Org Chart is web application that is being designed to run as a SF1 canvas app. This read me is a tutorial for setting up and running Org Chart locally. Please note, that this guide assumes that you already have Org Chart running on heroku.
3 |
4 | + [Prepare The App](README.md#Prepare)
5 | + [Building](README.md#Building)
6 | + [Local Back-up](README.md#Local)
7 | + [Testing](README.md#testing)
8 | + [Development Extras](README.md#development-extras)
9 | + [Deploying](README.md#deploying)
10 |
11 |
12 |
13 | ---------------------
14 | #### Prepare the app
15 | Get the project source and install dependencies:
16 |
17 | ```shell
18 | $ git clone [dest dir]
19 | $ npm install
20 | ```
21 |
22 | Run the node server to see the site locally:
23 |
24 | ```shell
25 | $ APP_SECRET= node server.js
26 | ```
27 |
28 | The application uses the following Environment variables:
29 |
30 |
31 | * `APP_SECRET` - **Required**, The secret key from your connected app. Used to decode the signed request.
32 | You will need a connected app in the desired salesforce org to get a client secret. [See this SF1 guide on setting up a connected app](https://developer.salesforce.com/docs/atlas.en-us.salesforce1.meta/salesforce1/canvas_custom_action_create_canvas_app_task.htm)
33 | * `NODE_ENV` - Used to determine the environment running. Defaults to "development". Used to adjust the logging for debugging.
34 |
35 | This will create a locally running node.js server, in order to run the UI application you must go through SF1. This requires:
36 |
37 | * A SF1 enabled org
38 | * A canvas connected app (see the `APP_SECRET` above) on that org.
39 | * The canvas app should be added to the Mobile Navigation of your org.
40 |
41 | Once those are in place you can login to the org and use SF1 through the browser by visiting `/one/one.app`. Then use the left-hand nav
42 | to visit your canvas app.
43 |
44 |
45 |
46 | ---------------
47 | #### Building
48 |
49 | There are several components of the application that require a build - namely the JavaScript and CSS. To build these you will need [gruntjs](http://gruntjs.com):
50 |
51 | * Install grunt (globally is recommended by them):
52 |
53 | ```shell
54 | $ npm install grunt
55 | ```
56 |
57 | * The default task will build both the CSS and JS:
58 |
59 | ```shell
60 | $ grunt
61 | ```
62 |
63 | For production build, set the `NODE_ENV`:
64 |
65 | ```shell
66 | $ NODE_ENV=production grunt
67 | ```
68 |
69 | This will produce minified and GZipped JavaScript bundle with no source maps for debugging.
70 |
71 |
72 |
73 | --------------
74 | #### Local Back-up
75 |
76 | + Install [PostGresSQL](http://www.postgresql.org/download/)
77 | + Capture the database and store it locally.
78 | ```shell
79 | $ heroku pg:backups capture
80 | $ curl -o latest.dump `heroku pg:backups public-url`
81 | $ pg_restore --verbose --clean --no-acl --no-owner -h localhost -U orgChartUser -d orgchart latest.dump
82 | ```
83 |
84 |
85 |
86 | --------
87 |
88 | #### Testing
89 | Install mocha with `-g` or better yet add [`node_modules` to your `PATH`](http://stackoverflow.com/a/15157360/42998)
90 |
91 | ```shell
92 | $ npm test
93 | ```
94 |
95 | #### Development Extras:
96 | * Install the Chrome extension Livereload in order to automatically refresh your dev browser as you make changes in your editor
97 | * Get Nodemon to automatically restart your node server as you make js file changes. Use the -e flag to include the extensions we want to monitor.
98 |
99 | ```shell
100 | $ npm install -g nodemon
101 | $ nodemon server.js -e js,ejs
102 | ```
103 |
104 | [Webstorm](http://www.jetbrains.com/webstorm/) can also be used to run the server and unit tests and is a good IDE for JS development
--------------------------------------------------------------------------------
/app/services.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var module = angular.module('orgchart.services', []);
4 |
5 | var headers = function () {
6 | return {
7 | headers: {
8 | 'Access-Token': canvasContext.client.oauthToken,
9 | 'Instance-Url': canvasContext.client.instanceUrl
10 | }
11 | }
12 | };
13 |
14 | //local storage caching
15 | module.service('orgChartCacheService', function ($http, $angularCacheFactory, $q) {
16 |
17 | //default cache definition
18 | var employeeCache = $angularCacheFactory('employeeCache', {
19 | maxAge: 43200000, //12 hours
20 | deleteOnExpire: 'aggressive',
21 | storageMode: 'localStorage',
22 | recycleFreq: 1000,
23 | capacity: 20
24 | });
25 |
26 | this.getCacheValue = function (value) {
27 | return employeeCache.get(value);
28 | };
29 |
30 | this.callService = function (url) {
31 | var promise = $http.get(url, headers());
32 |
33 | promise.success(function (data, status, headers, config) {
34 | //set maxAge defined from node
35 | setCacheAge(headers('cache-control'));
36 | });
37 |
38 |
39 | function setCacheAge(value) {
40 | // Split the string by = and , | This returns value,1,value
41 | var splitStr = value.split(/\s*[=|,]\s*/);
42 | var valueIndex = splitStr.indexOf('max-age');
43 |
44 | // If the value is found, exists, is number
45 | if ((valueIndex != -1) && (splitStr.length > (valueIndex + 1)) && (!isNaN(splitStr[valueIndex + 1]))) {
46 | valueIndex = splitStr[valueIndex + 1];
47 | employeeCache.setOptions({maxAge: (parseInt(valueIndex) * 1000)});
48 | }
49 | }
50 |
51 | return promise;
52 | };
53 |
54 | this.me = function (email) {
55 |
56 | var deferred = $q.defer();
57 |
58 | var cacheValue = this.getCacheValue('me');
59 | if (angular.isDefined(cacheValue).email == email) {
60 | deferred.resolve(cacheValue);
61 | } else {
62 | this.callService('/services/orgchart/' + canvasContext.context.user.email).then(function (response) {
63 | employeeCache.put('me', response.data);
64 | employeeCache.put(response.data.email, response.data);
65 |
66 | deferred.resolve(response.data, status);
67 | }, function (response) {
68 | deferred.reject(response);
69 | });
70 | }
71 |
72 | return deferred.promise;
73 | };
74 |
75 | this.getEmployee = function (email) {
76 |
77 | var deferred = $q.defer();
78 |
79 | var cacheValue = this.getCacheValue(email);
80 |
81 | if (angular.isDefined(cacheValue)) {
82 | deferred.resolve(cacheValue);
83 | } else {
84 | this.callService('/services/orgchart/' + email).then(function (response) {
85 | employeeCache.put(response.data.email, response.data);
86 |
87 | deferred.resolve(response.data, status);
88 | }, function (response) {
89 | deferred.reject(response);
90 | });
91 | }
92 |
93 | return deferred.promise;
94 | };
95 |
96 | });
97 |
98 | module.factory('Search', ['$http', function ($http) {
99 | return {
100 | getSearch: function (searchQuery) {
101 | //default 'application/json' content type.
102 | return $http.post('/services/orgchart', searchQuery, headers());
103 | }
104 | };
105 | }]);
106 |
107 | module.service('sharedFunctions', function () {
108 | //hides search results element
109 | this.toggleSearchDisplay = function (value) {
110 | var wrapperEle = document.getElementById("searchResultWrapper");
111 |
112 | wrapperEle.className = value;
113 | };
114 | });
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | var Config = require('./config.js');
2 |
3 | var browserifyTransforms = ['browserify-shim','browserify-ngmin'];
4 |
5 | var buildJSTasks = ['browserify', 'clean', 'filerev', 'filerev_assets'];
6 |
7 | if (!Config.isDevelopment()) {
8 | browserifyTransforms.push('uglifyify');
9 | buildJSTasks.push('compress');
10 | }
11 |
12 | var buildCSSTasks = ['less', 'autoprefixer', 'purifycss'];
13 | if(!Config.isDevelopment()){
14 | buildCSSTasks.push('cssmin');
15 | }
16 |
17 | module.exports = function (grunt) {
18 |
19 | grunt.initConfig({
20 | pkg: grunt.file.readJSON('package.json'),
21 |
22 | isDev: Config.isDevelopment(),
23 |
24 | browserify: {
25 | './dist/js/bundle.js': ['./app/app.js'],
26 | options: {
27 | browserifyOptions: {
28 | debug: '<%= isDev %>'
29 | },
30 | transform: browserifyTransforms
31 | }
32 | },
33 |
34 | uglify: {
35 | js: {
36 | options: {
37 | mangle: false
38 | },
39 | files: {
40 | 'dist/js/bundle.js': ['dist/js/bundle.js']
41 | }
42 | }
43 | },
44 |
45 | filerev: {
46 | options: {
47 | encoding: 'utf8',
48 | algorithm: 'md5',
49 | length: 8
50 | },
51 | assets: {
52 | src: 'dist/{fonts,js,css,images}/*.{jpg,jpeg,gif,png,webp,css,js,woff}',
53 | dest: 'dist/compiled',
54 | filter: function(src){
55 | return src.indexOf('<%= filerev.assets.dest %>') < 0;
56 | }
57 | }
58 | },
59 | clean: ['<%= filerev.assets.dest %>'],
60 |
61 | filerev_assets: {
62 | dist: {
63 | options: {
64 | dest: '<%= filerev.assets.dest %>' + '/assetManifest.json',
65 | prettyPrint: true,
66 | cwd: 'app/'
67 | }
68 | }
69 | },
70 |
71 | less: {
72 | style: {
73 | files: {
74 | "dist/css/style.css": "app/css/style.less"
75 | }
76 | }
77 | },
78 |
79 | autoprefixer: {
80 | options: {
81 | browsers: ["last 2 Chrome versions", "ios >= 7"]
82 | },
83 | dist: {
84 | files: {
85 | 'dist/css/style.css': 'dist/css/style.css'
86 | }
87 | }
88 | },
89 |
90 | cssmin: {
91 | css: {
92 | src: 'dist/css/style.css',
93 | dest: 'dist/css/style.css'
94 | }
95 | },
96 |
97 | purifycss: {
98 | options: {},
99 | target: {
100 | src: ['app/*.js', 'server/views/**/*.ejs'],
101 | css: ['dist/css/style.css'],
102 | dest: 'dist/css/style.css'
103 | }
104 | },
105 |
106 | watch: {
107 | css: {
108 | files: ['app/css/*'],
109 | tasks: ['buildCSS'],
110 | // NOTE: you need to install the livereload extension for chrome.
111 | options: {
112 | livereload: true
113 | }
114 | },
115 | js: {
116 | files: ['app/*.js'],
117 | tasks: ['buildJavaScript']
118 | }
119 | },
120 |
121 | compress: {
122 | main: {
123 | options: {
124 | mode: 'gzip',
125 | level: '9'
126 | },
127 | src: ['dist/js/bundle.js'],
128 | dest: 'dist/js/bundle.js.gz'
129 | }
130 | }
131 | });
132 |
133 | grunt.registerTask('buildJavaScript', buildJSTasks);
134 | grunt.registerTask('buildCSS', buildCSSTasks);
135 | grunt.registerTask('default', ['buildCSS', 'buildJavaScript']);
136 | grunt.registerTask('deploy', ['buildCSS', 'cssmin', 'buildJavaScript', 'compress']);
137 |
138 | grunt.loadNpmTasks('grunt-contrib-less' );
139 | grunt.loadNpmTasks('grunt-contrib-watch' );
140 | grunt.loadNpmTasks('grunt-contrib-cssmin' );
141 | grunt.loadNpmTasks('grunt-autoprefixer' );
142 | grunt.loadNpmTasks('grunt-browserify' );
143 | grunt.loadNpmTasks('grunt-contrib-compress' );
144 | grunt.loadNpmTasks('grunt-filerev' );
145 | grunt.loadNpmTasks('grunt-filerev-assets' );
146 | grunt.loadNpmTasks('grunt-contrib-clean' );
147 | grunt.loadNpmTasks('grunt-purifycss' );
148 |
149 | };
150 |
--------------------------------------------------------------------------------
/test/lib/handlers/authTest.js:
--------------------------------------------------------------------------------
1 | var Hapi = require('hapi');
2 | var expect = require('chai').expect;
3 | var _ = require('lodash');
4 |
5 | var routes = require('../.././routes');
6 | var AuthHandler = require('../.././auth');
7 | var config = require('../../../config');
8 |
9 |
10 | describe('AuthHandler', function () {
11 |
12 | var server;
13 | before(function(){
14 | server = new Hapi.Server(_.merge({debug:false}, config.server));
15 | server.route(routes);
16 | });
17 |
18 | it('should return a 500 error if there is no APP_SECRET configured', function(done){
19 |
20 | delete process.env.APP_SECRET;
21 |
22 | server.inject({
23 | method: 'POST',
24 | url: '/auth/signedrequest',
25 | headers: {
26 | 'Content-Type': 'application/x-www-form-urlencoded',
27 | 'Content-Length': 5
28 | },
29 | payload: 'foo=bar'
30 | }, function (response) {
31 | expect(response.statusCode).to.equal(500);
32 | done();
33 | });
34 | });
35 |
36 | it('should decode a signed request', function (done) {
37 |
38 | var test_signed_request = "V/lsK06wzoefsXdKVc231sIkTxi5zYV2J2FW1hlu0VQ=.eyJ1c2VySWQiOiIwMDVSMDAwMDAwMERnSkEiLCJjbGllbnQiOnsib2F1dGhUb2tlbiI6IjAwRFIwMDAwMDAwOGJQQyFBUThBUVBVSFBrTEpTUEdDVkpYZjBOak5IQzVCRmZUNHJJQ0tmLlpxMWFrRGlHYXZWcTAzel90dnpiMWY2TlVwT1AzVjFrZG1xY2t1bzY4a0w0bDZwUXczNDZkX1Y4ZFEiLCJpbnN0YW5jZUlkIjoiXzpzaGlwbWVudDoiLCJ0YXJnZXRPcmlnaW4iOiJodHRwczovL21vYmlsZTEudC5zYWxlc2ZvcmNlLmNvbSIsImluc3RhbmNlVXJsIjoiaHR0cHM6Ly9tb2JpbGUxLnQuc2FsZXNmb3JjZS5jb20ifSwiaXNzdWVkQXQiOjEzODQ4MTkwNTksImNvbnRleHQiOnsiYXBwbGljYXRpb24iOnsiZGV2ZWxvcGVyTmFtZSI6InNoaXBtZW50IiwicmVmZXJlbmNlSWQiOiIwOUhSMDAwMDAwMDAwTjkiLCJhcHBsaWNhdGlvbklkIjoiMDZQUjAwMDAwMDAwMGNJIiwiY2FudmFzVXJsIjoiaHR0cHM6Ly9sb2NhbGhvc3Qvc2lnbmVkcmVxdWVzdCIsIm5hbWUiOiJzaGlwbWVudCIsInZlcnNpb24iOiIxLjAiLCJuYW1lc3BhY2UiOm51bGwsImF1dGhUeXBlIjoiU0lHTkVEX1JFUVVFU1QifSwib3JnYW5pemF0aW9uIjp7Im9yZ2FuaXphdGlvbklkIjoiMDBEUjAwMDAwMDA4YlBDTUFZIiwiY3VycmVuY3lJc29Db2RlIjoiVVNEIiwibXVsdGljdXJyZW5jeUVuYWJsZWQiOmZhbHNlLCJuYW1lIjoiU2FtJ3MgUzEgVGVzdCBPcmciLCJuYW1lc3BhY2VQcmVmaXgiOm51bGx9LCJlbnZpcm9ubWVudCI6eyJkaW1lbnNpb25zIjp7ImhlaWdodCI6IjkwMHB4IiwibWF4SGVpZ2h0IjoiMjAwMHB4IiwibWF4V2lkdGgiOiIxMDAwcHgiLCJ3aWR0aCI6IjgwMHB4In0sImRpc3BsYXlMb2NhdGlvbiI6IkNoYXR0ZXIiLCJ1aVRoZW1lIjoiVGhlbWUzIiwidmVyc2lvbiI6eyJhcGkiOiIyOS4wIiwic2Vhc29uIjoiV0lOVEVSIn0sImxvY2F0aW9uVXJsIjoiaHR0cHM6Ly9tb2JpbGUxLnQuc2FsZXNmb3JjZS5jb20vX3VpL2NvcmUvY2hhdHRlci91aS9DaGF0dGVyUGFnZSIsInBhcmFtZXRlcnMiOnt9fSwibGlua3MiOnsibG9naW5VcmwiOiJodHRwczovL21vYmlsZTEudC5zYWxlc2ZvcmNlLmNvbS8iLCJjaGF0dGVyRmVlZEl0ZW1zVXJsIjoiL3NlcnZpY2VzL2RhdGEvdjI5LjAvY2hhdHRlci9mZWVkLWl0ZW1zIiwiY2hhdHRlckZlZWRzVXJsIjoiL3NlcnZpY2VzL2RhdGEvdjI5LjAvY2hhdHRlci9mZWVkcyIsImNoYXR0ZXJHcm91cHNVcmwiOiIvc2VydmljZXMvZGF0YS92MjkuMC9jaGF0dGVyL2dyb3VwcyIsImNoYXR0ZXJVc2Vyc1VybCI6Ii9zZXJ2aWNlcy9kYXRhL3YyOS4wL2NoYXR0ZXIvdXNlcnMiLCJlbnRlcnByaXNlVXJsIjoiL3NlcnZpY2VzL1NvYXAvYy8yOS4wLzAwRFIwMDAwMDAwOGJQQyIsIm1ldGFkYXRhVXJsIjoiL3NlcnZpY2VzL1NvYXAvbS8yOS4wLzAwRFIwMDAwMDAwOGJQQyIsInBhcnRuZXJVcmwiOiIvc2VydmljZXMvU29hcC91LzI5LjAvMDBEUjAwMDAwMDA4YlBDIiwicXVlcnlVcmwiOiIvc2VydmljZXMvZGF0YS92MjkuMC9xdWVyeS8iLCJyZWNlbnRJdGVtc1VybCI6Ii9zZXJ2aWNlcy9kYXRhL3YyOS4wL3JlY2VudC8iLCJyZXN0VXJsIjoiL3NlcnZpY2VzL2RhdGEvdjI5LjAvIiwic2VhcmNoVXJsIjoiL3NlcnZpY2VzL2RhdGEvdjI5LjAvc2VhcmNoLyIsInNvYmplY3RVcmwiOiIvc2VydmljZXMvZGF0YS92MjkuMC9zb2JqZWN0cy8iLCJ1c2VyVXJsIjoiLzAwNVIwMDAwMDAwRGdKQUlBMCJ9LCJ1c2VyIjp7InVzZXJJZCI6IjAwNVIwMDAwMDAwRGdKQUlBMCIsImZpcnN0TmFtZSI6IlRlc3QiLCJsYXN0TmFtZSI6IlVzZXIiLCJlbWFpbCI6InNyZWFkeUBzYWxlc2ZvcmNlLmNvbSIsInVzZXJUeXBlIjoiU1RBTkRBUkQiLCJpc0RlZmF1bHROZXR3b3JrIjp0cnVlLCJwcm9maWxlSWQiOiIwMGVSMDAwMDAwMExzWU4iLCJwcm9maWxlUGhvdG9VcmwiOiJodHRwczovL2MubW9iaWxlMS5jb250ZW50LnQuZm9yY2UuY29tL3Byb2ZpbGVwaG90by8wMDUvRiIsIm5ldHdvcmtJZCI6bnVsbCwiY3VycmVuY3lJU09Db2RlIjoiVVNEIiwicm9sZUlkIjpudWxsLCJzaXRlVXJsUHJlZml4IjpudWxsLCJhY2Nlc3NpYmlsaXR5TW9kZUVuYWJsZWQiOmZhbHNlLCJzaXRlVXJsIjpudWxsLCJwcm9maWxlVGh1bWJuYWlsVXJsIjoiaHR0cHM6Ly9jLm1vYmlsZTEuY29udGVudC50LmZvcmNlLmNvbS9wcm9maWxlcGhvdG8vMDA1L1QiLCJsYW5ndWFnZSI6ImVuX1VTIiwidGltZVpvbmUiOiJBbWVyaWNhL0xvc19BbmdlbGVzIiwidXNlck5hbWUiOiJzcmVhZHlAc2ZvbmUubW9iaWxlMSIsImxvY2FsZSI6ImVuX1VTIiwiZnVsbE5hbWUiOiJUZXN0IFVzZXIifX0sImFsZ29yaXRobSI6IkhNQUNTSEEyNTYifQ==";
39 | var access_token_inside_signed_request = '00DR00000008bPC!AQ8AQPUHPkLJSPGCVJXf0NjNHC5BFfT4rICKf.Zq1akDiGavVq03z_tvzb1f6NUpOP3V1kdmqckuo68kL4l6pQw346d_V8dQ';
40 | var instance_url_inside_signed_request = 'https://mobile1.t.salesforce.com';
41 | process.env.APP_SECRET = "6375554712652875436";
42 | var payload = 'signed_request=' + test_signed_request;
43 |
44 | server.inject({
45 | method: 'POST',
46 | url: '/auth/signedrequest',
47 | headers: {
48 | 'Content-Type': 'application/x-www-form-urlencoded',
49 | 'Content-Length': payload.length
50 | },
51 | payload: payload
52 | }, function (response) {
53 | // expect(response.statusCode).to.equal(200);
54 | var result = response.result
55 | done();
56 | });
57 |
58 |
59 | });
60 |
61 |
62 | });
--------------------------------------------------------------------------------
/server/views/partials/employeeDetail.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
19 |
29 |
30 |
39 |
48 |
49 |
50 |
51 |
53 |
54 |
55 |
{{employee.name}}
56 |
57 |
58 |
59 |
List reports
60 |
61 |
Back
62 |
63 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/app/vendor/angular-ellipsis.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Angular directive to truncate multi-line text to visible height
3 | *
4 | * @param bind (angular bound value to append) REQUIRED
5 | * @param ellipsisAppend (string) string to append at end of truncated text after ellipsis, can be HTML OPTIONAL
6 | * @param ellipsisSymbol (string) string to use as ellipsis, replaces default '...' OPTIONAL
7 | * @param ellipsisAppendClick (function) function to call if ellipsisAppend is clicked (ellipsisAppend must be clicked) OPTIONAL
8 | *
9 | * @example
10 | * @example
11 | * @example
12 | * @example
13 | *
14 | */
15 |
16 | "use strict";
17 |
18 | var app = angular.module('orgchart.directives');
19 |
20 | app.directive('ellipsis', function ($timeout, $window) {
21 |
22 | return {
23 | restrict: 'A',
24 | scope: {
25 | ngBind: '=',
26 | ellipsisAppend: '@',
27 | ellipsisAppendClick: '&',
28 | ellipsisSymbol: '@'
29 | },
30 | compile: function (elem, attr, linker) {
31 |
32 | return function (scope, element, attributes) {
33 | /* Window Resize Variables */
34 | attributes.lastWindowResizeTime = 0;
35 | attributes.lastWindowResizeWidth = 0;
36 | attributes.lastWindowResizeHeight = 0;
37 | attributes.lastWindowTimeoutEvent = null;
38 | /* State Variables */
39 | attributes.isTruncated = false;
40 |
41 | function buildEllipsis() {
42 | if (typeof(scope.ngBind) !== 'undefined') {
43 | var bindArray = scope.ngBind.split(" "),
44 | i = 0,
45 | ellipsisSymbol = (typeof(attributes.ellipsisSymbol) !== 'undefined') ? attributes.ellipsisSymbol : '…',
46 | appendString = (typeof(scope.ellipsisAppend) !== 'undefined' && scope.ellipsisAppend !== '') ? ellipsisSymbol + '' + scope.ellipsisAppend + '' : ellipsisSymbol;
47 |
48 | attributes.isTruncated = false;
49 | element.html(scope.ngBind);
50 |
51 | // If text has overflow
52 | if (isOverflowed(element)) {
53 | var bindArrayStartingLength = bindArray.length,
54 | initialMaxHeight = element[0].clientHeight;
55 |
56 | element.html(scope.ngBind + appendString);
57 |
58 | // Set complete text and remove one word at a time, until there is no overflow
59 | for (; i < bindArrayStartingLength; i++) {
60 | bindArray.pop();
61 | element.html(bindArray.join(" ") + appendString);
62 |
63 | if (element[0].scrollHeight < initialMaxHeight || isOverflowed(element) === false) {
64 | attributes.isTruncated = true;
65 | break;
66 | }
67 | }
68 |
69 | // If append string was passed and append click function included
70 | if (ellipsisSymbol != appendString && typeof(scope.ellipsisAppendClick) !== 'undefined' && scope.ellipsisAppendClick !== '') {
71 | element.find('span').bind("click", function (e) {
72 | scope.$apply(scope.ellipsisAppendClick);
73 | });
74 | }
75 | }
76 | }
77 | }
78 |
79 | /**
80 | * Test if element has overflow of text beyond height or max-height
81 | *
82 | * @param element (DOM object)
83 | *
84 | * @return bool
85 | *
86 | */
87 | function isOverflowed(thisElement) {
88 | return thisElement[0].scrollHeight > thisElement[0].clientHeight;
89 | }
90 |
91 | /**
92 | * Watchers
93 | */
94 |
95 | /**
96 | * Execute ellipsis truncate on ngBind update
97 | */
98 | scope.$watch('ngBind', function () {
99 | buildEllipsis();
100 | });
101 |
102 | /**
103 | * Execute ellipsis truncate on ngBind update
104 | */
105 | scope.$watch('ellipsisAppend', function () {
106 | buildEllipsis();
107 | });
108 |
109 | /**
110 | * When window width or height changes - re-init truncation
111 | */
112 | angular.element($window).bind('resize', function () {
113 | $timeout.cancel(attributes.lastWindowTimeoutEvent);
114 |
115 | attributes.lastWindowTimeoutEvent = $timeout(function () {
116 | if (attributes.lastWindowResizeWidth != window.innerWidth || attributes.lastWindowResizeHeight != window.innerHeight) {
117 | buildEllipsis();
118 | }
119 |
120 | attributes.lastWindowResizeWidth = window.innerWidth;
121 | attributes.lastWindowResizeHeight = window.innerHeight;
122 | }, 75);
123 | });
124 |
125 |
126 | };
127 | }
128 | };
129 | });
130 |
--------------------------------------------------------------------------------
/app/css/animations.less:
--------------------------------------------------------------------------------
1 | // The animation for a new manager entering the stage
2 | // sourced from Animate.css - http://daneden.me/animate
3 | // Vendor specific prefixes as needed are added through
4 | // grunt on compile.
5 |
6 | .animated {
7 | animation-duration: 1s;
8 | animation-fill-mode: both;
9 | }
10 |
11 | .bounceIn {
12 | animation: bounceIn .350s;
13 | opacity: 1;
14 | }
15 |
16 | @keyframes bounceIn{
17 | 0% {
18 | transform:scale(1)
19 | }
20 | 60% {
21 | transform:scale(1.3)
22 | }
23 | 100% {
24 | transform:scale(1)
25 | }
26 | }
27 |
28 | @keyframes bounceInDown {
29 | 0% {
30 | opacity: 0;
31 | transform: translateY(-2000px);
32 | }
33 |
34 | 60% {
35 | opacity: 1;
36 | transform: translateY(30px);
37 | }
38 |
39 | 80% {
40 | transform: translateY(-10px);
41 | }
42 |
43 | 100% {
44 | transform: translateY(0);
45 | }
46 | }
47 |
48 | .bounceInDown {
49 | animation-name: bounceInDown;
50 | }
51 |
52 |
53 | @keyframes fadeIn {
54 | 0% {
55 | opacity: 0;
56 | }
57 |
58 | 100% {
59 | opacity: 1;
60 | }
61 | }
62 |
63 | .fadeIn {
64 | animation-name: fadeIn;
65 | }
66 |
67 |
68 | @keyframes fadeInDown {
69 | 0% {
70 | opacity: 0;
71 | transform: translateY(-20px);
72 | }
73 |
74 | 100% {
75 | opacity: 1;
76 | transform: translateY(0);
77 | }
78 | }
79 |
80 | .fadeInDown {
81 | animation-name: fadeInDown;
82 | }
83 |
84 |
85 | @keyframes bounceInUp {
86 | 0% {
87 | opacity: 0;
88 | transform: translateY(2000px);
89 | }
90 |
91 | 60% {
92 | opacity: 1;
93 | transform: translateY(-30px);
94 | }
95 |
96 | 80% {
97 | transform: translateY(10px);
98 | }
99 |
100 | 100% {
101 | transform: translateY(0);
102 | }
103 | }
104 |
105 | .bounceInUp {
106 | animation-name: bounceInUp;
107 | }
108 |
109 |
110 | @keyframes bounceOut {
111 | 0% {
112 | transform: scale(1);
113 | }
114 |
115 | 25% {
116 | transform: scale(.95);
117 | }
118 |
119 | 50% {
120 | opacity: 1;
121 | transform: scale(1.1);
122 | }
123 |
124 | 100% {
125 | opacity: 0;
126 | transform: scale(.3);
127 | }
128 | }
129 |
130 | .bounceOut {
131 | animation-name: bounceOut;
132 | }
133 |
134 | @keyframes bounceOutDown {
135 | 0% {
136 | transform: translateY(0);
137 | }
138 |
139 | 20% {
140 | opacity: 1;
141 | transform: translateY(-20px);
142 | }
143 |
144 | 100% {
145 | opacity: 0;
146 | transform: translateY(2000px);
147 | }
148 | }
149 |
150 | .bounceOutDown {
151 | animation-name: bounceOutDown;
152 | }
153 |
154 | @keyframes fadeInUp {
155 | 0% {
156 | opacity: 0;
157 | transform: translateY(20px);
158 | }
159 |
160 | 100% {
161 | opacity: 1;
162 | transform: translateY(0);
163 | }
164 | }
165 |
166 | .fadeInUp {
167 | animation-name: fadeInUp;
168 | }
169 |
170 | @keyframes fadeInUpBig {
171 | 0% {
172 | opacity: 0;
173 | transform: translateY(2000px);
174 | }
175 |
176 | 100% {
177 | opacity: 1;
178 | transform: translateY(0);
179 | }
180 | }
181 |
182 | .fadeInUpBig {
183 | animation-name: fadeInUpBig;
184 | }
185 |
186 | @keyframes fadeOut {
187 | 0% {
188 | opacity: 1;
189 | }
190 |
191 | 100% {
192 | opacity: 0;
193 | }
194 | }
195 |
196 | .fadeOut {
197 | animation-name: fadeOut;
198 | }
199 |
200 | @keyframes fadeOutDown {
201 | 0% {
202 | opacity: 1;
203 | transform: translateY(0);
204 | }
205 |
206 | 100% {
207 | opacity: 0;
208 | transform: translateY(20px);
209 | }
210 | }
211 |
212 | .fadeOutDown {
213 | animation-name: fadeOutDown;
214 | }
215 |
216 | @keyframes fadeOutDownBig {
217 | 0% {
218 | opacity: 1;
219 | transform: translateY(0);
220 | }
221 |
222 | 100% {
223 | opacity: 0;
224 | transform: translateY(2000px);
225 | }
226 | }
227 |
228 | .fadeOutDownBig {
229 | animation-name: fadeOutDownBig;
230 | }
231 |
232 | @keyframes flipInX {
233 | 0% {
234 | transform: perspective(400px) rotateX(90deg);
235 | opacity: 0;
236 | }
237 |
238 | 40% {
239 | transform: perspective(400px) rotateX(-10deg);
240 | }
241 |
242 | 70% {
243 | transform: perspective(400px) rotateX(10deg);
244 | }
245 |
246 | 100% {
247 | transform: perspective(400px) rotateX(0deg);
248 | opacity: 1;
249 | }
250 | }
251 |
252 | .flipInX {
253 | backface-visibility: visible !important;
254 | animation-name: flipInX;
255 | }
256 |
257 |
258 | @keyframes flipInY {
259 | 0% {
260 | transform: perspective(400px) rotateY(90deg);
261 | opacity: 0;
262 | }
263 |
264 | 40% {
265 | transform: perspective(400px) rotateY(-10deg);
266 | }
267 |
268 | 70% {
269 | transform: perspective(400px) rotateY(10deg);
270 | }
271 |
272 | 100% {
273 | transform: perspective(400px) rotateY(0deg);
274 | opacity: 1;
275 | }
276 | }
277 |
278 | .flipInY {
279 | backface-visibility: visible !important;
280 | animation-name: flipInY;
281 | }
282 |
283 | @keyframes rubberBand {
284 | 0% {
285 | transform: scale(1);
286 | opacity: 0;
287 | }
288 |
289 | 30% {
290 | transform: scaleX(1.25) scaleY(0.75);
291 | }
292 |
293 | 40% {
294 | transform: scaleX(0.75) scaleY(1.25);
295 | }
296 |
297 | 60% {
298 | transform: scaleX(1.15) scaleY(0.85);
299 | }
300 |
301 | 100% {
302 | transform: scale(1);
303 | opacity: 1;
304 | }
305 | }
306 |
307 | .rubberBand {
308 | animation-name: rubberBand;
309 | }
310 |
311 |
312 | @keyframes tada {
313 | 0% {
314 | transform: scale(1);
315 | }
316 |
317 | 10%, 20% {
318 | transform: scale(0.9) rotate(-3deg);
319 | }
320 |
321 | 30%, 50%, 70%, 90% {
322 | transform: scale(1.1) rotate(3deg);
323 | }
324 |
325 | 40%, 60%, 80% {
326 | transform: scale(1.1) rotate(-3deg);
327 | }
328 |
329 | 100% {
330 | transform: scale(1) rotate(0);
331 | }
332 | }
333 |
334 | .tada {
335 | animation-name: tada;
336 | }
337 |
338 | @keyframes wobble {
339 | 0% {
340 | transform: translateX(0%);
341 | }
342 |
343 | 15% {
344 | transform: translateX(-25%) rotate(-5deg);
345 | }
346 |
347 | 30% {
348 | transform: translateX(20%) rotate(3deg);
349 | }
350 |
351 | 45% {
352 | transform: translateX(-15%) rotate(-3deg);
353 | }
354 |
355 | 60% {
356 | transform: translateX(10%) rotate(2deg);
357 | }
358 |
359 | 75% {
360 | transform: translateX(-5%) rotate(-1deg);
361 | }
362 |
363 | 100% {
364 | transform: translateX(0%);
365 | }
366 | }
367 |
368 | .wobble {
369 | animation-name: wobble;
370 | }
371 |
--------------------------------------------------------------------------------
/app/directives.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var module = angular.module('orgchart.directives', [
4 | 'orgchart.services',
5 | 'ngAnimate'
6 | ]);
7 |
8 | module.directive('animateOnPhoto', ['$animate', function($animate) {
9 | return function(scope, ele, attr) {
10 | attr.$observe('animateOnPhoto', function(value) {
11 |
12 | var animationClass = attr.animateName;
13 | var defaultImageURL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAADAFBMVEVQjrRVn81RmsdMkb2BrctNlcKTutR1rdGixNxLj7pUn89Ii7RNl8RqocRTodCxzeLB3fJSnMq50uZPmcZPm8l7qclRns7P4O+Xu9ROmcaHsc671ei52fFMlcGLtdFQmchzo8JOlcFOmMZKjriy1OxFha1alblLk75Fhq5qmrtTncpXm8a00OXR4vFimr7E2ep6tt1gmLrN3u1Gh69fp9XM4vNIibKryN5TnsydwduHvuJnnsCUw+REhq9KkbyZxubI3e1Pl8RUn82sy+Hf7/5EhKtHibJJjLZDgaiizOlJj7lSnMh1s9ten8fV6PlTlb5nq9XL3Ounx91EgqhJjbdXodC/2OyQt9KBut7F3O52psVPl8JKjbdQiq7D2uycyOfI4fVIirOavthglbZMjLNYkbViqdVwn76Es9Kp0Oywy9/E4PRPmMZVoM9bpdNapdTb7PzQ5fXB2OpNlMLQ5/lGhKrB1udLkLt6r9JNlsOeyuhDhq9tr9lwq8/X6vvM4/VJirHQ4vLJ3vBUkLVLkr3V5vbN5fhMh6zI2uq+1+mgwNdIhatFg6lYpNJXpNLa6vnb6/rU5fRTodLT5PPQ4fDc7PvZ6fjO3+5UodLd7fzV5vXe7v3X5/bM3ezS4/LW5vXY6fjV5fTU5PPW5/bY6PdSoNDT4/JToNBRoM/X6PfZ6vlSn9Da6/rS4vHQ4O9Qnczb7PvP3+5Rn8/c7fxSn8/O3u1Qns1SoM/R4fBRncxWotFLlMBRmshVodBRnctMlMBMlMFRns1Qm8lVotBHiLFUoM5Pm8pWotBRn85ToNFSns5Sn85WodBQnMpPnMpVodFLlMFDgadUoNBPmshOl8RTn9BQnctSnc1Qm8tSnsxMk79RmcVTns9Rnc1PnMtRnMtMlMJVo9JDgqlJjbhFh69OlsLG2+xFh7BZpNLQ4fFYpdNSnczW5/dOkbnS4/N8uN7O4PBQlcFuqMxCha5UotJTodFWo9JVotJXo9JSoNFZpdNSodFRoNFapdNZpNNYpNOaiSeMAAANB0lEQVR4AezPUarCQAwF0Owfmj3Ob78zQ0m5E6sitj5/hZdwzw6O7Bc2lokE5jJsv5DzYnMk4pt9jXRFOtr/RhpSap8RRVJ6jQTSinMkkFi8I4rU9BVpSK49Ix3p9UdEkZ7eI4YC7IgML2AckdULWHcxL8FkeAlD1EtQCS8hxItg5L+RWQQjP8QII4wwwggjjDASRTBywggjjDAyb9TRsYqDMByA8VfwATL2ATpkkoCLixCko5tOggixQ6cSvM1NzeIgWW4uHQq+VDkOupbCyQ23dDnumuQf4+8NPr6K4R+MVesMwVtSeAcZPpkyFPn8saIQnESlDH9Ro4DOawjJfTSFf4iLW+V2yOO22ct/OUTc3RBMGvkCdJudDMnTWL4o82fnQnBQSwVl4liI30hFiJsJaQ3oOJLqpiNr9ZkIuZJ61JJRJ0J2aNS1D76WD9keRgPQrls25IMMoxENXTSEFaMptX/XCul0YDQZRDoNWiHYuwwmpQuF5OVg2BE+BPLHk3SBEIYGAEQ55K5qcxkgJHc1yiHpBUZN7YYkAspbbjOExwIMutoLYZ4AFNgLOQpIJ2orhJ4FqIzZCWHlGVhqJyQ49cDeuY2Q3WcPTWxshBS9BQl8CD/ZCPFamJBvbsropakoDODbxeYIpt3VQLQGixh07hjtYXFFpXb2FGsRgc4VWMiKXu5DbwMZoeKDFcgQfJDBrDsE+xNke2ke9jIIrLe9Kj4kUh7Q104q5u7ZOdvgnvPg7/nH990f3842CbVarV6vZ+JLUpgk6zZPEBQS/iqFpWvjdbJQYIh7RRL3xgWF/KswnG/2JXWUbtYJzb8uG0JqtdMO5+iKNJ6Ok5VksZiQV9zda/2+4BZ2hP1prtRDpEzYP8UPeWl0FXLUEdVqtVKpwEbvGo9QBJ8x5ouyJO+5hInE4X3dMCpkMQk5ak+HIaQCQah8+sDb7JrB/5lfbC1Nj12QHEOccSuPIURk8SbB7pCPUV4HbsIx0bKjWcoQickjp7CQuyU2oRncTDBPORveMau0zJ4YRfaHGAhBABoD7K2FILbiokMilJQqsclBaBgG/5l0exGEdABii+ylcUzhMK1SfwtpgT1zFECEkICQ5AYbH6YJWaUeTBNnz7xjfwiCEKhqlhMSxDQuqxTBNH72zHdOACEkz8S+EEhCFGV4v8BkD9P0mBYpg2l8BTa5X6ch/JN0HXIjxNmJO/nG+92FFCeB7SG6rirKnMle6sA0g1ZpvtXZCmweJJx2hwCQuK4VOIxgGrdV8mCa6QKTUkjRdd0wDHtDGsl9k00K06StkgvTTJls0t+AgJAfHpNDegdbof25LUoaMTnkbb+IrqpK7PCJyWEQW5gZ6kDCXpNHAgCAEKpUq/aFNGK5Io/ZIG7G30qKWKRB7sxVTUhIb5FLuvk/yWe2lF43S6v8mbaHABLyIlvk89CDz9lLMT5x7oK0QyTZIYlEYze72o5nYXzClm+iA+n54kK7gRpQVSg/hDDlTvkH4rN8KX0iRdtPMzVVQogMugg57gi1XI4Fsp+lo5XLZV3Xr/b1fT/m001I76UJyUvH/hBFiQVy8kMaZyHb2zaGXDm8JCEBj/yQmIiQW8kvsknHFEWx/yLauuwQ708hIX9+L8sl7xZzkUAoL7nEf1tIyO7wsmRygi7y9kBux4G2K+aNJNc5/GXOfkLayNs4gIuTyWsYGiFD0pCAjQ5RA2pIBwxB3lCQ/MFLqRMQxCxMVVzEU8UOubWvgn+a00ugXtqb60EsvGBRISQ65NKRHLJOSDJrGbGbLFL60m7b+z6jdt2yHR3T+bX7vTo+Tz6/J8/k31bAYLAv/qQ93ZMtVsPkRVc4WxFNZK6m3tR7+knX12LRpLjuNzScfkZ8qH5RAEcFWXqqlu6e89+qrIEfnl6UhxbjtfPvHIfUL/wXsokMr6v1HHz/WWZdxv4vaR46mxx9vZ9f+n+1mse/vEMFiR+rNf3SDwZjPcMOY5N/sstp6eofDLQYrrXnvnDZY7Wa/a3IJsL41Zpa39ed/6geDkBQvNcCSMG6rhJj3Y4GtZLHt9FBmPmbKl1rD+qFqJ5NE/UOASSdPoEwd7tVEqjT8WBRpeD679L5RMxmvSG+dTXJ3Vxdjn61evfeSO+uAvmgKXI6LeI4QJjmbrVY+q7M+HV4Ua3a+o+chOMiQARBKJrNy5c8Qu2QKRyXCIJwdavHMtT3m3bF2HCgpl7r1m0uW62KPM8DZFVnCM5x3Ixl66I88Tv6NDzHGlzGrtRFdY6NGAaQKQWyt4cCEnJtpS7J2y671aeqGetzBJyX1qjdRgepVk8gya6UpjgHhwyunoZ/fwKEx/uGHXcnvSkt2XJgGIaLogLZ0xOS4XkSIFA9xHhSV8rb2j3novdW6kq592Zubg4gpCzLyCBMUwp5rLFvAZm9g9rRP4IKUpHlRlEUoTo1MtJpeIY2t3ooKpvNiiSZzmQyxdVV8/IyCgidnLyONAbqG0GY2RpCxrNBGh1EyGTSJDklSZKJogiaHkYI8c4SHMfhOE42NvKVSmVVZwhPkiQcE2cyxVi214jM8fhahMIwrFqtkum0LAjCKeQDIsgMsjVxRJBCKhW5sZGEeWMcRxEEy8Qt/0MSOxuLUbCLsJGNPK9AzGZw6AXZEwSZ59On+26KxQiGGfc+RhB/ZISiONhFmD8vy5VisQgQYKCDMKML+juaG9hvD2E8NQSOuiHLmgL1SrJcnpqampYkzGSiWJZNRj0LejsikYgJwyTY9DRseqkkQOOfIcuXRivk4OATZHr6T0h0VNc96W/o/U4QpjBuea5Xbi6N9aKHrBaLwuvX8o0bNxKtrRKGcdCR7uwMReP+5zf1SQsNh2OiKEySpuHAyuVySRAO4EVEcegJKZZKJZ7npxKJ6bk5jKIotrfXHWVmhnRheF05ZRwcx0nT01WAyLIsHBwUtUP+qykra2sbL1++BEhbInF4BgmH3VGIy/v1juZ2Ohc+gxweJtra2vL5vE0Q1lZWVl68eKHhEWqEQD3BZjuBiCLe0TEHEIJl6WQyeRSd9X/10+oRHQ6zkcirYDC4g+MJkiS3t7dtGxu6Q6AYDEWA6vv7+204jkNHKhIhwuFwJ6yKdeFrGF09uRxLEK9MJtPOzg6Mg4QFsdlsKgqEkCMmvvSk3iwYZnL/GAhsim+yPoc9zjBoIOqSlTWoni+X92EXDw8PgxwHlghYTm7E0YnmqzOW2mmazhEEQQ0MBCVJEkURTqoM95W1NcWhKDRCPmoL3AY3NzcFQYC7iQKBo8OCwYEzCE27C4UCM+G/2pPK3u7upOH/oQplMgEkCxCS53mY/MbGxiY0/ag1ekIgo3avVobFEC+4O5FC1ClQfTuT4ff3yUQike3owAYGFAsLFrfbHYIk511Ndy5X3Dd6QjOhEJ3LhVmWoCgqiGES1FTe8+bzmd3dXTi2VWSQVYBAjwycGQwF39mRYOdPX1FYWFhYe4VTmJ+wO+9ckEmHbwYuhDmGT149YhzHdWSzuCi2QeXM9vYu9AGIBgdaiJJZ15D//t8NliWHZx7+nPzOEGVNdne383nlm4gEjmdP36xECII9XRWQhI6UMEyBOXoUHx/1eTwTEI/HNzobnzk6SwiWA+hwAJFYjILjkE7uu3+BQDdEkPOh/MFuGSJnDAJRWDAr0NU1ILgFB0DnAj1FL8ClfhOaTFUzKFCcpu91U9PB/JGdPAsz8GX3W3KABSe+ONdFJOf8npKnKm/E+SQPazMPljhxsZFD13sbo8Hk6M6dlh+Tcvx3kBvk1xOw4MS67w2qcBCPEVT7BTy4oCJNgyVsoBnWWpPzEBHI0WjH+sBD9eTcvQ4CFB1eVZ3vvUspwxg+Kn4BCmqjmUAw2LAAmrMqhIIPoZZXfQZvkCdDEP1Z0e76YWF7lUJb0C/sMPLMgyWfEsyIoHgVIUVrrV7vq+sgp/M4d1vXVVkcWODsCCHjjhZhff4kIahDNCaPQTmoOClqfWzb1yXLb5CTBCceAEF7AaQBhKqIcH7lTBZc2GoSYjWkiBEbRikfKgdAdnyO7bt9OmaNEIYCOP4FbpSAQz++VOggZMjSPWQSoZDR73G8Dv3XBzlyVCgirQn5wy3mIe/HGYW80kGIHGsYhnEcrbXBORfn2Xdd1/c9m91Y8IWWZXl/aiFOGLjpzfDez9M0GWMsbxu25FgNUg2EEoZFHPvM6mHBXk0/1BMDHYQY4+ScsVYRSfG3kNwSwvbHxDiv68PCL+9DFQx40NCdMSFBMkWDHLeMxDJqYbnIjl7rnvK0rmskBhm3SUHyf5CEeXC+rwtB2klPYSfEjqJBTrgqFILRj4z2FEwYVVwKknsQZdlU9nigXNAg1UHuZ/f2q+5nlyBnd1jQIBerQaQOh1wP0iBShUOAfFbQBpEaHEAqkEh1ECneoRAq3gGkdIlkECqYkUOkXEcOoXIZCinTInlfCWC6C4hDUwYAAAAASUVORK5CYII=';
14 | ele.css("background-image","url('" + defaultImageURL + "')");
15 |
16 | //no animateClass passed in. default to nothing.
17 | if (!angular.isDefined(animationClass)) {
18 | animationClass = '';
19 | }
20 |
21 | $animate.removeClass(ele,animationClass);
22 |
23 | var image = new Image();
24 | image.src = attr.animateOnPhoto;
25 |
26 | $animate.addClass(ele,animationClass);
27 |
28 | image.onload = function() {
29 | ele.css("background-image","url(" + attr.animateOnPhoto + ")");
30 | };
31 |
32 | image.isError = function() {
33 | ele.css("background-image","url('" + defaultImageURL + "')");
34 | }
35 | })
36 | }
37 | }]);
38 |
39 | module.directive('searchWatch', ['$timeout', 'sharedFunctions', function($timeout, sharedFunctions) {
40 | return function(scope, ele, attr) {
41 |
42 | var startTimer = false;
43 | var counter = 0;
44 | var timerObj;
45 |
46 | scope.$watch(attr.ngModel, function(queryString) {
47 |
48 | sharedFunctions.toggleSearchDisplay("hide");
49 |
50 | if (angular.isDefined(queryString)) {
51 |
52 | if (queryString.length >= 3 && counter < 2) {
53 | startTimer = true;
54 | counter = 0;
55 |
56 | if (counter == 0) {
57 | scope.stopCounter();
58 | scope.runCounter();
59 | }
60 | } else {
61 | scope.stopCounter();
62 | }
63 | }
64 |
65 | scope.runCounter = function() {
66 | if (counter < 2) {
67 | counter++;
68 | timerObj = $timeout(scope.runCounter, 100);
69 | } else {
70 | scope.searchEmployee(queryString);
71 | scope.stopCounter();
72 | }
73 | };
74 |
75 | scope.stopCounter = function() {
76 | startTimer = false;
77 | counter = 0;
78 | $timeout.cancel( timerObj );
79 | }
80 |
81 | })
82 | }
83 | }]);
84 |
85 | module.directive('searchToggle', ['$animate', 'sharedFunctions', function($animate, sharedFunctions) {
86 | return function (scope, ele, attr) {
87 | attr.$observe('searchToggle', function() {
88 |
89 | var managerDetailEle = angular.element(document.getElementById('managerDetail'));
90 | var managerDetailClassOn = 'overflowScrolling-touch';
91 | var managerDetailClassOff = 'overflowScrolling-default';
92 |
93 | var searchToggleEle = angular.element(document.getElementById('searchToggle'));
94 | var searchToggleOn = 'icon-utility-close';
95 | var searchToggleOff = 'icon-utility-search';
96 |
97 | if (attr.searchToggle == 'false') {
98 | managerDetailEle.removeClass(managerDetailClassOff).addClass(managerDetailClassOn);
99 | searchToggleEle.removeClass(searchToggleOn).addClass(searchToggleOff);
100 | } else {
101 | managerDetailEle.removeClass(managerDetailClassOn).addClass(managerDetailClassOff);
102 | searchToggleEle.removeClass(searchToggleOff).addClass(searchToggleOn);
103 | }
104 | });
105 | }
106 | }]);
--------------------------------------------------------------------------------
/app/css/employee.less:
--------------------------------------------------------------------------------
1 | // Path inspired animation sequence
2 | // from the the incredible work of
3 | // http://sparanoid.com/
4 | // Tunghsiao Liu
5 |
6 |
7 |
8 | @d1: (@r * 0);
9 | @d2: (@r * 1);
10 | @d3: (@r * 2);
11 | @d4: (@r * 3);
12 | @d5: (@r * 4);
13 | @d6: (@r * 5);
14 | @d7: (@r * 6);
15 | @d8: (@r * 7);
16 | @d9: (@r * 8);
17 |
18 | @r: 15deg;
19 | @n: 8;
20 |
21 | .employee-image,
22 | .report-image {
23 | height: 100px;
24 | width: 100px;
25 | background-size: 100px;
26 | position: relative;
27 | z-index: 3;
28 | margin: 0px auto;
29 | border: 4px solid #fff;
30 | border-radius: 50%;
31 | display: inline-block;
32 | background-position: center;
33 | background-color: #FFF;
34 | background-repeat: no-repeat;
35 | }
36 |
37 | .report-image {
38 | border-width: 1px;
39 | }
40 |
41 | .employee-image {
42 | border: 2px solid #8D3EEA;
43 | }
44 |
45 | #swipePad {
46 | height: 100px;
47 | width: 100%;
48 | margin: -26px auto 2px auto;
49 | z-index: 10;
50 | }
51 |
52 | .peer-indicator {
53 | margin: 0 auto;
54 | width: 123px;
55 | }
56 |
57 | .report-image {
58 | height: 30px;
59 | width: 30px;
60 | background-size: 30px;
61 | margin: 0;
62 | }
63 |
64 | #employeeDetail {
65 | text-align: center;
66 | position: fixed;
67 | bottom: 0;
68 | left: 0;
69 | width: 100%;
70 | z-index: 5;
71 | height: 200px;
72 | }
73 |
74 | #employeeStrip {
75 | width: 100%;
76 | height: 46px;
77 | margin: 0;
78 | background: #f5f5f5;
79 | border-width: 1px 0 1px 0;
80 | position: absolute;
81 | font-size: 18pt;
82 | top: 0;
83 | }
84 |
85 | #employeeStrip a,
86 | #employeeStrip i {
87 | width: 44px;
88 | height: 44px;
89 | color: #293f54;
90 | display: inline-block;
91 | line-height: 44px;
92 | }
93 |
94 | #employeeLeftIcons,
95 | #employeeRightIcons {
96 | margin: 0 10px;
97 | }
98 |
99 | #employeeLeftIcons {
100 | float: left;
101 | }
102 |
103 | #employeeLeftIcons a {
104 | text-decoration: none;
105 | }
106 |
107 | #employeeRightIcons {
108 | float: right;
109 | }
110 |
111 | #employeeEmail {
112 | display: block;
113 | }
114 |
115 | #peersIcons {
116 | width: 185px;
117 | margin: 0 auto;
118 | i {
119 | color: #ccc;
120 | font-size: 14pt;
121 | }
122 | }
123 |
124 | .name,
125 | .title {
126 | color: #2A94D6;
127 | z-index: 999;
128 | display: block;
129 | overflow: hidden;
130 | white-space: nowrap;
131 | width: 200px;
132 | margin: 0 auto;
133 | text-overflow: ellipsis;
134 | }
135 |
136 | .title {
137 | color: #999;
138 | font-size: .8em;
139 | }
140 |
141 | .main-title {
142 | overflow: hidden;
143 | max-height: 40px;
144 | width: 180px;
145 | margin: 0 auto;
146 | height: 40px;
147 | font-size: .8em;
148 | }
149 |
150 | .feedback {
151 | position: absolute;
152 | bottom: 10px;
153 | right: 10px;
154 | width: initial;
155 | text-align: right;
156 | }
157 |
158 | // TODO: needs refactor to turn back into Less functions
159 | // Gah...
160 |
161 | .reports {
162 | background: #fff;
163 | list-style-type: none;
164 | position: relative;
165 | margin: 0;
166 | padding: 0;
167 | top: -270px;
168 | }
169 | .reports li {
170 | margin: 0 0 0 -15px;
171 | padding: 0;
172 | border: 0;
173 | position: absolute;
174 | height: 170px;
175 | width: 30px;
176 | transform-origin: 15px bottom;
177 | }
178 |
179 | .reports li span {
180 | transition: transform .4s ease;
181 | display: block;
182 | margin: 0;
183 | }
184 |
185 | .reports>li a {
186 | position: absolute;
187 | display: block;
188 | width: 30px;
189 | height: 30px;
190 | overflow: visible;
191 | text-align: center;
192 | border-radius: 50%;
193 | }
194 |
195 | .reports>li:nth-of-type(1) { transform: rotate(-108deg); }
196 | .reports>li:nth-of-type(2) { transform: rotate(-126deg); }
197 | .reports>li:nth-of-type(3) { transform: rotate(-144deg); }
198 | .reports>li:nth-of-type(4) { transform: rotate(-162deg); }
199 | .reports>li:nth-of-type(5) { transform: rotate(-180deg); }
200 | .reports>li:nth-of-type(6) { transform: rotate(-198deg); }
201 | .reports>li:nth-of-type(7) { transform: rotate(-216deg); }
202 | .reports>li:nth-of-type(8) { transform: rotate(-234deg); }
203 | .reports>li:nth-of-type(9) { transform: rotate(-252deg); }
204 |
205 | // custom styling for the First LI and the last LI
206 | .reports.expand li:nth-of-type(9) a {
207 | line-height: 22px;
208 | text-align: center;
209 | background: rgba(42, 148, 214, 0.5);
210 | border: 4px solid rgba(255, 255, 255, 0.4);
211 | color: white;
212 | font-size: .7em;
213 | ::before {
214 | content: '+';
215 | margin-right: 2px;
216 | }
217 | }
218 |
219 | // Set the rotation to square off the span AND
220 | // add a rotation animation for entry
221 | .reports.expand li:nth-of-type(1) a span {
222 | transform: rotate(108deg);
223 | // disable spin for reports count
224 | // animation: spin1-expand .6s ease-out 1 backwards;
225 | }
226 | .reports.expand li:nth-of-type(2) a span {
227 | transform: rotate(126deg);
228 | animation: spin2-expand .6s ease-out 1 backwards;
229 | }
230 | .reports.expand li:nth-of-type(3) a span {
231 | transform: rotate(144deg);
232 | animation: spin3-expand .6s ease-out 1 backwards;
233 | }
234 | .reports.expand li:nth-of-type(4) a span {
235 | transform: rotate(162deg);
236 | animation: spin4-expand .6s ease-out 1 backwards;
237 | }
238 | .reports.expand li:nth-of-type(5) a span {
239 | transform: rotate(180deg);
240 | animation: spin5-expand .6s ease-out 1 backwards;
241 | }
242 | .reports.expand li:nth-of-type(6) a span {
243 | transform: rotate(198deg);
244 | animation: spin6-expand .6s ease-out 1 backwards;
245 | }
246 | .reports.expand li:nth-of-type(7) a span {
247 | transform: rotate(216deg);
248 | animation: spin7-expand .6s ease-out 1 backwards;
249 | }
250 | .reports.expand li:nth-of-type(8) a span {
251 | transform: rotate(234deg);
252 | animation: spin8-expand .6s ease-out 1 backwards;
253 | }
254 | .reports.expand li:nth-of-type(9) a span {
255 | transform: rotate(252deg);
256 | animation: spin9-expand .6s ease-out 1 backwards;
257 | }
258 |
259 | @keyframes spin1-expand {
260 | 0% { transform: rotate(108deg); }
261 | 60% { transform: rotate(-252deg); }
262 | 100% { transform: rotate(-252deg); }
263 | }
264 | @keyframes spin2-expand {
265 | 0% { transform: rotate(126deg); }
266 | 60% { transform: rotate(-234deg); }
267 | 100% { transform: rotate(-234deg); }
268 | }
269 | @keyframes spin3-expand {
270 | 0% { transform: rotate(144deg); }
271 | 60% { transform: rotate(-216deg); }
272 | 100% { transform: rotate(-216deg); }
273 | }
274 | @keyframes spin4-expand {
275 | 0% { transform: rotate(162deg); }
276 | 60% { transform: rotate(-198deg); }
277 | 100% { transform: rotate(-198deg); }
278 | }
279 | @keyframes spin5-expand {
280 | 0% { transform: rotate(180deg); }
281 | 60% { transform: rotate(-180deg); }
282 | 100% { transform: rotate(-180deg); }
283 | }
284 | @keyframes spin6-expand {
285 | 0% { transform: rotate(198deg); }
286 | 60% { transform: rotate(-162deg); }
287 | 100% { transform: rotate(-162deg); }
288 | }
289 | @keyframes spin7-expand {
290 | 0% { transform: rotate(216eg); }
291 | 60% { transform: rotate(-144deg); }
292 | 100% { transform: rotate(-144deg); }
293 | }
294 | @keyframes spin8-expand {
295 | 0% { transform: rotate(234deg); }
296 | 60% { transform: rotate(-126deg); }
297 | 100% { transform: rotate(-126deg); }
298 | }
299 | @keyframes spin9-expand {
300 | 0% { transform: rotate(252deg); }
301 | 60% { transform: rotate(-108deg); }
302 | 100% { transform: rotate(-108deg); }
303 | }
304 | .reports li a {
305 | top: 165px;
306 | animation: contract 0.35s ease-out 1 backwards;
307 | }
308 | .reports.expand li a {
309 | /* top of 10 to overshoot expansion*/
310 | top: 10px;
311 | animation: expand .6s ease 1 backwards;
312 | }
313 |
314 | @keyframes expand {
315 | 0% {
316 | top: 165px;
317 | }
318 | 50% {
319 | top: -10px;
320 | }
321 | 70% {
322 | top: 15px;
323 | }
324 | 100% {
325 | top: 10px;
326 | }
327 | }
328 |
329 | @keyframes contract {
330 | 0% {
331 | top: 10px;
332 | }
333 | 40% {
334 | top: -25px;
335 | }
336 | 100% {
337 | top: 165px;
338 | }
339 | }
340 |
341 | .reports li:nth-of-type(1) a { animation-delay: 0.32s; }
342 | .reports li:nth-of-type(2) a { animation-delay: 0.28s; }
343 | .reports li:nth-of-type(3) a { animation-delay: 0.24s; }
344 | .reports li:nth-of-type(4) a { animation-delay: 0.20s; }
345 | .reports li:nth-of-type(5) a { animation-delay: 0.16s; }
346 | .reports li:nth-of-type(6) a { animation-delay: 0.12s; }
347 | .reports li:nth-of-type(7) a { animation-delay: 0.08s; }
348 | .reports li:nth-of-type(8) a { animation-delay: 0.04s; }
349 | .reports li:nth-of-type(9) a { animation-delay: 0s; }
350 | .reports.expand li:nth-of-type(1) a { animation-delay: 0s; }
351 | .reports.expand li:nth-of-type(2) a { animation-delay: 0.04s; }
352 | .reports.expand li:nth-of-type(3) a { animation-delay: 0.08s; }
353 | .reports.expand li:nth-of-type(4) a { animation-delay: 0.12s; }
354 | .reports.expand li:nth-of-type(5) a { animation-delay: 0.16s; }
355 | .reports.expand li:nth-of-type(6) a { animation-delay: 0.20s; }
356 | .reports.expand li:nth-of-type(7) a { animation-delay: 0.24s; }
357 | .reports.expand li:nth-of-type(8) a { animation-delay: 0.28s; }
358 | .reports.expand li:nth-of-type(9) a { animation-delay: 0.32s; }
359 |
360 | // List view of employees
361 | #reportsListView {
362 | width: 100%;
363 | height: 100%;
364 | z-index: 5;
365 | top: 150px;
366 | left: 0;
367 | position: fixed;
368 | overflow-y: scroll; //must be scroll for the momentum scrolling to work
369 | -webkit-overflow-scrolling: touch; //key for momentum scrolling
370 |
371 | // Hack to address some wierd issue with the last report being cut off on
372 | // list view
373 | ul {
374 | margin-bottom: 170px;
375 | }
376 | li {
377 | text-align: left;
378 | cursor: pointer;
379 | clear: both;
380 | border-top: 1px solid #dadee2;
381 | height: 45px;
382 | padding: 0 10px;
383 | :first-child {
384 | border-top: none;
385 | }
386 | i {
387 | float: left;
388 | margin: 12px 12px 8px 2px;
389 | }
390 | div {
391 | float: left;
392 | margin: 10px 10px 10px 5px;
393 | }
394 | }
395 | .name,
396 | .title {
397 | width: 240px;
398 | }
399 | }
400 |
401 | .tog {
402 | position: absolute;
403 | bottom: 10px;
404 | left: 10px;
405 | text-align: left;
406 | }
407 |
408 | .toggle {
409 | position: absolute;
410 | left: 10px;
411 | top: 70px;
412 | text-align: left;
413 | }
414 |
415 | .listViewBackground {
416 | }
417 |
418 | .employeeFocusListView {
419 | top: 40px !important;
420 | position: fixed !important;
421 | width: 100% !important;
422 | }
--------------------------------------------------------------------------------
/app/controllers/orgchartController.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var module = angular.module('orgchart', [
4 | 'ngSanitize',
5 | 'ngAnimate',
6 | 'ngTouch',
7 | 'orgchart.controllers',
8 | 'orgchart.services',
9 | 'orgchart.directives',
10 | 'jmdobry.angular-cache'
11 | ]);
12 |
13 | module.controller('orgchartController', ['$scope', 'sharedFunctions', 'orgChartCacheService',
14 | function ($scope, sharedFunctions, orgChartCacheService) {
15 |
16 | //defaults
17 | $scope.includePath = '../views/includes/';
18 | $scope.initialLoadFlag = true; // Included as part of fix for 7.0.6 manager scroll bug
19 | $scope.listToggleFlag = true;
20 | $scope.ellipsisFlag = false;
21 | $scope.searchFlag = false;
22 | $scope.expiredTokenFlag = false;
23 | $scope.employee = {};
24 |
25 | var me;
26 |
27 | $scope.searchClick = function (employee) {
28 | if (typeof employee.id == "undefined") {
29 | // No-op. The search term is invalid.
30 | return;
31 | }
32 |
33 | if (employee.id != $scope.employee.id || employee.id != me.id) {
34 | // Search for new employee. Do the things.
35 | $scope.getEmployee(employee);
36 | }
37 |
38 | $scope.toggleSearch();
39 | };
40 |
41 | $scope.repaintView = function (employee) {
42 | if ($scope.initialLoadFlag) { //
43 | $scope.toggleListView(); // Included as part of fix for 7.0.6 manager scroll bug
44 | $scope.initialLoadFlag = false; //
45 | }
46 | };
47 |
48 | var onServiceError = function (response) {
49 | if (response.status == 401) {
50 | $scope.expiredTokenFlag = true;
51 | } else if ( response.status == 404 && typeof me == "undefined" ) {
52 | orgChartCacheService.getEmployee('marcb@salesforce.com') //fallback. if service cannot find current user. route to marc b.
53 | .then(function (data) {
54 | me = data;
55 | $scope.employee = data;
56 | $scope.employeeTitle = data.title;
57 | $scope.repaintView(data);
58 | })
59 | }
60 | };
61 |
62 | $scope.getEmployee = function (employee) {
63 | // Update employee object with whatever info we already have for the new employee
64 | $scope.employee = employee;
65 | $scope.employeeTitle = employee.title;
66 |
67 | var id = employee.email;
68 |
69 | // Then request the full employee object
70 |
71 | orgChartCacheService.getEmployee(id)
72 | .then(function (data) {
73 |
74 | $scope.employee = data;
75 | $scope.employeeTitle = data.title;
76 |
77 | }, onServiceError);
78 | };
79 |
80 | $scope.hasReports = function () {
81 | var reportsCount = $scope.employee.directReports;
82 |
83 | if (typeof reportsCount == "undefined") {
84 | return false;
85 | }
86 | return reportsCount.length > 0;
87 | };
88 |
89 | $scope.showReportsString = function () {
90 | var reportsCount = $scope.employee.directReports;
91 |
92 | if (typeof reportsCount == "undefined") {
93 | return false;
94 | }
95 | return reportsCount.length - 8 > 0;
96 | };
97 |
98 | $scope.hasPeer = function (direction) {
99 | var peers = $scope.employee.peers;
100 |
101 | if (typeof peers == "undefined") {
102 | return false;
103 | }
104 | return peers.length > 0;
105 | };
106 |
107 | $scope.getRightPeer = function () {
108 | $scope.getPeer('right');
109 | };
110 |
111 | $scope.getLeftPeer = function () {
112 | $scope.getPeer('left');
113 | };
114 |
115 | $scope.getPeer = function (direction) {
116 | var currentEmployee = $scope.employee.name;
117 | var peers = $scope.employee.peers;
118 |
119 | peers.sort(function (a, b) {
120 | return a.name.localeCompare(b.name);
121 | });
122 |
123 | if (direction == 'left') {
124 | peers.reverse();
125 | }
126 |
127 | var peer;
128 | var leftPeerFound = true;
129 | var rightPeerFound = true;
130 |
131 | for (var i = 0; i < peers.length; i++) {
132 | var n = peers[i].name.localeCompare(currentEmployee);
133 | if (direction == 'left') {
134 | if (n == -1) {
135 | leftPeerFound = true;
136 | peer = peers[i];
137 | break;
138 | } else {
139 | leftPeerFound = false;
140 | }
141 | } else {
142 | if (n == 1) {
143 | rightPeerFound = true;
144 | peer = peers[i];
145 | break;
146 | } else {
147 | rightPeerFound = false;
148 | }
149 | }
150 | }
151 |
152 | if ((!leftPeerFound) && (direction == 'left')) {
153 | peer = peers[0];
154 | }
155 |
156 | if ((!rightPeerFound) && (direction == 'right')) {
157 | peer = peers[0];
158 | }
159 |
160 | if (typeof peer !== "undefined") {
161 | $scope.getEmployee(peer);
162 | }
163 | };
164 |
165 | $scope.toggleSearch = function () {
166 | $scope.searchFlag = !$scope.searchFlag;
167 | };
168 |
169 | $scope.togglePhoneSelector = function (objLength) {
170 |
171 | var phoneObject = $scope.employee.phones;
172 |
173 | if (phoneObject.length > 1) {
174 | $scope.phoneSelectorFlag = !$scope.phoneSelectorFlag;
175 | } else {
176 | for (var i = 0; i < phoneObject.length; i++) {
177 | $scope.callEmployee(phoneObject[i].value);
178 | }
179 | }
180 | };
181 |
182 | $scope.toggleExpiredTokenOverlay = function () {
183 | $scope.expiredTokenFlag = !$scope.expiredTokenFlag;
184 | };
185 |
186 | $scope.toggleListView = function () {
187 | $scope.listToggleFlag = !$scope.listToggleFlag;
188 | };
189 |
190 | $scope.callEmployee = function (number) {
191 | window.top.location = "tel:" + number;
192 | };
193 |
194 | $scope.employeeFocusDetail = function (viewflag) {
195 | if (viewflag == true) {
196 | $scope.toggleListView();
197 | } else {
198 | if ($scope.employee && $scope.employee.id) {
199 | Sfdc.canvas.client.publish(canvasContext.client, {
200 | name: 's1.navigateToSObject',
201 | payload: {
202 | recordId: $scope.employee.id,
203 | view: 'detail'
204 | }
205 | });
206 | }
207 | }
208 | };
209 |
210 | $scope.sendTokenExpired = function () {
211 | Sfdc.canvas.client.repost({refresh : true});
212 | };
213 |
214 | $scope.hasOverflow = function (directReportsSize) {
215 | return directReportsSize > 8 ? "hasOverflow" : "noOverflow";
216 | };
217 |
218 | $scope.reduceSearchResults = function (searchResults) {
219 | if (angular.isDefined(searchResults)) {
220 | var result = [];
221 | var resultUl = document.getElementById('searchResultWrapper').getElementsByTagName('UL');
222 | var resultLi = 32;
223 | var eleHeight = resultUl[0].offsetHeight;
224 | var windowHeight = window.innerHeight;
225 | var sf1NavBar = 74; // sf1bar height and padding(50) && ellipsis(24px)
226 | var limit = (windowHeight - sf1NavBar) / resultLi;
227 |
228 | if ((eleHeight >= windowHeight) && (searchResults.length >= limit)) {
229 | var count = 1; //start index @ 1
230 |
231 | searchResults.forEach(function (value, key) {
232 | if (count <= Math.floor(limit)) {
233 | count++;
234 | result[key] = value;
235 | $scope.ellipsisFlag = true;
236 | }
237 | });
238 |
239 | } else {
240 | result = searchResults;
241 | $scope.ellipsisFlag = false;
242 | }
243 |
244 | return result;
245 | }
246 | };
247 |
248 | var init = function () {
249 | var mgrDetail = document.getElementById('managerDetail');
250 | var empDetail = document.getElementById('employeeDetail');
251 | var overlay = document.getElementById('overlay');
252 | var phoneSel = document.getElementById('phoneSelector');
253 | var startingPoint;
254 | var currentUserEmail = canvasContext.context.user.email;
255 |
256 | //async loading the initial model
257 | orgChartCacheService.me(currentUserEmail)
258 | .then(function (data) {
259 | $scope.employee = data;
260 | $scope.employeeTitle = data.title;
261 | me = data;
262 | $scope.repaintView(data); // Included as part of fix for 7.0.6 manager scroll bug
263 |
264 | }, onServiceError);
265 |
266 | // Swallow touchmove events in the employee area.
267 | empDetail.addEventListener('touchmove', function (event) {
268 | event.preventDefault();
269 | });
270 |
271 | overlay.addEventListener('touchmove', function (event) {
272 | event.preventDefault();
273 | });
274 |
275 | phoneSel.addEventListener('touchmove', function (event) {
276 | event.preventDefault();
277 | });
278 |
279 | mgrDetail.addEventListener('touchstart', function (event) {
280 | startingPoint = event.changedTouches[0].clientY;
281 | });
282 |
283 | mgrDetail.addEventListener('touchmove', function (event) {
284 | var nextPoint = event.changedTouches[0].clientY;
285 |
286 | if ((startingPoint > nextPoint) && (mgrDetail.scrollTop <= 0)) {
287 | event.preventDefault();
288 | }
289 |
290 | if ((startingPoint < nextPoint) && ((mgrDetail.clientHeight + mgrDetail.scrollTop) >= mgrDetail.scrollHeight)) {
291 | event.preventDefault();
292 | }
293 | });
294 |
295 |
296 | window.addEventListener('shake', function () {
297 | if ($scope.employee != me) {
298 | $scope.employee = me;
299 | $scope.employeeTitle = me.title;
300 | $scope.$apply();
301 | }
302 | }, false);
303 | };
304 |
305 | init();
306 |
307 | }
308 |
309 | ]);
310 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/app/vendor/angular-cache-2.3.4.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Jason Dobry
3 | * @file angular-cache.min.js
4 | * @version 2.3.4 - Homepage
5 | * @copyright (c) 2013 -2014 Jason Dobry
6 | * @license MIT
7 | *
8 | * @overview angular-cache is a very useful replacement for Angular's $cacheFactory.
9 | */
10 | !function (a, b, c) {
11 | "use strict";
12 | function d() {
13 | this.$get = function () {
14 | function a(a, b, c) {
15 | for (var d = a[c], e = b(d); c > 0;) {
16 | var f = Math.floor((c + 1) / 2) - 1, g = a[f];
17 | if (e >= b(g))break;
18 | a[f] = d, a[c] = g, c = f
19 | }
20 | }
21 |
22 | function c(a, b, c) {
23 | for (var d = a.length, e = a[c], f = b(e); ;) {
24 | var g = 2 * (c + 1), h = g - 1, i = null;
25 | if (d > h) {
26 | var j = a[h], k = b(j);
27 | f > k && (i = h)
28 | }
29 | if (d > g) {
30 | var l = a[g], m = b(l);
31 | m < (null === i ? f : b(a[h])) && (i = g)
32 | }
33 | if (null === i)break;
34 | a[c] = a[i], a[i] = e, c = i
35 | }
36 | }
37 |
38 | function d(a) {
39 | if (a && !b.isFunction(a))throw new Error("BinaryHeap(weightFunc): weightFunc: must be a function!");
40 | a = a || function (a) {
41 | return a
42 | }, this.weightFunc = a, this.heap = []
43 | }
44 |
45 | return d.prototype.push = function (b) {
46 | this.heap.push(b), a(this.heap, this.weightFunc, this.heap.length - 1)
47 | }, d.prototype.peek = function () {
48 | return this.heap[0]
49 | }, d.prototype.pop = function () {
50 | var a = this.heap[0], b = this.heap.pop();
51 | return this.heap.length > 0 && (this.heap[0] = b, c(this.heap, this.weightFunc, 0)), a
52 | }, d.prototype.remove = function (d) {
53 | for (var e = this.heap.length, f = 0; e > f; f++)if (b.equals(this.heap[f], d)) {
54 | var g = this.heap[f], h = this.heap.pop();
55 | return f !== e - 1 && (this.heap[f] = h, a(this.heap, this.weightFunc, f), c(this.heap, this.weightFunc, f)), g
56 | }
57 | return null
58 | }, d.prototype.removeAll = function () {
59 | this.heap = []
60 | }, d.prototype.size = function () {
61 | return this.heap.length
62 | }, d
63 | }
64 | }
65 |
66 | function e() {
67 | function a(a, c) {
68 | c(b.isNumber(a) ? 0 > a ? "must be greater than zero!" : null : "must be a number!")
69 | }
70 |
71 | var d, e = function () {
72 | return{capacity: Number.MAX_VALUE, maxAge: null, deleteOnExpire: "none", onExpire: null, cacheFlushInterval: null, recycleFreq: 1e3, storageMode: "none", storageImpl: null, verifyIntegrity: !0, disabled: !1}
73 | };
74 | this.setCacheDefaults = function (c) {
75 | var f = "$angularCacheFactoryProvider.setCacheDefaults(options): ";
76 | if (c = c || {}, !b.isObject(c))throw new Error(f + "options: must be an object!");
77 | if ("disabled"in c && (c.disabled = c.disabled === !0), "capacity"in c && a(c.capacity, function (a) {
78 | if (a)throw new Error(f + "capacity: " + a)
79 | }), "deleteOnExpire"in c) {
80 | if (!b.isString(c.deleteOnExpire))throw new Error(f + "deleteOnExpire: must be a string!");
81 | if ("none" !== c.deleteOnExpire && "passive" !== c.deleteOnExpire && "aggressive" !== c.deleteOnExpire)throw new Error(f + 'deleteOnExpire: accepted values are "none", "passive" or "aggressive"!')
82 | }
83 | if ("maxAge"in c && a(c.maxAge, function (a) {
84 | if (a)throw new Error(f + "maxAge: " + a)
85 | }), "recycleFreq"in c && a(c.recycleFreq, function (a) {
86 | if (a)throw new Error(f + "recycleFreq: " + a)
87 | }), "cacheFlushInterval"in c && a(c.cacheFlushInterval, function (a) {
88 | if (a)throw new Error(f + "cacheFlushInterval: " + a)
89 | }), "storageMode"in c) {
90 | if (!b.isString(c.storageMode))throw new Error(f + "storageMode: must be a string!");
91 | if ("none" !== c.storageMode && "localStorage" !== c.storageMode && "sessionStorage" !== c.storageMode)throw new Error(f + 'storageMode: accepted values are "none", "localStorage" or "sessionStorage"!');
92 | if ("storageImpl"in c) {
93 | if (!b.isObject(c.storageImpl))throw new Error(f + "storageImpl: must be an object!");
94 | if (!("setItem"in c.storageImpl && "function" == typeof c.storageImpl.setItem))throw new Error(f + 'storageImpl: must implement "setItem(key, value)"!');
95 | if (!("getItem"in c.storageImpl && "function" == typeof c.storageImpl.getItem))throw new Error(f + 'storageImpl: must implement "getItem(key)"!');
96 | if (!("removeItem"in c.storageImpl) || "function" != typeof c.storageImpl.removeItem)throw new Error(f + 'storageImpl: must implement "removeItem(key)"!')
97 | }
98 | }
99 | if ("onExpire"in c && "function" != typeof c.onExpire)throw new Error(f + "onExpire: must be a function!");
100 | d = b.extend({}, e(), c)
101 | }, this.setCacheDefaults({}), this.$get = ["$window", "BinaryHeap", function (e, f) {
102 | function g(a) {
103 | return a && b.isNumber(a) ? a.toString() : a
104 | }
105 |
106 | function h(a) {
107 | var b, c = {};
108 | for (b in a)a.hasOwnProperty(b) && (c[b] = b);
109 | return c
110 | }
111 |
112 | function i(a) {
113 | var b, c = [];
114 | for (b in a)a.hasOwnProperty(b) && c.push(b);
115 | return c
116 | }
117 |
118 | function j(j, k) {
119 | function m(b) {
120 | a(b, function (a) {
121 | if (a)throw new Error("capacity: " + a);
122 | for (B.capacity = b; E.size() > B.capacity;)H.remove(E.peek().key, {verifyIntegrity: !1})
123 | })
124 | }
125 |
126 | function n(a) {
127 | if (!b.isString(a))throw new Error("deleteOnExpire: must be a string!");
128 | if ("none" !== a && "passive" !== a && "aggressive" !== a)throw new Error('deleteOnExpire: accepted values are "none", "passive" or "aggressive"!');
129 | B.deleteOnExpire = a
130 | }
131 |
132 | function o(b) {
133 | var c = i(C);
134 | if (null === b) {
135 | if (B.maxAge)for (var d = 0; d < c.length; d++) {
136 | var e = c[d];
137 | "maxAge"in C[e] || (delete C[e].expires, D.remove(C[e]))
138 | }
139 | B.maxAge = b
140 | } else a(b, function (a) {
141 | if (a)throw new Error("maxAge: " + a);
142 | if (b !== B.maxAge) {
143 | B.maxAge = b;
144 | for (var d = (new Date).getTime(), e = 0; e < c.length; e++) {
145 | var f = c[e];
146 | "maxAge"in C[f] || (D.remove(C[f]), C[f].expires = C[f].created + B.maxAge, D.push(C[f]), C[f].expires < d && H.remove(f, {verifyIntegrity: !1}))
147 | }
148 | }
149 | })
150 | }
151 |
152 | function p() {
153 | for (var a = (new Date).getTime(), b = D.peek(); b && b.expires && b.expires < a;)H.remove(b.key, {verifyIntegrity: !1}), B.onExpire && B.onExpire(b.key, b.value), b = D.peek()
154 | }
155 |
156 | function q(b) {
157 | null === b ? (B.recycleFreqId && (clearInterval(B.recycleFreqId), delete B.recycleFreqId), B.recycleFreq = d.recycleFreq, B.recycleFreqId = setInterval(p, B.recycleFreq)) : a(b, function (a) {
158 | if (a)throw new Error("recycleFreq: " + a);
159 | B.recycleFreq = b, B.recycleFreqId && clearInterval(B.recycleFreqId), B.recycleFreqId = setInterval(p, B.recycleFreq)
160 | })
161 | }
162 |
163 | function r(b) {
164 | null === b ? (B.cacheFlushIntervalId && (clearInterval(B.cacheFlushIntervalId), delete B.cacheFlushIntervalId), B.cacheFlushInterval = b) : a(b, function (a) {
165 | if (a)throw new Error("cacheFlushInterval: " + a);
166 | b !== B.cacheFlushInterval && (B.cacheFlushIntervalId && clearInterval(B.cacheFlushIntervalId), B.cacheFlushInterval = b, B.cacheFlushIntervalId = setInterval(H.removeAll, B.cacheFlushInterval))
167 | })
168 | }
169 |
170 | function s(a, c) {
171 | var d, f;
172 | if (!b.isString(a))throw new Error("storageMode: must be a string!");
173 | if ("none" !== a && "localStorage" !== a && "sessionStorage" !== a)throw new Error('storageMode: accepted values are "none", "localStorage" or "sessionStorage"!');
174 | if (("localStorage" === B.storageMode || "sessionStorage" === B.storageMode) && a !== B.storageMode) {
175 | for (d = i(C), f = 0; f < d.length; f++)I.removeItem(F + ".data." + d[f]);
176 | I.removeItem(F + ".keys")
177 | }
178 | if (B.storageMode = a, c) {
179 | if (!b.isObject(c))throw new Error("storageImpl: must be an object!");
180 | if (!("setItem"in c && "function" == typeof c.setItem))throw new Error('storageImpl: must implement "setItem(key, value)"!');
181 | if (!("getItem"in c && "function" == typeof c.getItem))throw new Error('storageImpl: must implement "getItem(key)"!');
182 | if (!("removeItem"in c) || "function" != typeof c.removeItem)throw new Error('storageImpl: must implement "removeItem(key)"!');
183 | I = c
184 | } else"localStorage" === B.storageMode ? I = e.localStorage : "sessionStorage" === B.storageMode && (I = e.sessionStorage);
185 | if ("none" !== B.storageMode && I)if (G)for (d = i(C), f = 0; f < d.length; f++)v(d[f]); else u()
186 | }
187 |
188 | function t(a, c, e) {
189 | if (a = a || {}, e = e || {}, c = !!c, !b.isObject(a))throw new Error("AngularCache.setOptions(cacheOptions, strict, options): cacheOptions: must be an object!");
190 | if (w(e.verifyIntegrity), c && (a = b.extend({}, d, a)), "disabled"in a && (B.disabled = a.disabled === !0), "verifyIntegrity"in a && (B.verifyIntegrity = a.verifyIntegrity === !0), "capacity"in a && m(a.capacity), "deleteOnExpire"in a && n(a.deleteOnExpire), "maxAge"in a && o(a.maxAge), "recycleFreq"in a && q(a.recycleFreq), "cacheFlushInterval"in a && r(a.cacheFlushInterval), "storageMode"in a && s(a.storageMode, a.storageImpl), "onExpire"in a) {
191 | if (null !== a.onExpire && "function" != typeof a.onExpire)throw new Error("onExpire: must be a function!");
192 | B.onExpire = a.onExpire
193 | }
194 | G = !0
195 | }
196 |
197 | function u() {
198 | var a = b.fromJson(I.getItem(F + ".keys"));
199 | if (I.removeItem(F + ".keys"), a && a.length) {
200 | for (var c = 0; c < a.length; c++) {
201 | var d = b.fromJson(I.getItem(F + ".data." + a[c])) || {}, e = d.maxAge || B.maxAge, f = d.deleteOnExpire || B.deleteOnExpire;
202 | if (e && (new Date).getTime() - d.created > e && "aggressive" === f)I.removeItem(F + ".data." + a[c]); else {
203 | var g = {created: d.created};
204 | d.expires && (g.expires = d.expires), d.accessed && (g.accessed = d.accessed), d.maxAge && (g.maxAge = d.maxAge), d.deleteOnExpire && (g.deleteOnExpire = d.deleteOnExpire), H.put(a[c], d.value, g)
205 | }
206 | }
207 | v(null)
208 | }
209 | }
210 |
211 | function v(a) {
212 | "none" !== B.storageMode && I && (I.setItem(F + ".keys", b.toJson(i(C))), a && I.setItem(F + ".data." + a, b.toJson(C[a])))
213 | }
214 |
215 | function w(a) {
216 | if ((a || a !== !1 && B.verifyIntegrity) && "none" !== B.storageMode && I) {
217 | var c = i(C);
218 | I.setItem(F + ".keys", b.toJson(c));
219 | for (var d = 0; d < c.length; d++)I.setItem(F + ".data." + c[d], b.toJson(C[c[d]]))
220 | }
221 | }
222 |
223 | function x(a, c) {
224 | if ((c || c !== !1 && B.verifyIntegrity) && "none" !== B.storageMode && I) {
225 | var d = I.getItem(F + ".data." + a);
226 | if (!d && a in C)H.remove(a); else if (d) {
227 | var e = b.fromJson(d), f = e ? e.value : null;
228 | f && H.put(a, f)
229 | }
230 | }
231 | }
232 |
233 | function y(a) {
234 | if ("none" !== B.storageMode && I) {
235 | var c = a || i(C);
236 | I.setItem(F + ".keys", b.toJson(c))
237 | }
238 | }
239 |
240 | function z(a) {
241 | "none" !== B.storageMode && I && a in C && I.setItem(F + ".data." + a, b.toJson(C[a]))
242 | }
243 |
244 | function A() {
245 | if ("none" !== B.storageMode && I) {
246 | for (var a = i(C), c = 0; c < a.length; c++)I.removeItem(F + ".data." + a[c]);
247 | I.setItem(F + ".keys", b.toJson([]))
248 | }
249 | }
250 |
251 | var B = b.extend({}, {id: j}), C = {}, D = new f(function (a) {
252 | return a.expires
253 | }), E = new f(function (a) {
254 | return a.accessed
255 | }), F = "angular-cache.caches." + j, G = !1, H = this, I = null;
256 | k = k || {}, this.put = function (c, d, e) {
257 | if (!B.disabled) {
258 | if (e = e || {}, c = g(c), !b.isString(c))throw new Error("AngularCache.put(key, value, options): key: must be a string!");
259 | if (e && !b.isObject(e))throw new Error("AngularCache.put(key, value, options): options: must be an object!");
260 | if (e.maxAge && null !== e.maxAge)a(e.maxAge, function (a) {
261 | if (a)throw new Error("AngularCache.put(key, value, options): maxAge: " + a)
262 | }); else {
263 | if (e.deleteOnExpire && !b.isString(e.deleteOnExpire))throw new Error("AngularCache.put(key, value, options): deleteOnExpire: must be a string!");
264 | if (e.deleteOnExpire && "none" !== e.deleteOnExpire && "passive" !== e.deleteOnExpire && "aggressive" !== e.deleteOnExpire)throw new Error('AngularCache.put(key, value, options): deleteOnExpire: accepted values are "none", "passive" or "aggressive"!');
265 | if (b.isUndefined(d))return
266 | }
267 | var f, h, i = (new Date).getTime();
268 | return w(e.verifyIntegrity), C[c] ? (D.remove(C[c]), E.remove(C[c])) : C[c] = {key: c}, h = C[c], h.value = d, h.created = parseInt(e.created, 10) || h.created || i, h.accessed = parseInt(e.accessed, 10) || i, e.deleteOnExpire && (h.deleteOnExpire = e.deleteOnExpire), e.maxAge && (h.maxAge = e.maxAge), (h.maxAge || B.maxAge) && (h.expires = h.created + (h.maxAge || B.maxAge)), f = h.deleteOnExpire || B.deleteOnExpire, h.expires && "aggressive" === f && D.push(h), y(), z(c), E.push(h), E.size() > B.capacity && this.remove(E.peek().key, {verifyIntegrity: !1}), d
269 | }
270 | }, this.get = function (a, d) {
271 | if (!B.disabled) {
272 | if (b.isArray(a)) {
273 | var e = a, f = [];
274 | return b.forEach(e, function (a) {
275 | var c = H.get(a, d);
276 | b.isDefined(c) && f.push(c)
277 | }), f
278 | }
279 | if (a = g(a), d = d || {}, !b.isString(a))throw new Error("AngularCache.get(key, options): key: must be a string!");
280 | if (d && !b.isObject(d))throw new Error("AngularCache.get(key, options): options: must be an object!");
281 | if (d.onExpire && !b.isFunction(d.onExpire))throw new Error("AngularCache.get(key, options): onExpire: must be a function!");
282 | if (x(a, d.verifyIntegrity), a in C) {
283 | var h = C[a], i = h.value, j = (new Date).getTime(), k = h.deleteOnExpire || B.deleteOnExpire;
284 | return E.remove(h), h.accessed = j, E.push(h), "passive" === k && "expires"in h && h.expires < j && (this.remove(a, {verifyIntegrity: !1}), B.onExpire ? B.onExpire(a, h.value, d.onExpire) : d.onExpire && d.onExpire(a, h.value), i = c), z(a), i
285 | }
286 | }
287 | }, this.remove = function (a, b) {
288 | b = b || {}, w(b.verifyIntegrity), E.remove(C[a]), D.remove(C[a]), "none" !== B.storageMode && I && I.removeItem(F + ".data." + a), delete C[a], y()
289 | }, this.removeAll = function () {
290 | A(), E.removeAll(), D.removeAll(), C = {}
291 | }, this.removeExpired = function (a) {
292 | a = a || {};
293 | for (var b = (new Date).getTime(), c = i(C), d = {}, e = 0; e < c.length; e++)C[c[e]] && C[c[e]].expires && C[c[e]].expires < b && (d[c[e]] = C[c[e]].value);
294 | for (var f in d)H.remove(f);
295 | if (w(a.verifyIntegrity), a.asArray) {
296 | var g = [];
297 | for (f in d)g.push(d[f]);
298 | return g
299 | }
300 | return d
301 | }, this.destroy = function () {
302 | B.cacheFlushIntervalId && clearInterval(B.cacheFlushIntervalId), B.recycleFreqId && clearInterval(B.recycleFreqId), this.removeAll(), "none" !== B.storageMode && I && (I.removeItem(F + ".keys"), I.removeItem(F)), I = null, C = null, E = null, D = null, B = null, F = null, H = null;
303 | for (var a = i(this), b = 0; b < a.length; b++)this.hasOwnProperty(a[b]) && delete this[a[b]];
304 | l[j] = null, delete l[j]
305 | }, this.info = function (a) {
306 | if (a) {
307 | if (C[a]) {
308 | var c = {created: C[a].created, accessed: C[a].accessed, expires: C[a].expires, maxAge: C[a].maxAge || B.maxAge, deleteOnExpire: C[a].deleteOnExpire || B.deleteOnExpire, isExpired: !1};
309 | return c.maxAge && (c.isExpired = (new Date).getTime() - c.created > c.maxAge), c
310 | }
311 | return C[a]
312 | }
313 | return b.extend({}, B, {size: E && E.size() || 0})
314 | }, this.keySet = function () {
315 | return h(C)
316 | }, this.keys = function () {
317 | return i(C)
318 | }, this.setOptions = t, t(k, !0, {verifyIntegrity: !1})
319 | }
320 |
321 | function k(a, c) {
322 | if (a in l)throw new Error("cacheId " + a + " taken!");
323 | if (!b.isString(a))throw new Error("cacheId must be a string!");
324 | return l[a] = new j(a, c), l[a]
325 | }
326 |
327 | var l = {};
328 | return k.info = function () {
329 | for (var a = i(l), c = {size: a.length, caches: {}}, e = 0; e < a.length; e++) {
330 | var f = a[e];
331 | c.caches[f] = l[f].info()
332 | }
333 | return c.cacheDefaults = b.extend({}, d), c
334 | }, k.get = function (a) {
335 | if (!b.isString(a))throw new Error("$angularCacheFactory.get(cacheId): cacheId: must be a string!");
336 | return l[a]
337 | }, k.keySet = function () {
338 | return h(l)
339 | }, k.keys = function () {
340 | return i(l)
341 | }, k.removeAll = function () {
342 | for (var a = i(l), b = 0; b < a.length; b++)l[a[b]].destroy()
343 | }, k.clearAll = function () {
344 | for (var a = i(l), b = 0; b < a.length; b++)l[a[b]].removeAll()
345 | }, k.enableAll = function () {
346 | for (var a = i(l), b = 0; b < a.length; b++)l[a[b]].setOptions({disabled: !1})
347 | }, k.disableAll = function () {
348 | for (var a = i(l), b = 0; b < a.length; b++)l[a[b]].setOptions({disabled: !0})
349 | }, k
350 | }]
351 | }
352 |
353 | b.module("jmdobry.binary-heap", []).provider("BinaryHeap", d), b.module("jmdobry.angular-cache", ["ng", "jmdobry.binary-heap"]).provider("$angularCacheFactory", e)
354 | }(window, window.angular);
355 |
--------------------------------------------------------------------------------
/dist/css/style.css:
--------------------------------------------------------------------------------
1 | .list-horizontal,.list-plain,ul{list-style:none}.list-horizontal,body,p,ul{margin:0;padding:0}#employeeLeftIcons a,.icon-utility-call,a{text-decoration:none}.reports,.reports li,body,p,ul{padding:0}*,:after,:before{box-sizing:border-box}body{font-family:SalesforceSans;background:#fff;color:#999;font-smoothing:antialiased;-webkit-tap-highlight-color:transparent;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-utility-call,a{color:#293f54}.list-horizontal>li,.list-horizontal>li>a{display:inline-block;zoom:1}.shadow{box-shadow:0 2px 0 rgba(0,0,0,.1)}.hide{display:none!important}.show{display:block!important}.fadeIt{opacity:.01}.unfadeIt{opacity:1}.showFlex{display:-webkit-flex!important;display:flex!important}.capitalize{text-transform:capitalize}.overflowScrolling-touch{-webkit-overflow-scrolling:touch}.overflowScrolling-default{-webkit-overflow-scrolling:auto}.main-title,.name,.title{overflow:hidden;margin:0 auto}@font-face{font-family:SalesforceSans;src:url(https://www2.sfdcstatic.com/system/shared/common/assets/fonts/SalesforceSans/SalesforceSans-Regular.woff) format('woff');font-weight:300;font-style:normal}.employee-image,.report-image{height:100px;width:100px;background-size:100px;position:relative;z-index:3;margin:0 auto;border:4px solid #fff;border-radius:50%;display:inline-block;background-position:center;background-color:#FFF;background-repeat:no-repeat}.report-image{border-width:1px}.employee-image{border:2px solid #8D3EEA}#swipePad{height:100px;width:100%;margin:-26px auto 2px;z-index:10}.report-image{height:30px;width:30px;background-size:30px;margin:0}#employeeDetail{text-align:center;position:fixed;bottom:0;left:0;width:100%;z-index:5;height:200px}#employeeStrip{width:100%;height:46px;margin:0;background:#f5f5f5;border-width:1px 0;position:absolute;font-size:18pt;top:0}#employeeStrip a,#employeeStrip i{width:44px;height:44px;color:#293f54;display:inline-block;line-height:44px}#employeeEmail,.name,.reports>li a,.title{display:block}#employeeLeftIcons,#employeeRightIcons{margin:0 10px}#employeeLeftIcons{float:left}#employeeRightIcons{float:right}#peersIcons{width:185px;margin:0 auto}#peersIcons i{color:#ccc;font-size:14pt}.name,.title{color:#2A94D6;z-index:999;white-space:nowrap;width:200px;text-overflow:ellipsis}.title{color:#999;font-size:.8em}.main-title{max-height:40px;width:180px;height:40px;font-size:.8em}.reports{background:#fff;list-style-type:none;position:relative;margin:0;top:-270px}.reports li,.reports>li a{position:absolute;width:30px}.reports li{margin:0 0 0 -15px;border:0;height:170px;-webkit-transform-origin:15px bottom;transform-origin:15px bottom}.reports li span{transition:-webkit-transform .4s ease;transition:transform .4s ease;display:block;margin:0}.reports>li a{height:30px;overflow:visible;text-align:center;border-radius:50%}.reports>li:nth-of-type(1){-webkit-transform:rotate(-108deg);transform:rotate(-108deg)}.reports>li:nth-of-type(2){-webkit-transform:rotate(-126deg);transform:rotate(-126deg)}.reports>li:nth-of-type(3){-webkit-transform:rotate(-144deg);transform:rotate(-144deg)}.reports>li:nth-of-type(4){-webkit-transform:rotate(-162deg);transform:rotate(-162deg)}.reports>li:nth-of-type(5){-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}.reports>li:nth-of-type(6){-webkit-transform:rotate(-198deg);transform:rotate(-198deg)}.reports>li:nth-of-type(7){-webkit-transform:rotate(-216deg);transform:rotate(-216deg)}.reports>li:nth-of-type(8){-webkit-transform:rotate(-234deg);transform:rotate(-234deg)}.reports>li:nth-of-type(9){-webkit-transform:rotate(-252deg);transform:rotate(-252deg)}.reports.expand li:nth-of-type(9) a ::before{content:'+';margin-right:2px}.reports.expand li:nth-of-type(1) a span{-webkit-transform:rotate(108deg);transform:rotate(108deg)}.reports.expand li:nth-of-type(2) a span{-webkit-transform:rotate(126deg);transform:rotate(126deg);-webkit-animation:spin2-expand .6s ease-out 1 backwards;animation:spin2-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(3) a span{-webkit-transform:rotate(144deg);transform:rotate(144deg);-webkit-animation:spin3-expand .6s ease-out 1 backwards;animation:spin3-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(4) a span{-webkit-transform:rotate(162deg);transform:rotate(162deg);-webkit-animation:spin4-expand .6s ease-out 1 backwards;animation:spin4-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(5) a span{-webkit-transform:rotate(180deg);transform:rotate(180deg);-webkit-animation:spin5-expand .6s ease-out 1 backwards;animation:spin5-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(6) a span{-webkit-transform:rotate(198deg);transform:rotate(198deg);-webkit-animation:spin6-expand .6s ease-out 1 backwards;animation:spin6-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(7) a span{-webkit-transform:rotate(216deg);transform:rotate(216deg);-webkit-animation:spin7-expand .6s ease-out 1 backwards;animation:spin7-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(8) a span{-webkit-transform:rotate(234deg);transform:rotate(234deg);-webkit-animation:spin8-expand .6s ease-out 1 backwards;animation:spin8-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(9) a span{-webkit-transform:rotate(252deg);transform:rotate(252deg);-webkit-animation:spin9-expand .6s ease-out 1 backwards;animation:spin9-expand .6s ease-out 1 backwards}.reports li a{top:165px;-webkit-animation:contract .35s ease-out 1 backwards;animation:contract .35s ease-out 1 backwards}.reports.expand li a{top:10px;-webkit-animation:expand .6s ease 1 backwards;animation:expand .6s ease 1 backwards}.reports li:nth-of-type(1) a{-webkit-animation-delay:.32s;animation-delay:.32s}.reports li:nth-of-type(2) a{-webkit-animation-delay:.28s;animation-delay:.28s}.reports li:nth-of-type(3) a{-webkit-animation-delay:.24s;animation-delay:.24s}.reports li:nth-of-type(4) a{-webkit-animation-delay:.2s;animation-delay:.2s}.reports li:nth-of-type(5) a{-webkit-animation-delay:.16s;animation-delay:.16s}.reports li:nth-of-type(6) a{-webkit-animation-delay:.12s;animation-delay:.12s}.reports li:nth-of-type(7) a{-webkit-animation-delay:.08s;animation-delay:.08s}.reports li:nth-of-type(8) a{-webkit-animation-delay:.04s;animation-delay:.04s}.reports li:nth-of-type(9) a,.reports.expand li:nth-of-type(1) a{-webkit-animation-delay:0s;animation-delay:0s}.reports.expand li:nth-of-type(2) a{-webkit-animation-delay:.04s;animation-delay:.04s}.reports.expand li:nth-of-type(3) a{-webkit-animation-delay:.08s;animation-delay:.08s}.reports.expand li:nth-of-type(4) a{-webkit-animation-delay:.12s;animation-delay:.12s}.reports.expand li:nth-of-type(5) a{-webkit-animation-delay:.16s;animation-delay:.16s}.reports.expand li:nth-of-type(6) a{-webkit-animation-delay:.2s;animation-delay:.2s}.reports.expand li:nth-of-type(7) a{-webkit-animation-delay:.24s;animation-delay:.24s}.reports.expand li:nth-of-type(8) a{-webkit-animation-delay:.28s;animation-delay:.28s}.reports.expand li:nth-of-type(9) a{line-height:22px;text-align:center;background:rgba(42,148,214,.5);border:4px solid rgba(255,255,255,.4);color:#fff;font-size:.7em;-webkit-animation-delay:.32s;animation-delay:.32s}#reportsListView{width:100%;height:100%;z-index:5;top:150px;left:0;position:fixed;overflow-y:scroll;-webkit-overflow-scrolling:touch}.tog,.toggle{position:absolute;left:10px;text-align:left}#reportsListView ul{margin-bottom:170px}#reportsListView li{text-align:left;cursor:pointer;clear:both;border-top:1px solid #dadee2;height:45px;padding:0 10px}#reportsListView li :first-child{border-top:none}#reportsListView li i{float:left;margin:12px 12px 8px 2px}#reportsListView li div{float:left;margin:10px 10px 10px 5px}#reportsListView .name,#reportsListView .title{width:240px}.tog{bottom:10px}.toggle{top:70px}.employeeFocusListView{top:40px!important;position:fixed!important;width:100%!important}#managerDetail{background-color:#FFF;text-align:center;width:100%;overflow-y:scroll;height:325px;height:calc(100% - 180px);position:absolute;-webkit-transform:translate3D(0,0,-1px) rotateX(180deg);transform:translate3D(0,0,-1px) rotateX(180deg)}.manager-image{height:60px;width:60px;background-size:60px;border:4px solid #fff;border-radius:50%;display:inline-block;background-position:center;background-repeat:no-repeat}.reporting-structure{-webkit-transform:translate3D(0,0,-1px) rotateX(180deg);transform:translate3D(0,0,-1px) rotateX(180deg);position:absolute;width:100%;padding-bottom:50px}.reporting-structure li{margin:30px auto;position:relative}.reporting-structure li::before,.reporting-structure li:nth-last-child(1)::after{-webkit-animation:fadeIn 5s 0 1 ease normal;animation:fadeIn 5s 0 1 ease normal;content:'';position:absolute;top:-62px;left:50%;width:2px;height:70px;background:#eee;z-index:-1}#searchResultWrapper,#searchResultWrapper_row{width:100%;height:100%}.reporting-structure li:nth-last-child(1)::after{top:62px}.reporting-structure li:first-child::before{display:none}.reporting-structure li:nth-last-child(1) i,.reporting-structure li:nth-last-child(1) p{-webkit-animation-delay:0s;animation-delay:0s}.reporting-structure li:nth-last-child(2) i,.reporting-structure li:nth-last-child(2) p{-webkit-animation-delay:.04s;animation-delay:.04s}.reporting-structure li:nth-last-child(3) i,.reporting-structure li:nth-last-child(3) p{-webkit-animation-delay:.08s;animation-delay:.08s}.reporting-structure li:nth-last-child(4) i,.reporting-structure li:nth-last-child(4) p{-webkit-animation-delay:.12s;animation-delay:.12s}.reporting-structure li:nth-last-child(5) i,.reporting-structure li:nth-last-child(5) p{-webkit-animation-delay:.16s;animation-delay:.16s}.reporting-structure li:nth-last-child(6) i,.reporting-structure li:nth-last-child(6) p{-webkit-animation-delay:.2s;animation-delay:.2s}.reporting-structure li:nth-last-child(7) i,.reporting-structure li:nth-last-child(7) p{-webkit-animation-delay:.24s;animation-delay:.24s}.reporting-structure li:nth-last-child(8) i,.reporting-structure li:nth-last-child(8) p{-webkit-animation-delay:.28s;animation-delay:.28s}.reporting-structure li:nth-last-child(9) i,.reporting-structure li:nth-last-child(9) p{-webkit-animation-delay:.32s;animation-delay:.32s}.reporting-structure li:nth-last-child(10) i,.reporting-structure li:nth-last-child(10) p{-webkit-animation-delay:.36s;animation-delay:.36s}.reporting-structure li:nth-last-child(11) i,.reporting-structure li:nth-last-child(11) p{-webkit-animation-delay:.4s;animation-delay:.4s}.reporting-structure li:nth-last-child(12) i,.reporting-structure li:nth-last-child(12) p{-webkit-animation-delay:.44s;animation-delay:.44s}.reporting-structure li:nth-last-child(13) i,.reporting-structure li:nth-last-child(13) p{-webkit-animation-delay:.48s;animation-delay:.48s}.reporting-structure li:nth-last-child(14) i,.reporting-structure li:nth-last-child(14) p{-webkit-animation-delay:.52s;animation-delay:.52s}.reporting-structure li:nth-last-child(15) i,.reporting-structure li:nth-last-child(15) p{-webkit-animation-delay:.56s;animation-delay:.56s}.reporting-structure li:nth-last-child(16) i,.reporting-structure li:nth-last-child(16) p{-webkit-animation-delay:.6s;animation-delay:.6s}.reporting-structure li:nth-last-child(17) i,.reporting-structure li:nth-last-child(17) p{-webkit-animation-delay:.64s;animation-delay:.64s}.reporting-structure li:nth-last-child(18) i,.reporting-structure li:nth-last-child(18) p{-webkit-animation-delay:.68s;animation-delay:.68s}.reporting-structure li:nth-last-child(19) i,.reporting-structure li:nth-last-child(19) p{-webkit-animation-delay:.72s;animation-delay:.72s}.reporting-structure li:nth-last-child(20) i,.reporting-structure li:nth-last-child(20) p{-webkit-animation-delay:.76s;animation-delay:.76s}#searchResultWrapper{z-index:14;position:absolute;top:48px;left:0;display:-webkit-flex;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center}#searchResultWrapper ul{height:100%;padding:5px 0;background-color:#fff}#searchResultWrapper ul li{cursor:pointer;padding:0;margin:10px}#searchBoxWrapper{position:absolute;top:5px;left:50%;z-index:15;width:86%;margin-left:-47%}#searchBox{width:100%;height:36px;padding:6px 10px;border-radius:5px;border:1px solid #85929F;font-size:16px}#searchToggle{padding:0;position:fixed;right:0;top:0;z-index:999;height:44px;width:44px;background:0;border:0;outline:0}.icon-utility-search{color:#999}.icon-utility-close{color:#fff}.icon-utility-close,.icon-utility-search{font-size:15px}#closeSelector{margin:0;padding:0;z-index:15;position:absolute;top:-44px;right:-4px}#expiredTokenOverlay,#phoneSelector{z-index:15;position:absolute;height:100%;width:100%;padding:0;margin:0;display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center}#expiredTokenMessage{width:100%;padding:10px;text-align:center}#selectorRow{width:260px;border-radius:5px;background-color:#fff;padding:14px;position:relative}.selection li{border:1px solid #dfe0e1;color:#5c7995;font-size:16px;font-weight:700;width:100%;padding:7px 0;margin:0 auto 14px;text-align:center;cursor:pointer;border-radius:5px}.selection li:last-child{margin:0!important}#overlay{opacity:.5;background:#344A5F;width:100%;height:100%;z-index:10;top:0;left:0;position:fixed}.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.bounceIn{-webkit-animation:bounceIn .35s;animation:bounceIn .35s;opacity:1}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes spin1-expand{0%{-webkit-transform:rotate(108deg);transform:rotate(108deg)}100%,60%{-webkit-transform:rotate(-252deg);transform:rotate(-252deg)}}@keyframes spin1-expand{0%{-webkit-transform:rotate(108deg);transform:rotate(108deg)}100%,60%{-webkit-transform:rotate(-252deg);transform:rotate(-252deg)}}@-webkit-keyframes spin2-expand{0%{-webkit-transform:rotate(126deg);transform:rotate(126deg)}100%,60%{-webkit-transform:rotate(-234deg);transform:rotate(-234deg)}}@keyframes spin2-expand{0%{-webkit-transform:rotate(126deg);transform:rotate(126deg)}100%,60%{-webkit-transform:rotate(-234deg);transform:rotate(-234deg)}}@-webkit-keyframes spin3-expand{0%{-webkit-transform:rotate(144deg);transform:rotate(144deg)}100%,60%{-webkit-transform:rotate(-216deg);transform:rotate(-216deg)}}@keyframes spin3-expand{0%{-webkit-transform:rotate(144deg);transform:rotate(144deg)}100%,60%{-webkit-transform:rotate(-216deg);transform:rotate(-216deg)}}@-webkit-keyframes spin4-expand{0%{-webkit-transform:rotate(162deg);transform:rotate(162deg)}100%,60%{-webkit-transform:rotate(-198deg);transform:rotate(-198deg)}}@keyframes spin4-expand{0%{-webkit-transform:rotate(162deg);transform:rotate(162deg)}100%,60%{-webkit-transform:rotate(-198deg);transform:rotate(-198deg)}}@-webkit-keyframes spin5-expand{0%{-webkit-transform:rotate(180deg);transform:rotate(180deg)}100%,60%{-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}}@keyframes spin5-expand{0%{-webkit-transform:rotate(180deg);transform:rotate(180deg)}100%,60%{-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}}@-webkit-keyframes spin6-expand{0%{-webkit-transform:rotate(198deg);transform:rotate(198deg)}100%,60%{-webkit-transform:rotate(-162deg);transform:rotate(-162deg)}}@keyframes spin6-expand{0%{-webkit-transform:rotate(198deg);transform:rotate(198deg)}100%,60%{-webkit-transform:rotate(-162deg);transform:rotate(-162deg)}}@-webkit-keyframes spin7-expand{0%{-webkit-transform:rotate(216eg);transform:rotate(216eg)}100%,60%{-webkit-transform:rotate(-144deg);transform:rotate(-144deg)}}@keyframes spin7-expand{0%{-webkit-transform:rotate(216eg);transform:rotate(216eg)}100%,60%{-webkit-transform:rotate(-144deg);transform:rotate(-144deg)}}@-webkit-keyframes spin8-expand{0%{-webkit-transform:rotate(234deg);transform:rotate(234deg)}100%,60%{-webkit-transform:rotate(-126deg);transform:rotate(-126deg)}}@keyframes spin8-expand{0%{-webkit-transform:rotate(234deg);transform:rotate(234deg)}100%,60%{-webkit-transform:rotate(-126deg);transform:rotate(-126deg)}}@-webkit-keyframes spin9-expand{0%{-webkit-transform:rotate(252deg);transform:rotate(252deg)}100%,60%{-webkit-transform:rotate(-108deg);transform:rotate(-108deg)}}@keyframes spin9-expand{0%{-webkit-transform:rotate(252deg);transform:rotate(252deg)}100%,60%{-webkit-transform:rotate(-108deg);transform:rotate(-108deg)}}@-webkit-keyframes expand{0%{top:165px}50%{top:-10px}70%{top:15px}100%{top:10px}}@keyframes expand{0%{top:165px}50%{top:-10px}70%{top:15px}100%{top:10px}}@-webkit-keyframes contract{0%{top:10px}40%{top:-25px}100%{top:165px}}@keyframes contract{0%{top:10px}40%{top:-25px}100%{top:165px}}@-webkit-keyframes bounceIn{0%,100%{-webkit-transform:scale(1);transform:scale(1)}60%{-webkit-transform:scale(1.3);transform:scale(1.3)}}@keyframes bounceIn{0%,100%{-webkit-transform:scale(1);transform:scale(1)}60%{-webkit-transform:scale(1.3);transform:scale(1.3)}}@-webkit-keyframes bounceInDown{0%{opacity:0;-webkit-transform:translateY(-2000px);transform:translateY(-2000px)}60%{opacity:1;-webkit-transform:translateY(30px);transform:translateY(30px)}80%{-webkit-transform:translateY(-10px);transform:translateY(-10px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes bounceInDown{0%{opacity:0;-webkit-transform:translateY(-2000px);transform:translateY(-2000px)}60%{opacity:1;-webkit-transform:translateY(30px);transform:translateY(30px)}80%{-webkit-transform:translateY(-10px);transform:translateY(-10px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes bounceInUp{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}60%{opacity:1;-webkit-transform:translateY(-30px);transform:translateY(-30px)}80%{-webkit-transform:translateY(10px);transform:translateY(10px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes bounceInUp{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}60%{opacity:1;-webkit-transform:translateY(-30px);transform:translateY(-30px)}80%{-webkit-transform:translateY(10px);transform:translateY(10px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes bounceOut{0%{-webkit-transform:scale(1);transform:scale(1)}25%{-webkit-transform:scale(.95);transform:scale(.95)}50%{opacity:1;-webkit-transform:scale(1.1);transform:scale(1.1)}100%{opacity:0;-webkit-transform:scale(.3);transform:scale(.3)}}@keyframes bounceOut{0%{-webkit-transform:scale(1);transform:scale(1)}25%{-webkit-transform:scale(.95);transform:scale(.95)}50%{opacity:1;-webkit-transform:scale(1.1);transform:scale(1.1)}100%{opacity:0;-webkit-transform:scale(.3);transform:scale(.3)}}@-webkit-keyframes bounceOutDown{0%{-webkit-transform:translateY(0);transform:translateY(0)}20%{opacity:1;-webkit-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}}@keyframes bounceOutDown{0%{-webkit-transform:translateY(0);transform:translateY(0)}20%{opacity:1;-webkit-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translateY(20px);transform:translateY(20px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translateY(20px);transform:translateY(20px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@-webkit-keyframes fadeOutDown{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(20px);transform:translateY(20px)}}@keyframes fadeOutDown{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(20px);transform:translateY(20px)}}@-webkit-keyframes fadeOutDownBig{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}}@keyframes fadeOutDownBig{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}40%{-webkit-transform:perspective(400px) rotateX(-10deg);transform:perspective(400px) rotateX(-10deg)}70%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg)}100%{-webkit-transform:perspective(400px) rotateX(0);transform:perspective(400px) rotateX(0);opacity:1}}@keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}40%{-webkit-transform:perspective(400px) rotateX(-10deg);transform:perspective(400px) rotateX(-10deg)}70%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg)}100%{-webkit-transform:perspective(400px) rotateX(0);transform:perspective(400px) rotateX(0);opacity:1}}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}40%{-webkit-transform:perspective(400px) rotateY(-10deg);transform:perspective(400px) rotateY(-10deg)}70%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg)}100%{-webkit-transform:perspective(400px) rotateY(0);transform:perspective(400px) rotateY(0);opacity:1}}@keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}40%{-webkit-transform:perspective(400px) rotateY(-10deg);transform:perspective(400px) rotateY(-10deg)}70%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg)}100%{-webkit-transform:perspective(400px) rotateY(0);transform:perspective(400px) rotateY(0);opacity:1}}@-webkit-keyframes rubberBand{0%{-webkit-transform:scale(1);transform:scale(1);opacity:0}30%{-webkit-transform:scaleX(1.25) scaleY(.75);transform:scaleX(1.25) scaleY(.75)}40%{-webkit-transform:scaleX(.75) scaleY(1.25);transform:scaleX(.75) scaleY(1.25)}60%{-webkit-transform:scaleX(1.15) scaleY(.85);transform:scaleX(1.15) scaleY(.85)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes rubberBand{0%{-webkit-transform:scale(1);transform:scale(1);opacity:0}30%{-webkit-transform:scaleX(1.25) scaleY(.75);transform:scaleX(1.25) scaleY(.75)}40%{-webkit-transform:scaleX(.75) scaleY(1.25);transform:scaleX(.75) scaleY(1.25)}60%{-webkit-transform:scaleX(1.15) scaleY(.85);transform:scaleX(1.15) scaleY(.85)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@-webkit-keyframes tada{0%{-webkit-transform:scale(1);transform:scale(1)}10%,20%{-webkit-transform:scale(.9) rotate(-3deg);transform:scale(.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale(1.1) rotate(3deg);transform:scale(1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale(1.1) rotate(-3deg);transform:scale(1.1) rotate(-3deg)}100%{-webkit-transform:scale(1) rotate(0);transform:scale(1) rotate(0)}}@keyframes tada{0%{-webkit-transform:scale(1);transform:scale(1)}10%,20%{-webkit-transform:scale(.9) rotate(-3deg);transform:scale(.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale(1.1) rotate(3deg);transform:scale(1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale(1.1) rotate(-3deg);transform:scale(1.1) rotate(-3deg)}100%{-webkit-transform:scale(1) rotate(0);transform:scale(1) rotate(0)}}@-webkit-keyframes wobble{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}15%{-webkit-transform:translateX(-25%) rotate(-5deg);transform:translateX(-25%) rotate(-5deg)}30%{-webkit-transform:translateX(20%) rotate(3deg);transform:translateX(20%) rotate(3deg)}45%{-webkit-transform:translateX(-15%) rotate(-3deg);transform:translateX(-15%) rotate(-3deg)}60%{-webkit-transform:translateX(10%) rotate(2deg);transform:translateX(10%) rotate(2deg)}75%{-webkit-transform:translateX(-5%) rotate(-1deg);transform:translateX(-5%) rotate(-1deg)}}@keyframes wobble{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}15%{-webkit-transform:translateX(-25%) rotate(-5deg);transform:translateX(-25%) rotate(-5deg)}30%{-webkit-transform:translateX(20%) rotate(3deg);transform:translateX(20%) rotate(3deg)}45%{-webkit-transform:translateX(-15%) rotate(-3deg);transform:translateX(-15%) rotate(-3deg)}60%{-webkit-transform:translateX(10%) rotate(2deg);transform:translateX(10%) rotate(2deg)}75%{-webkit-transform:translateX(-5%) rotate(-1deg);transform:translateX(-5%) rotate(-1deg)}}@media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:portrait) and (-webkit-min-device-pixel-ratio:2){#reportsListView div{width:90%}#reportsListView .name,#reportsListView .title{width:initial;margin:0}}@media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:landscape) and (-webkit-min-device-pixel-ratio:2){#reportsListView div{width:90%}#reportsListView .name,#reportsListView .title{width:initial;margin:0}}@media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:portrait){#reportsListView div{width:90%}#reportsListView .name,#reportsListView .title{width:initial;margin:0}}@media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:landscape){#reportsListView div{width:90%}#reportsListView .name,#reportsListView .title{width:initial;margin:0}}
--------------------------------------------------------------------------------
/dist/compiled/style.522e5d67.css:
--------------------------------------------------------------------------------
1 | .list-horizontal,.list-plain,ul{list-style:none}.list-horizontal,body,p,ul{margin:0;padding:0}#employeeLeftIcons a,.icon-utility-call,a{text-decoration:none}.reports,.reports li,body,p,ul{padding:0}*,:after,:before{box-sizing:border-box}body{font-family:SalesforceSans;background:#fff;color:#999;font-smoothing:antialiased;-webkit-tap-highlight-color:transparent;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-utility-call,a{color:#293f54}.list-horizontal>li,.list-horizontal>li>a{display:inline-block;zoom:1}.shadow{box-shadow:0 2px 0 rgba(0,0,0,.1)}.hide{display:none!important}.show{display:block!important}.fadeIt{opacity:.01}.unfadeIt{opacity:1}.showFlex{display:-webkit-flex!important;display:flex!important}.capitalize{text-transform:capitalize}.overflowScrolling-touch{-webkit-overflow-scrolling:touch}.overflowScrolling-default{-webkit-overflow-scrolling:auto}.main-title,.name,.title{overflow:hidden;margin:0 auto}@font-face{font-family:SalesforceSans;src:url(https://www2.sfdcstatic.com/system/shared/common/assets/fonts/SalesforceSans/SalesforceSans-Regular.woff) format('woff');font-weight:300;font-style:normal}.employee-image,.report-image{height:100px;width:100px;background-size:100px;position:relative;z-index:3;margin:0 auto;border:4px solid #fff;border-radius:50%;display:inline-block;background-position:center;background-color:#FFF;background-repeat:no-repeat}.report-image{border-width:1px}.employee-image{border:2px solid #8D3EEA}#swipePad{height:100px;width:100%;margin:-26px auto 2px;z-index:10}.report-image{height:30px;width:30px;background-size:30px;margin:0}#employeeDetail{text-align:center;position:fixed;bottom:0;left:0;width:100%;z-index:5;height:200px}#employeeStrip{width:100%;height:46px;margin:0;background:#f5f5f5;border-width:1px 0;position:absolute;font-size:18pt;top:0}#employeeStrip a,#employeeStrip i{width:44px;height:44px;color:#293f54;display:inline-block;line-height:44px}#employeeEmail,.name,.reports>li a,.title{display:block}#employeeLeftIcons,#employeeRightIcons{margin:0 10px}#employeeLeftIcons{float:left}#employeeRightIcons{float:right}#peersIcons{width:185px;margin:0 auto}#peersIcons i{color:#ccc;font-size:14pt}.name,.title{color:#2A94D6;z-index:999;white-space:nowrap;width:200px;text-overflow:ellipsis}.title{color:#999;font-size:.8em}.main-title{max-height:40px;width:180px;height:40px;font-size:.8em}.reports{background:#fff;list-style-type:none;position:relative;margin:0;top:-270px}.reports li,.reports>li a{position:absolute;width:30px}.reports li{margin:0 0 0 -15px;border:0;height:170px;-webkit-transform-origin:15px bottom;transform-origin:15px bottom}.reports li span{transition:-webkit-transform .4s ease;transition:transform .4s ease;display:block;margin:0}.reports>li a{height:30px;overflow:visible;text-align:center;border-radius:50%}.reports>li:nth-of-type(1){-webkit-transform:rotate(-108deg);transform:rotate(-108deg)}.reports>li:nth-of-type(2){-webkit-transform:rotate(-126deg);transform:rotate(-126deg)}.reports>li:nth-of-type(3){-webkit-transform:rotate(-144deg);transform:rotate(-144deg)}.reports>li:nth-of-type(4){-webkit-transform:rotate(-162deg);transform:rotate(-162deg)}.reports>li:nth-of-type(5){-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}.reports>li:nth-of-type(6){-webkit-transform:rotate(-198deg);transform:rotate(-198deg)}.reports>li:nth-of-type(7){-webkit-transform:rotate(-216deg);transform:rotate(-216deg)}.reports>li:nth-of-type(8){-webkit-transform:rotate(-234deg);transform:rotate(-234deg)}.reports>li:nth-of-type(9){-webkit-transform:rotate(-252deg);transform:rotate(-252deg)}.reports.expand li:nth-of-type(9) a ::before{content:'+';margin-right:2px}.reports.expand li:nth-of-type(1) a span{-webkit-transform:rotate(108deg);transform:rotate(108deg)}.reports.expand li:nth-of-type(2) a span{-webkit-transform:rotate(126deg);transform:rotate(126deg);-webkit-animation:spin2-expand .6s ease-out 1 backwards;animation:spin2-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(3) a span{-webkit-transform:rotate(144deg);transform:rotate(144deg);-webkit-animation:spin3-expand .6s ease-out 1 backwards;animation:spin3-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(4) a span{-webkit-transform:rotate(162deg);transform:rotate(162deg);-webkit-animation:spin4-expand .6s ease-out 1 backwards;animation:spin4-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(5) a span{-webkit-transform:rotate(180deg);transform:rotate(180deg);-webkit-animation:spin5-expand .6s ease-out 1 backwards;animation:spin5-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(6) a span{-webkit-transform:rotate(198deg);transform:rotate(198deg);-webkit-animation:spin6-expand .6s ease-out 1 backwards;animation:spin6-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(7) a span{-webkit-transform:rotate(216deg);transform:rotate(216deg);-webkit-animation:spin7-expand .6s ease-out 1 backwards;animation:spin7-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(8) a span{-webkit-transform:rotate(234deg);transform:rotate(234deg);-webkit-animation:spin8-expand .6s ease-out 1 backwards;animation:spin8-expand .6s ease-out 1 backwards}.reports.expand li:nth-of-type(9) a span{-webkit-transform:rotate(252deg);transform:rotate(252deg);-webkit-animation:spin9-expand .6s ease-out 1 backwards;animation:spin9-expand .6s ease-out 1 backwards}.reports li a{top:165px;-webkit-animation:contract .35s ease-out 1 backwards;animation:contract .35s ease-out 1 backwards}.reports.expand li a{top:10px;-webkit-animation:expand .6s ease 1 backwards;animation:expand .6s ease 1 backwards}.reports li:nth-of-type(1) a{-webkit-animation-delay:.32s;animation-delay:.32s}.reports li:nth-of-type(2) a{-webkit-animation-delay:.28s;animation-delay:.28s}.reports li:nth-of-type(3) a{-webkit-animation-delay:.24s;animation-delay:.24s}.reports li:nth-of-type(4) a{-webkit-animation-delay:.2s;animation-delay:.2s}.reports li:nth-of-type(5) a{-webkit-animation-delay:.16s;animation-delay:.16s}.reports li:nth-of-type(6) a{-webkit-animation-delay:.12s;animation-delay:.12s}.reports li:nth-of-type(7) a{-webkit-animation-delay:.08s;animation-delay:.08s}.reports li:nth-of-type(8) a{-webkit-animation-delay:.04s;animation-delay:.04s}.reports li:nth-of-type(9) a,.reports.expand li:nth-of-type(1) a{-webkit-animation-delay:0s;animation-delay:0s}.reports.expand li:nth-of-type(2) a{-webkit-animation-delay:.04s;animation-delay:.04s}.reports.expand li:nth-of-type(3) a{-webkit-animation-delay:.08s;animation-delay:.08s}.reports.expand li:nth-of-type(4) a{-webkit-animation-delay:.12s;animation-delay:.12s}.reports.expand li:nth-of-type(5) a{-webkit-animation-delay:.16s;animation-delay:.16s}.reports.expand li:nth-of-type(6) a{-webkit-animation-delay:.2s;animation-delay:.2s}.reports.expand li:nth-of-type(7) a{-webkit-animation-delay:.24s;animation-delay:.24s}.reports.expand li:nth-of-type(8) a{-webkit-animation-delay:.28s;animation-delay:.28s}.reports.expand li:nth-of-type(9) a{line-height:22px;text-align:center;background:rgba(42,148,214,.5);border:4px solid rgba(255,255,255,.4);color:#fff;font-size:.7em;-webkit-animation-delay:.32s;animation-delay:.32s}#reportsListView{width:100%;height:100%;z-index:5;top:150px;left:0;position:fixed;overflow-y:scroll;-webkit-overflow-scrolling:touch}.tog,.toggle{position:absolute;left:10px;text-align:left}#reportsListView ul{margin-bottom:170px}#reportsListView li{text-align:left;cursor:pointer;clear:both;border-top:1px solid #dadee2;height:45px;padding:0 10px}#reportsListView li :first-child{border-top:none}#reportsListView li i{float:left;margin:12px 12px 8px 2px}#reportsListView li div{float:left;margin:10px 10px 10px 5px}#reportsListView .name,#reportsListView .title{width:240px}.tog{bottom:10px}.toggle{top:70px}.employeeFocusListView{top:40px!important;position:fixed!important;width:100%!important}#managerDetail{background-color:#FFF;text-align:center;width:100%;overflow-y:scroll;height:325px;height:calc(100% - 180px);position:absolute;-webkit-transform:translate3D(0,0,-1px) rotateX(180deg);transform:translate3D(0,0,-1px) rotateX(180deg)}.manager-image{height:60px;width:60px;background-size:60px;border:4px solid #fff;border-radius:50%;display:inline-block;background-position:center;background-repeat:no-repeat}.reporting-structure{-webkit-transform:translate3D(0,0,-1px) rotateX(180deg);transform:translate3D(0,0,-1px) rotateX(180deg);position:absolute;width:100%;padding-bottom:50px}.reporting-structure li{margin:30px auto;position:relative}.reporting-structure li::before,.reporting-structure li:nth-last-child(1)::after{-webkit-animation:fadeIn 5s 0 1 ease normal;animation:fadeIn 5s 0 1 ease normal;content:'';position:absolute;top:-62px;left:50%;width:2px;height:70px;background:#eee;z-index:-1}#searchResultWrapper,#searchResultWrapper_row{width:100%;height:100%}.reporting-structure li:nth-last-child(1)::after{top:62px}.reporting-structure li:first-child::before{display:none}.reporting-structure li:nth-last-child(1) i,.reporting-structure li:nth-last-child(1) p{-webkit-animation-delay:0s;animation-delay:0s}.reporting-structure li:nth-last-child(2) i,.reporting-structure li:nth-last-child(2) p{-webkit-animation-delay:.04s;animation-delay:.04s}.reporting-structure li:nth-last-child(3) i,.reporting-structure li:nth-last-child(3) p{-webkit-animation-delay:.08s;animation-delay:.08s}.reporting-structure li:nth-last-child(4) i,.reporting-structure li:nth-last-child(4) p{-webkit-animation-delay:.12s;animation-delay:.12s}.reporting-structure li:nth-last-child(5) i,.reporting-structure li:nth-last-child(5) p{-webkit-animation-delay:.16s;animation-delay:.16s}.reporting-structure li:nth-last-child(6) i,.reporting-structure li:nth-last-child(6) p{-webkit-animation-delay:.2s;animation-delay:.2s}.reporting-structure li:nth-last-child(7) i,.reporting-structure li:nth-last-child(7) p{-webkit-animation-delay:.24s;animation-delay:.24s}.reporting-structure li:nth-last-child(8) i,.reporting-structure li:nth-last-child(8) p{-webkit-animation-delay:.28s;animation-delay:.28s}.reporting-structure li:nth-last-child(9) i,.reporting-structure li:nth-last-child(9) p{-webkit-animation-delay:.32s;animation-delay:.32s}.reporting-structure li:nth-last-child(10) i,.reporting-structure li:nth-last-child(10) p{-webkit-animation-delay:.36s;animation-delay:.36s}.reporting-structure li:nth-last-child(11) i,.reporting-structure li:nth-last-child(11) p{-webkit-animation-delay:.4s;animation-delay:.4s}.reporting-structure li:nth-last-child(12) i,.reporting-structure li:nth-last-child(12) p{-webkit-animation-delay:.44s;animation-delay:.44s}.reporting-structure li:nth-last-child(13) i,.reporting-structure li:nth-last-child(13) p{-webkit-animation-delay:.48s;animation-delay:.48s}.reporting-structure li:nth-last-child(14) i,.reporting-structure li:nth-last-child(14) p{-webkit-animation-delay:.52s;animation-delay:.52s}.reporting-structure li:nth-last-child(15) i,.reporting-structure li:nth-last-child(15) p{-webkit-animation-delay:.56s;animation-delay:.56s}.reporting-structure li:nth-last-child(16) i,.reporting-structure li:nth-last-child(16) p{-webkit-animation-delay:.6s;animation-delay:.6s}.reporting-structure li:nth-last-child(17) i,.reporting-structure li:nth-last-child(17) p{-webkit-animation-delay:.64s;animation-delay:.64s}.reporting-structure li:nth-last-child(18) i,.reporting-structure li:nth-last-child(18) p{-webkit-animation-delay:.68s;animation-delay:.68s}.reporting-structure li:nth-last-child(19) i,.reporting-structure li:nth-last-child(19) p{-webkit-animation-delay:.72s;animation-delay:.72s}.reporting-structure li:nth-last-child(20) i,.reporting-structure li:nth-last-child(20) p{-webkit-animation-delay:.76s;animation-delay:.76s}#searchResultWrapper{z-index:14;position:absolute;top:48px;left:0;display:-webkit-flex;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center}#searchResultWrapper ul{height:100%;padding:5px 0;background-color:#fff}#searchResultWrapper ul li{cursor:pointer;padding:0;margin:10px}#searchBoxWrapper{position:absolute;top:5px;left:50%;z-index:15;width:86%;margin-left:-47%}#searchBox{width:100%;height:36px;padding:6px 10px;border-radius:5px;border:1px solid #85929F;font-size:16px}#searchToggle{padding:0;position:fixed;right:0;top:0;z-index:999;height:44px;width:44px;background:0;border:0;outline:0}.icon-utility-search{color:#999}.icon-utility-close{color:#fff}.icon-utility-close,.icon-utility-search{font-size:15px}#closeSelector{margin:0;padding:0;z-index:15;position:absolute;top:-44px;right:-4px}#expiredTokenOverlay,#phoneSelector{z-index:15;position:absolute;height:100%;width:100%;padding:0;margin:0;display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center}#expiredTokenMessage{width:100%;padding:10px;text-align:center}#selectorRow{width:260px;border-radius:5px;background-color:#fff;padding:14px;position:relative}.selection li{border:1px solid #dfe0e1;color:#5c7995;font-size:16px;font-weight:700;width:100%;padding:7px 0;margin:0 auto 14px;text-align:center;cursor:pointer;border-radius:5px}.selection li:last-child{margin:0!important}#overlay{opacity:.5;background:#344A5F;width:100%;height:100%;z-index:10;top:0;left:0;position:fixed}.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.bounceIn{-webkit-animation:bounceIn .35s;animation:bounceIn .35s;opacity:1}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes spin1-expand{0%{-webkit-transform:rotate(108deg);transform:rotate(108deg)}100%,60%{-webkit-transform:rotate(-252deg);transform:rotate(-252deg)}}@keyframes spin1-expand{0%{-webkit-transform:rotate(108deg);transform:rotate(108deg)}100%,60%{-webkit-transform:rotate(-252deg);transform:rotate(-252deg)}}@-webkit-keyframes spin2-expand{0%{-webkit-transform:rotate(126deg);transform:rotate(126deg)}100%,60%{-webkit-transform:rotate(-234deg);transform:rotate(-234deg)}}@keyframes spin2-expand{0%{-webkit-transform:rotate(126deg);transform:rotate(126deg)}100%,60%{-webkit-transform:rotate(-234deg);transform:rotate(-234deg)}}@-webkit-keyframes spin3-expand{0%{-webkit-transform:rotate(144deg);transform:rotate(144deg)}100%,60%{-webkit-transform:rotate(-216deg);transform:rotate(-216deg)}}@keyframes spin3-expand{0%{-webkit-transform:rotate(144deg);transform:rotate(144deg)}100%,60%{-webkit-transform:rotate(-216deg);transform:rotate(-216deg)}}@-webkit-keyframes spin4-expand{0%{-webkit-transform:rotate(162deg);transform:rotate(162deg)}100%,60%{-webkit-transform:rotate(-198deg);transform:rotate(-198deg)}}@keyframes spin4-expand{0%{-webkit-transform:rotate(162deg);transform:rotate(162deg)}100%,60%{-webkit-transform:rotate(-198deg);transform:rotate(-198deg)}}@-webkit-keyframes spin5-expand{0%{-webkit-transform:rotate(180deg);transform:rotate(180deg)}100%,60%{-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}}@keyframes spin5-expand{0%{-webkit-transform:rotate(180deg);transform:rotate(180deg)}100%,60%{-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}}@-webkit-keyframes spin6-expand{0%{-webkit-transform:rotate(198deg);transform:rotate(198deg)}100%,60%{-webkit-transform:rotate(-162deg);transform:rotate(-162deg)}}@keyframes spin6-expand{0%{-webkit-transform:rotate(198deg);transform:rotate(198deg)}100%,60%{-webkit-transform:rotate(-162deg);transform:rotate(-162deg)}}@-webkit-keyframes spin7-expand{0%{-webkit-transform:rotate(216eg);transform:rotate(216eg)}100%,60%{-webkit-transform:rotate(-144deg);transform:rotate(-144deg)}}@keyframes spin7-expand{0%{-webkit-transform:rotate(216eg);transform:rotate(216eg)}100%,60%{-webkit-transform:rotate(-144deg);transform:rotate(-144deg)}}@-webkit-keyframes spin8-expand{0%{-webkit-transform:rotate(234deg);transform:rotate(234deg)}100%,60%{-webkit-transform:rotate(-126deg);transform:rotate(-126deg)}}@keyframes spin8-expand{0%{-webkit-transform:rotate(234deg);transform:rotate(234deg)}100%,60%{-webkit-transform:rotate(-126deg);transform:rotate(-126deg)}}@-webkit-keyframes spin9-expand{0%{-webkit-transform:rotate(252deg);transform:rotate(252deg)}100%,60%{-webkit-transform:rotate(-108deg);transform:rotate(-108deg)}}@keyframes spin9-expand{0%{-webkit-transform:rotate(252deg);transform:rotate(252deg)}100%,60%{-webkit-transform:rotate(-108deg);transform:rotate(-108deg)}}@-webkit-keyframes expand{0%{top:165px}50%{top:-10px}70%{top:15px}100%{top:10px}}@keyframes expand{0%{top:165px}50%{top:-10px}70%{top:15px}100%{top:10px}}@-webkit-keyframes contract{0%{top:10px}40%{top:-25px}100%{top:165px}}@keyframes contract{0%{top:10px}40%{top:-25px}100%{top:165px}}@-webkit-keyframes bounceIn{0%,100%{-webkit-transform:scale(1);transform:scale(1)}60%{-webkit-transform:scale(1.3);transform:scale(1.3)}}@keyframes bounceIn{0%,100%{-webkit-transform:scale(1);transform:scale(1)}60%{-webkit-transform:scale(1.3);transform:scale(1.3)}}@-webkit-keyframes bounceInDown{0%{opacity:0;-webkit-transform:translateY(-2000px);transform:translateY(-2000px)}60%{opacity:1;-webkit-transform:translateY(30px);transform:translateY(30px)}80%{-webkit-transform:translateY(-10px);transform:translateY(-10px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes bounceInDown{0%{opacity:0;-webkit-transform:translateY(-2000px);transform:translateY(-2000px)}60%{opacity:1;-webkit-transform:translateY(30px);transform:translateY(30px)}80%{-webkit-transform:translateY(-10px);transform:translateY(-10px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes bounceInUp{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}60%{opacity:1;-webkit-transform:translateY(-30px);transform:translateY(-30px)}80%{-webkit-transform:translateY(10px);transform:translateY(10px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes bounceInUp{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}60%{opacity:1;-webkit-transform:translateY(-30px);transform:translateY(-30px)}80%{-webkit-transform:translateY(10px);transform:translateY(10px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes bounceOut{0%{-webkit-transform:scale(1);transform:scale(1)}25%{-webkit-transform:scale(.95);transform:scale(.95)}50%{opacity:1;-webkit-transform:scale(1.1);transform:scale(1.1)}100%{opacity:0;-webkit-transform:scale(.3);transform:scale(.3)}}@keyframes bounceOut{0%{-webkit-transform:scale(1);transform:scale(1)}25%{-webkit-transform:scale(.95);transform:scale(.95)}50%{opacity:1;-webkit-transform:scale(1.1);transform:scale(1.1)}100%{opacity:0;-webkit-transform:scale(.3);transform:scale(.3)}}@-webkit-keyframes bounceOutDown{0%{-webkit-transform:translateY(0);transform:translateY(0)}20%{opacity:1;-webkit-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}}@keyframes bounceOutDown{0%{-webkit-transform:translateY(0);transform:translateY(0)}20%{opacity:1;-webkit-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translateY(20px);transform:translateY(20px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translateY(20px);transform:translateY(20px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}@-webkit-keyframes fadeOutDown{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(20px);transform:translateY(20px)}}@keyframes fadeOutDown{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(20px);transform:translateY(20px)}}@-webkit-keyframes fadeOutDownBig{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}}@keyframes fadeOutDownBig{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}40%{-webkit-transform:perspective(400px) rotateX(-10deg);transform:perspective(400px) rotateX(-10deg)}70%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg)}100%{-webkit-transform:perspective(400px) rotateX(0);transform:perspective(400px) rotateX(0);opacity:1}}@keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}40%{-webkit-transform:perspective(400px) rotateX(-10deg);transform:perspective(400px) rotateX(-10deg)}70%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg)}100%{-webkit-transform:perspective(400px) rotateX(0);transform:perspective(400px) rotateX(0);opacity:1}}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}40%{-webkit-transform:perspective(400px) rotateY(-10deg);transform:perspective(400px) rotateY(-10deg)}70%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg)}100%{-webkit-transform:perspective(400px) rotateY(0);transform:perspective(400px) rotateY(0);opacity:1}}@keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}40%{-webkit-transform:perspective(400px) rotateY(-10deg);transform:perspective(400px) rotateY(-10deg)}70%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg)}100%{-webkit-transform:perspective(400px) rotateY(0);transform:perspective(400px) rotateY(0);opacity:1}}@-webkit-keyframes rubberBand{0%{-webkit-transform:scale(1);transform:scale(1);opacity:0}30%{-webkit-transform:scaleX(1.25) scaleY(.75);transform:scaleX(1.25) scaleY(.75)}40%{-webkit-transform:scaleX(.75) scaleY(1.25);transform:scaleX(.75) scaleY(1.25)}60%{-webkit-transform:scaleX(1.15) scaleY(.85);transform:scaleX(1.15) scaleY(.85)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes rubberBand{0%{-webkit-transform:scale(1);transform:scale(1);opacity:0}30%{-webkit-transform:scaleX(1.25) scaleY(.75);transform:scaleX(1.25) scaleY(.75)}40%{-webkit-transform:scaleX(.75) scaleY(1.25);transform:scaleX(.75) scaleY(1.25)}60%{-webkit-transform:scaleX(1.15) scaleY(.85);transform:scaleX(1.15) scaleY(.85)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@-webkit-keyframes tada{0%{-webkit-transform:scale(1);transform:scale(1)}10%,20%{-webkit-transform:scale(.9) rotate(-3deg);transform:scale(.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale(1.1) rotate(3deg);transform:scale(1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale(1.1) rotate(-3deg);transform:scale(1.1) rotate(-3deg)}100%{-webkit-transform:scale(1) rotate(0);transform:scale(1) rotate(0)}}@keyframes tada{0%{-webkit-transform:scale(1);transform:scale(1)}10%,20%{-webkit-transform:scale(.9) rotate(-3deg);transform:scale(.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale(1.1) rotate(3deg);transform:scale(1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale(1.1) rotate(-3deg);transform:scale(1.1) rotate(-3deg)}100%{-webkit-transform:scale(1) rotate(0);transform:scale(1) rotate(0)}}@-webkit-keyframes wobble{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}15%{-webkit-transform:translateX(-25%) rotate(-5deg);transform:translateX(-25%) rotate(-5deg)}30%{-webkit-transform:translateX(20%) rotate(3deg);transform:translateX(20%) rotate(3deg)}45%{-webkit-transform:translateX(-15%) rotate(-3deg);transform:translateX(-15%) rotate(-3deg)}60%{-webkit-transform:translateX(10%) rotate(2deg);transform:translateX(10%) rotate(2deg)}75%{-webkit-transform:translateX(-5%) rotate(-1deg);transform:translateX(-5%) rotate(-1deg)}}@keyframes wobble{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}15%{-webkit-transform:translateX(-25%) rotate(-5deg);transform:translateX(-25%) rotate(-5deg)}30%{-webkit-transform:translateX(20%) rotate(3deg);transform:translateX(20%) rotate(3deg)}45%{-webkit-transform:translateX(-15%) rotate(-3deg);transform:translateX(-15%) rotate(-3deg)}60%{-webkit-transform:translateX(10%) rotate(2deg);transform:translateX(10%) rotate(2deg)}75%{-webkit-transform:translateX(-5%) rotate(-1deg);transform:translateX(-5%) rotate(-1deg)}}@media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:portrait) and (-webkit-min-device-pixel-ratio:2){#reportsListView div{width:90%}#reportsListView .name,#reportsListView .title{width:initial;margin:0}}@media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:landscape) and (-webkit-min-device-pixel-ratio:2){#reportsListView div{width:90%}#reportsListView .name,#reportsListView .title{width:initial;margin:0}}@media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:portrait){#reportsListView div{width:90%}#reportsListView .name,#reportsListView .title{width:initial;margin:0}}@media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:landscape){#reportsListView div{width:90%}#reportsListView .name,#reportsListView .title{width:initial;margin:0}}
--------------------------------------------------------------------------------