<commit_before>angular.module('provider.firebase', [
    'service.data'
])
    .provider('$firebaseService', function FirebaseServiceProvider() {

        this.Service = {};
        this.Providers = {};

        // @IMPORTANT MUST be independent of $rideStorage
        function FirebaseService($all, $firebase, $providers, $store) {
            console.log('$firebaseService');
            console.warn('Firebase ready? %o', (typeof $firebase === 'object' && $firebase.facebookProvider !== undefined));

            var _ = {
                Service: $firebase,
                providers: function (name) {
                    return $providers;
                },
                provider: function (name) {
                    if (!name) return;
                    switch (name.toLowerCase()) {
                        case 'fb':
                        case 'facebook':
                            return $providers.Facebook;
                    }
                },
                write: function (path, valueStruct, dataIsPrivate) {
                    // get current user
                    var currentUser = $firebase.auth().currentUser;
                    if (!currentUser) {
                        console.error('No firebase user available! Aborting write to database.');
                        return;
                    }
                    // compose path for data update
                    var refPath = dataIsPrivate ? ['users', currentUser.uid, path].join('/') : (path || ['temp', 'sessions', currentUser.uid].join('/'));
                    console.warn('Starting save to path: %s', refPath);
                    // write back data update, and return promise
                    return $firebase.database()
                        .ref(refPath)
                        .set(valueStruct)
                        /*
                        .then(function () {
                            console.info('Data saved @%s: %o', refPath, arguments);
                        })
                        */
                        ;
                },
                saveFile: function (path, name, buffer) {
                    return new Promise(function (resolve, reject) {
                        // create root reference
                        var storeRef = $firebase.storage().ref();
                        // create specific file reference
                        var fileRef = storeRef.child([path, name].join('/'));
                        var task = fileRef.put(buffer);
                        task.on('state_changed',
                            function progress(snapshot) {
                                // Observe state change events such as progress, pause, and resume
                                // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
                                var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
                                console.log('Upload is ' + progress + '% done');
                                switch (snapshot.state) {
                                    case firebase.storage.TaskState.PAUSED: // or 'paused'
                                        console.log('Upload is paused');
                                        break;
                                    case firebase.storage.TaskState.RUNNING: // or 'running'
                                        console.log('Upload is running');
                                        break;
                                }
                            },
                            function errorHandler(error) {
                                return reject(error);
                            },
                            function complete() {
                                // get download URL
                                task.snapshot.downloadURL;
                                // resolve
                                return resolve(task.snapshot);
                            })
                            ;
                    });
                }
            };

            // Housekeeping here
            $firebase.auth().onAuthStateChanged(function (user) {
                console.warn('Firebase user: %o', user);
                // send signals that firebase is ready, and authenticated
                $all.$broadcast('firebase:ready', user);
                $all.$broadcast('firebase:authChanged', user);
                // save firebase user
                $store.set('firebaseUser', user);
                // apply any changes to UI
                if (!$all.$$phase) $all.$apply();
            });

            return _;

        };

        this.$get = ["$rootScope", "$instanceStorage", function authServiceFactory($rootScope, nameSpacedStorage) {
            var _this = this;
            // initialize the factory
            return new FirebaseService($rootScope, _this.Service, _this.Providers, nameSpacedStorage);
        }];

    });<commit_msg>Nothing to see here. Just updating...<commit_after>angular.module('provider.firebase', [
    'service.data'
])
    .provider('$firebaseService', function FirebaseServiceProvider() {

        this.Service = {};
        this.Providers = {};

        // @IMPORTANT MUST be independent of $rideStorage
        function FirebaseService($all, $firebase, $providers, $store) {
            console.log('$firebaseService');
            console.warn('Firebase ready? %o', (typeof $firebase === 'object' && $firebase.facebookProvider !== undefined));

            var validProviders = ['Facebook', 'Google', 'Twitter']
                , _ = {
                    Service: $firebase,
                    setValidProviders: function (arr) {
                        if (!Array.isArray(arr)) return console.error("providers list MUST be an array of strings; e.g. [ 'Facebook', 'Google' ]");
                        validProviders = arr;
                    },
                    providers: function (name) {
                        return $providers;
                    },
                    provider: function (name) {
                        if (!name) return;
                        /*
                        switch (name.toLowerCase()) {
                            case 'fb':
                            case 'facebook':
                                return $providers.Facebook;
                        }
                        */
                        try {
                            return $providers[name];
                        } catch (err) {
                            console.error('Failed to locate and serve up provider object @ key [%s] - may not be available', name);
                        }
                    },
                    write: function (path, valueStruct, dataIsPrivate) {
                        // get current user
                        var currentUser = $firebase.auth().currentUser;
                        if (!currentUser) {
                            console.error('No firebase user available! Aborting write to database.');
                            return;
                        }
                        // compose path for data update
                        var refPath = dataIsPrivate ? ['users', currentUser.uid, path].join('/') : (path || ['temp', 'sessions', currentUser.uid].join('/'));
                        console.warn('Starting save to path: %s', refPath);
                        // write back data update, and return promise
                        return $firebase.database()
                            .ref(refPath)
                            .set(valueStruct)
                            /*
                            .then(function () {
                                console.info('Data saved @%s: %o', refPath, arguments);
                            })
                            */
                            ;
                    },
                    saveFile: function (path, name, buffer) {
                        return new Promise(function (resolve, reject) {
                            // create root reference
                            var storeRef = $firebase.storage().ref();
                            // create specific file reference
                            var fileRef = storeRef.child([path, name].join('/'));
                            var task = fileRef.put(buffer);
                            task.on('state_changed',
                                function progress(snapshot) {
                                    // Observe state change events such as progress, pause, and resume
                                    // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
                                    var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
                                    console.log('Upload is ' + progress + '% done');
                                    switch (snapshot.state) {
                                        case firebase.storage.TaskState.PAUSED: // or 'paused'
                                            console.log('Upload is paused');
                                            break;
                                        case firebase.storage.TaskState.RUNNING: // or 'running'
                                            console.log('Upload is running');
                                            break;
                                    }
                                },
                                function errorHandler(error) {
                                    return reject(error);
                                },
                                function complete() {
                                    // get download URL
                                    task.snapshot.downloadURL;
                                    // resolve
                                    return resolve(task.snapshot);
                                })
                                ;
                        });
                    }
                };

            // Housekeeping here
            $firebase.auth().onAuthStateChanged(function (user) {
                console.warn('Firebase user: %o', user);
                // send signals that firebase is ready, and authenticated
                $all.$broadcast('firebase:ready', user);
                $all.$broadcast('firebase:authChanged', user);
                // save firebase user
                $store.set('firebaseUser', user);
                // apply any changes to UI
                if (!$all.$$phase) $all.$apply();
            });

            return _;

        };

        this.$get = ["$rootScope", "$instanceStorage", function authServiceFactory($rootScope, nameSpacedStorage) {
            var _this = this;
            // initialize the factory
            return new FirebaseService($rootScope, _this.Service, _this.Providers, nameSpacedStorage);
        }];

    });<|endoftext|>


<commit_before>var model = null;

module.exports = function modelLoader(isParent) {
    'use strict';

    if (isParent || process.env.MCTEST) {
        return require('./mocks/model');
    }

    if (!model) {
        let ropts = {
            db: process.env.MCDB || 'materialscommons',
            port: process.env.MCPORT || 30815
        };

        let r = require('rethinkdbdash')(ropts);
        model = require('./db/model')(r);
    }

    return model;
};
<commit_msg>Change model-loader so it only creates the model once.<commit_after>var model = null;

module.exports = function modelLoader(isParent) {
    'use strict';

    if (model) {
        return model;
    }

    if (isParent || process.env.MCTEST) {
        model = require('./mocks/model');
    } else {
        let ropts = {
            db: process.env.MCDB || 'materialscommons',
            port: process.env.MCPORT || 30815
        };

        let r = require('rethinkdbdash')(ropts);
        model = require('./db/model')(r);
    }

    return model;
};
<|endoftext|>


<commit_before>import { CatApp } from "../src/cat.app"
import data from "./data.json"

describe("CatApp", function () {
  let catApp;

  beforeEach(function () {
    catApp = new CatApp();
  })

  describe('init', function () {
    it('should get the dom element with id "pets" as the target node', function () {

      catApp.init().then(function () {
        document.getElementById('pets').innerHTML = "test";
        expect(document.getElementById('pets').innerHTML).toBe("test")
      })
    })
  })

  describe('fetchData', function () {
    let apiUrl = "testurl.api.com";
    let window = {};
    beforeEach(function () {
      window = {
        fetch: function (apiUrl) {
          return {};
        }
      };
    })

    it('should call window.fetch function and get data from api', function () {
      spyOn(window, 'fetch').and.callThrough();
      window.fetch(apiUrl);
      catApp.fetchData();

      expect(window).toBeDefined();
      expect(window.fetch).toHaveBeenCalled();
      expect(window.fetch).toHaveBeenCalledWith(apiUrl);

    })
  })

  describe('processData', function () {
    it('should list all male and female pets grouped by owners gender', function () {
      const result = catApp.processData(data)
      expect(result).toEqual(jasmine.any(Object))
      expect(Object.keys(result)).toEqual([
        'male',
        'female',
      ]);
      expect(result.male.length).toBeGreaterThan(1);
      expect(result.female.length).toBeGreaterThan(1);
    })

    it('should return if data is undefined', function () {
      const result = catApp.processData(undefined)
      expect(result).toBe(undefined)
    })

  })

  describe('getCatsSortedByName', function () {
    it('should filter all cats from pets sorted by  name', function () {
      const result = catApp.getCatsSortedByName([{
        "name": "Bob",
        "gender": "Male",
        "age": 23,
        "pets": [{
          "name": "Garfield",
          "type": "Cat"
        }, {
          "name": "Fido",
          "type": "Dog"
        }]
      }, {
        "name": "Jennifer",
        "gender": "Female",
        "age": 18,
        "pets": [{
          "name": "Garfield",
          "type": "Cat"
        }]
      }
      ])
      expect(result).toEqual(jasmine.any(Object))
      expect(result.length).toBeGreaterThan(1);
    })

    it('should return an empty array if data is undefined', function () {
      const result = catApp.getCatsSortedByName(undefined)
      expect(result).toEqual(jasmine.any(Object))
    })

  })

  describe('drawPets', function () {
    it('should render output to the target node', function () {
      const result = catApp.drawPets([
        {
          "name": "Garfield",
          "type": "Cat"
        },
        {
          "name": "Tom",
          "type": "Cat"
        }
      ], 'MALE')

      expect(result).toBeDefined();
      expect(result).toEqual(jasmine.any(Object));
      expect(result.length).toBeGreaterThan(1);
      expect(result[0]).toEqual('
MALE
')
      expect(result[1]).toEqual('
')
      expect(result[2]).toEqual(['
Garfield
'])
      expect(result[3]).toEqual(['
Tom
'])
      expect(result[4]).toEqual('
')
    })

    it('should return "No Cats For Male" message if data is undefined', function () {
      const result = catApp.drawPets(undefined, 'Male')

      expect(result).toEqual(jasmine.any(Object))
      expect(result[0]).toEqual('
No Cats For Male
')
    })

  })

});<commit_msg>Update testcase<commit_after>import { CatApp } from "../src/cat.app"
import data from "./data.json"

describe("CatApp", function () {
  let catApp;

  beforeEach(function () {
    catApp = new CatApp();
  })

  describe('init', function () {
    it('should get the dom element with id "pets" as the target node', function () {
      catApp.init().then(function () {
        document.getElementById('pets').innerHTML = "test";
        expect(document.getElementById('pets').innerHTML).toBe("test")
      })
    })
  })

  describe('fetchData', function () {
    let apiUrl = "testurl.api.com";
    let window = {};
    beforeEach(function () {
      window = {
        fetch: function (apiUrl) {
          return {};
        },
        fetchEmptyUrl: function () {
           return {};
        }
      };
    })

    it('should call window.fetch function and get data from api', function () {
      spyOn(window, 'fetch').and.callThrough();
      window.fetch(apiUrl);
      catApp.fetchData();

      expect(window).toBeDefined();
      expect(window.fetch).toHaveBeenCalled();
      expect(window.fetch).toHaveBeenCalledWith(apiUrl);

    })

    it('should log error if there is no url passed', function () {
      spyOn(window, 'fetchEmptyUrl').and.callThrough();
      window.fetchEmptyUrl();

      catApp.fetchData();
      expect(window).toBeDefined();
      expect(window.fetchEmptyUrl).toHaveBeenCalled();
      expect(window.fetchEmptyUrl).toHaveBeenCalledWith();

    })
  })

  describe('processData', function () {
    it('should list all male and female pets grouped by owners gender', function () {
      const result = catApp.processData(data)
      expect(result).toEqual(jasmine.any(Object))
      expect(Object.keys(result)).toEqual([
        'male',
        'female',
      ]);
      expect(result.male.length).toBeGreaterThan(1);
      expect(result.female.length).toBeGreaterThan(1);
    })

    it('should return if data is undefined', function () {
      const result = catApp.processData(undefined)
      expect(result).toBe(undefined)
    })

    it('should filter data and group by gender type', function () {
      const result = data.filter(subitem => subitem.gender === 'Male')
      expect(result[0].gender).toEqual('Male')
    })

  })

  describe('getCatsSortedByName', function () {
    it('should filter all cats from pets', function () {
      const result = catApp.getCatsSortedByName([{
        "name": "Bob",
        "gender": "Male",
        "age": 23,
        "pets": [{
          "name": "Garfield",
          "type": "Cat"
        }, {
          "name": "Fido",
          "type": "Dog"
        }]
      }, {
        "name": "Jennifer",
        "gender": "Female",
        "age": 18,
        "pets": [{
          "name": "Garfield",
          "type": "Cat"
        }]
      }
      ])
      expect(result).toEqual(jasmine.any(Object))
      expect(result.length).toBeGreaterThan(1)
    })

    it('should sort all cats by their name', function () {
      const catList = [
        {
          "name": "Garfield",
          "type": "Cat"
        },
        {
          "name": "Max",
          "type": "Cat"
        },
        {
          "name": "Jim",
          "type": "Cat"
        }
      ]
      const result = catList.sort(function (a, b) { return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0); })
      expect(result).toEqual(jasmine.any(Object))
      expect(result.length).toBeGreaterThan(1)
    })

    it('should return an empty array if data is undefined', function () {
      const result = catApp.getCatsSortedByName(undefined)
      expect(result).toEqual(jasmine.any(Object))
    })

  })

  describe('renderList', function () {

    it('should return if data is undefined', function () {
      const result = catApp.renderList(undefined)
      expect(result).toEqual(undefined)
    })
  })

  describe('drawPets', function () {
    it('should render output to the target node', function () {
      const result = catApp.drawPets([
        {
          "name": "Garfield",
          "type": "Cat"
        },
        {
          "name": "Tom",
          "type": "Cat"
        }
      ], 'MALE')

      expect(result).toBeDefined();
      expect(result).toEqual(jasmine.any(Object));
      expect(result.length).toBeGreaterThan(1);
      expect(result[0]).toEqual('
MALE
')
      expect(result[1]).toEqual('
')
      expect(result[2]).toEqual(['
Garfield
'])
      expect(result[3]).toEqual(['
Tom
'])
      expect(result[4]).toEqual('
')
    })

    it('should return "No Cats For Male" message if data is undefined', function () {
      const result = catApp.drawPets(undefined, 'Male')

      expect(result).toEqual(jasmine.any(Object))
      expect(result[0]).toEqual('
No Cats For Male
')
    })

  })

});
<|endoftext|>


<commit_before>/**
 * jQuery DOMEC (DOM Elements Creator) 0.3.4
 * http://code.google.com/p/jquery-domec
 * http://plugins.jquery.com/project/DOMEC
 *
 * Copyright (c) 2008 Lukasz Rajchel (lukasz@rajchel.pl | http://lukasz.rajchel.pl)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Documentation	:	http://code.google.com/p/jquery-domec/wiki/Documentation
 * Changelog:		:	http://code.google.com/p/jquery-domec/wiki/Changelog
 */

 /*global document, jQuery*/
(function ($) {

 	// register jQuery extension
	$.extend({
		create: function (element, attributes, children, root) {

			if (typeof(root) === 'undefined') {
				root = document;
			}

			// define variables
			var key, i, elem;

			//create new element
			elem = $(root.createElement(element));

			// add passed attributes
			if (typeof(attributes) === 'object') {
				for (key in attributes) {
					if (typeof(attributes[key]) === 'string') {
						elem.attr(key, attributes[key]);
					}
				}
			}

			// add passed child elements
			if (children !== null) {
				if (typeof(children) === 'object') {
					for (i = 0; i < children.length; i += 1) {
						elem.append(children[i]);
					}
				} else if (typeof(children) !== 'undefined') {
					elem.text(children.toString());
				}
			}

			return elem;
		}
	});

})(jQuery);<commit_msg>duplicated file removed<commit_after><|endoftext|>


<commit_before>import { React, PropTypes, styled, observer, css } from 'vendor';
import { StickyTable, Row } from 'react-sticky-table';
import moment from 'moment';
import { isNil } from 'lodash';
import { OverlayTrigger, Popover } from 'react-bootstrap';
import { Icon } from 'shared';
import { colors } from 'theme';
import S from '../../helpers/string';
import SortIcon from '../../components/icons/sort';
import TutorLink from '../../components/link';
import TaskResultCell from './task-result-cell';
import AggregateResult from './aggregate-result-cell';
import MinMaxResult, { TYPE as MinMaxType } from './min-max-result-cell';
import { getCell } from './styles';
import AverageInfoModal from './average-info-modal';
import SetWeightsModal from './set-weights-modal';

const StyledStickyTable = styled(StickyTable)`
  margin: 2.2rem 0 1.4rem;

  .sticky-table-row:last-child .sticky-table-cell {
    border-bottom: 1px solid ${colors.neutral.pale};
  }
`;

const Cell = getCell('0');

const centeredCSS = css`
  display: flex;
  justify-content: center;
  align-items: center;
`;

const headingCSS = css`
  height: 100%;
`;

const paddingCSS = css`
  padding: 1rem;
`;

const CellContents = styled.div`
  ${centeredCSS}
  > * { width: 80px; }
  > *:first-child {
    width: 23rem;
  }
  > *:last-child {
    width: 15rem;
  }
`;

const Heading = styled.div`
  ${props => !props.first && centeredCSS}
  ${props => props.first && css`
    border-right: 1px solid ${colors.neutral.pale};
  `}
  ${headingCSS}
  ${paddingCSS}
`;

const HeadingTop = styled.div`
  ${paddingCSS}
  padding-top: 1.2rem;
  align-self: stretch;
  font-weight: bold;
  letter-spacing: 0.1rem;

  & .info-circle-icon-button {
    color: ${colors.bright_blue};
    display: block;
    margin-bottom: -2px;
    margin-top: 0.5px;
  }

  & .heading-title {
    white-space: nowrap;
    width: 70px;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  & .sort-wrapper {
    cursor: pointer;
  }
`;

const HeadingMiddle = styled.div`
  ${paddingCSS}
  align-self: stretch;
  padding-top: 0;
  font-size: 1rem;
  color: ${colors.neutral.thin};
  border-bottom: 1px solid ${colors.neutral.pale};

  & .set-weight-span {
    cursor: pointer;
    color: ${colors.link};
  }

  & .invert-name-icon-button {
    color: ${colors.link};
    font-size: 14px;
    display: inline-flex;
  }
`;

const HeadingBottom = styled.div`
  padding: 0.5rem 1rem;
  align-self: stretch;
  font-size: 1.2rem;
  background: #fff;
  position: relative;
  color: ${colors.neutral.thin};

  .available-points {
    margin-top: 0.5px;
  }
`;

const ColumnHeading = styled.div`
  ${headingCSS}
  background: ${props =>
    !props.variant
      ? colors.neutral.lighter
      // gradebook can only have homework, reading, and external
      : props.variant === 'homework'
        ? colors.templates.homework.background
        : props.variant === 'reading'
          ? colors.templates.reading.background
          : colors.templates.external.background};
  border-top: 0.4rem solid ${props =>
    !props.variant
      ? colors.neutral.std
      // gradebook can only have homework, reading, and external
      : props.variant === 'homework'
        ? colors.templates.homework.border
        : props.variant === 'reading'
          ? colors.templates.reading.border
          : colors.templates.external.border};
  &:not(:last-child) {
    border-right: 1px solid ${colors.neutral.pale};
  }
  > * {
    ${props => !props.first && css`
      ${centeredCSS}
    `}
  }
  border-bottom: 1rem solid ${colors.neutral.pale};
`;

const SplitCell = styled.div`
  ${centeredCSS}
  flex: 1.0;
  ${props => props.border && css`
    border-right: 1px solid ${colors.neutral.pale};
  `}
  ${props => props.onClick && css`
      cursor: pointer;
    `}
`;

const Average = styled.div`
  padding: 0;
  ${centeredCSS}
  align-self: stretch;
  position: relative;
`;

const Total = styled.div`
  padding: 0;
  align-self: stretch;
  border-right: 1px solid  ${colors.neutral.pale};
  ${centeredCSS}
`;

const FirstRowCell = styled.div`
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: left;
  padding-left: 15px;
`;

const DroppedNote = styled.div`
  font-size: 1.5rem;
  font-style: italic;
  font-family: serif;
`;

// Overriding the default 1px
const StyledStudentCell = styled(Cell)`
    ${props => props.drawBorderBottom && css`
    border-bottom: 2px solid ${colors.neutral.pale};
  `}
`;

const StudentColumnHeader = observer(({ ux }) => {

  return (
    <Cell>
      <CellContents>
        <ColumnHeading
          first={true}
          data-test-id="student-name-heading"
        >
          <HeadingTop
            onClick={() => ux.changeRowSortingOrder(ux.isNameInverted ? 'last_name' : 'first_name', 'score')}
          >
            Student Name
            <SortIcon className="sort-name" sort={ux.sortForColumn(ux.isNameInverted ? 'last_name' : 'first_name', 'score')} />
          </HeadingTop>
          <HeadingMiddle>
            {ux.isNameInverted ? 'Lastname, Firstname' : 'Firstname, Lastname'}
            <Icon type="exchange-alt"
              className="invert-name-icon-button"
              onClick={() => ux.isNameInverted = !ux.isNameInverted}
            />
          </HeadingMiddle>
          <HeadingBottom>

Available Points

          </HeadingBottom>
        </ColumnHeading>
        <ColumnHeading>
          <HeadingTop
            onClick={() => ux.changeRowSortingOrder('course_average', 'score')}>
            Total
            <SortIcon
              sort={ux.sortForColumn('course_average', 'score')}
            />
          </HeadingTop>
          <HeadingMiddle>
             ux.weights.showWeights()}>Set Weight
          </HeadingMiddle>
          <HeadingBottom>
            {ux.weights.total}%
          </HeadingBottom>
        </ColumnHeading>
        <ColumnHeading>
          <HeadingTop>
            Averages <Icon
              type="info-circle"
              className="info-circle-icon-button"
              onClick={() => ux.showAverageInfo()}
            />
          </HeadingTop>
          <HeadingMiddle>
            <SplitCell
              border
              onClick={() => ux.changeRowSortingOrder('homework_score', 'score')}
            >
              Homework
              <SortIcon
                sort={ux.sortForColumn('homework_score', 'score')}
              />
            </SplitCell>
            <SplitCell
              onClick={() => ux.changeRowSortingOrder('reading_score', 'score')}
            >
              Reading
              <SortIcon
                sort={ux.sortForColumn('reading_score', 'score')}
              />
            </SplitCell>
          </HeadingMiddle>
          <HeadingBottom>
            <SplitCell border>
              {`${ux.weights.ux_homework_weight}%`}
            </SplitCell>
            <SplitCell>
              {`${ux.weights.ux_reading_weight}%`}
            </SplitCell>
          </HeadingBottom>
        </ColumnHeading>
      </CellContents>
    </Cell>
  );
});

const AssignmentHeading = observer(({ ux, heading, sortKey }) => {
  const onClick = () => ux.changeRowSortingOrder(sortKey, 'score');
  return (
    // Overlay has a lot of problems when showing at the top. Putting at the bottom for now
    <OverlayTrigger
      placement="bottom"
      trigger="hover"
      overlay={
        <Popover className="gradebook-popover">

{heading.title}

        </Popover>}
    >
      <Cell>
        <ColumnHeading variant={heading.type}>
          <HeadingTop>

              <TutorLink
                to={'reviewAssignment'}
                params={{
                  courseId: ux.course.id,
                  id: heading.plan_id,
                }}
              >
                {heading.title}
              </TutorLink>

<SortIcon sort={ux.sortForColumn(sortKey, 'score')} />

          </HeadingTop>
          <HeadingMiddle>
            {moment(heading.due_at).format('MMM D')}
          </HeadingMiddle>
          <HeadingBottom>
            {false && <CornerTriangle color="blue" tooltip="Dropped" />}
            {S.numberWithOneDecimalPlace(heading.available_points)}
          </HeadingBottom>
        </ColumnHeading>
      </Cell>
    </OverlayTrigger>

  );
});

const percentOrDash = (score) => isNil(score) ? '--' : S.asPercent(score) + '%';

const StudentCell = observer(({ ux, student, striped, isLast }) => {
  return (

  );
});

const GradebookTable = observer(({ ux }) => {
  return (
    <>

      {ux.hasDroppedStudents && <DroppedNote>* Dropped students' scores are not included in the overall course averages</DroppedNote>}
      <AverageInfoModal ux={ux} />
      <SetWeightsModal ux={ux} />
    </>
  );
});
GradebookTable.propTypes = {
  ux: PropTypes.object.isRequired,
};

export default GradebookTable;
<commit_msg>change to a black color for the assignment heading title in teacher gradebook<commit_after>import { React, PropTypes, styled, observer, css } from 'vendor';
import { StickyTable, Row } from 'react-sticky-table';
import moment from 'moment';
import { isNil } from 'lodash';
import { OverlayTrigger, Popover } from 'react-bootstrap';
import { Icon } from 'shared';
import { colors } from 'theme';
import S from '../../helpers/string';
import SortIcon from '../../components/icons/sort';
import TutorLink from '../../components/link';
import TaskResultCell from './task-result-cell';
import AggregateResult from './aggregate-result-cell';
import MinMaxResult, { TYPE as MinMaxType } from './min-max-result-cell';
import { getCell } from './styles';
import AverageInfoModal from './average-info-modal';
import SetWeightsModal from './set-weights-modal';

const StyledStickyTable = styled(StickyTable)`
  margin: 2.2rem 0 1.4rem;

  .sticky-table-row:last-child .sticky-table-cell {
    border-bottom: 1px solid ${colors.neutral.pale};
  }
`;

const Cell = getCell('0');

const centeredCSS = css`
  display: flex;
  justify-content: center;
  align-items: center;
`;

const headingCSS = css`
  height: 100%;
`;

const paddingCSS = css`
  padding: 1rem;
`;

const CellContents = styled.div`
  ${centeredCSS}
  > * { width: 80px; }
  > *:first-child {
    width: 23rem;
  }
  > *:last-child {
    width: 15rem;
  }
`;

const Heading = styled.div`
  ${props => !props.first && centeredCSS}
  ${props => props.first && css`
    border-right: 1px solid ${colors.neutral.pale};
  `}
  ${headingCSS}
  ${paddingCSS}
`;

const HeadingTop = styled.div`
  ${paddingCSS}
  padding-top: 1.2rem;
  align-self: stretch;
  font-weight: bold;
  letter-spacing: 0.1rem;

  & .info-circle-icon-button {
    color: ${colors.bright_blue};
    display: block;
    margin-bottom: -2px;
    margin-top: 0.5px;
  }

  & .heading-title {
    white-space: nowrap;
    width: 70px;
    overflow: hidden;
    text-overflow: ellipsis;

    & a {
      color: inherit;
    }
  }

  & .sort-wrapper {
    cursor: pointer;
  }
`;

const HeadingMiddle = styled.div`
  ${paddingCSS}
  align-self: stretch;
  padding-top: 0;
  font-size: 1rem;
  color: ${colors.neutral.thin};
  border-bottom: 1px solid ${colors.neutral.pale};

  & .set-weight-span {
    cursor: pointer;
    color: ${colors.link};
  }

  & .invert-name-icon-button {
    color: ${colors.link};
    font-size: 14px;
    display: inline-flex;
  }
`;

const HeadingBottom = styled.div`
  padding: 0.5rem 1rem;
  align-self: stretch;
  font-size: 1.2rem;
  background: #fff;
  position: relative;
  color: ${colors.neutral.thin};

  .available-points {
    margin-top: 0.5px;
  }
`;

const ColumnHeading = styled.div`
  ${headingCSS}
  background: ${props =>
    !props.variant
      ? colors.neutral.lighter
      // gradebook can only have homework, reading, and external
      : props.variant === 'homework'
        ? colors.templates.homework.background
        : props.variant === 'reading'
          ? colors.templates.reading.background
          : colors.templates.external.background};
  border-top: 0.4rem solid ${props =>
    !props.variant
      ? colors.neutral.std
      // gradebook can only have homework, reading, and external
      : props.variant === 'homework'
        ? colors.templates.homework.border
        : props.variant === 'reading'
          ? colors.templates.reading.border
          : colors.templates.external.border};
  &:not(:last-child) {
    border-right: 1px solid ${colors.neutral.pale};
  }
  > * {
    ${props => !props.first && css`
      ${centeredCSS}
    `}
  }
  border-bottom: 1rem solid ${colors.neutral.pale};
`;

const SplitCell = styled.div`
  ${centeredCSS}
  flex: 1.0;
  ${props => props.border && css`
    border-right: 1px solid ${colors.neutral.pale};
  `}
  ${props => props.onClick && css`
      cursor: pointer;
    `}
`;

const Average = styled.div`
  padding: 0;
  ${centeredCSS}
  align-self: stretch;
  position: relative;
`;

const Total = styled.div`
  padding: 0;
  align-self: stretch;
  border-right: 1px solid  ${colors.neutral.pale};
  ${centeredCSS}
`;

const FirstRowCell = styled.div`
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: left;
  padding-left: 15px;
`;

const DroppedNote = styled.div`
  font-size: 1.5rem;
  font-style: italic;
  font-family: serif;
`;

// Overriding the default 1px
const StyledStudentCell = styled(Cell)`
    ${props => props.drawBorderBottom && css`
    border-bottom: 2px solid ${colors.neutral.pale};
  `}
`;

const StudentColumnHeader = observer(({ ux }) => {

  return (
    <Cell>
      <CellContents>
        <ColumnHeading
          first={true}
          data-test-id="student-name-heading"
        >
          <HeadingTop
            onClick={() => ux.changeRowSortingOrder(ux.isNameInverted ? 'last_name' : 'first_name', 'score')}
          >
            Student Name
            <SortIcon className="sort-name" sort={ux.sortForColumn(ux.isNameInverted ? 'last_name' : 'first_name', 'score')} />
          </HeadingTop>
          <HeadingMiddle>
            {ux.isNameInverted ? 'Lastname, Firstname' : 'Firstname, Lastname'}
            <Icon type="exchange-alt"
              className="invert-name-icon-button"
              onClick={() => ux.isNameInverted = !ux.isNameInverted}
            />
          </HeadingMiddle>
          <HeadingBottom>

Available Points

          </HeadingBottom>
        </ColumnHeading>
        <ColumnHeading>
          <HeadingTop
            onClick={() => ux.changeRowSortingOrder('course_average', 'score')}>
            Total
            <SortIcon
              sort={ux.sortForColumn('course_average', 'score')}
            />
          </HeadingTop>
          <HeadingMiddle>
             ux.weights.showWeights()}>Set Weight
          </HeadingMiddle>
          <HeadingBottom>
            {ux.weights.total}%
          </HeadingBottom>
        </ColumnHeading>
        <ColumnHeading>
          <HeadingTop>
            Averages <Icon
              type="info-circle"
              className="info-circle-icon-button"
              onClick={() => ux.showAverageInfo()}
            />
          </HeadingTop>
          <HeadingMiddle>
            <SplitCell
              border
              onClick={() => ux.changeRowSortingOrder('homework_score', 'score')}
            >
              Homework
              <SortIcon
                sort={ux.sortForColumn('homework_score', 'score')}
              />
            </SplitCell>
            <SplitCell
              onClick={() => ux.changeRowSortingOrder('reading_score', 'score')}
            >
              Reading
              <SortIcon
                sort={ux.sortForColumn('reading_score', 'score')}
              />
            </SplitCell>
          </HeadingMiddle>
          <HeadingBottom>
            <SplitCell border>
              {`${ux.weights.ux_homework_weight}%`}
            </SplitCell>
            <SplitCell>
              {`${ux.weights.ux_reading_weight}%`}
            </SplitCell>
          </HeadingBottom>
        </ColumnHeading>
      </CellContents>
    </Cell>
  );
});

const AssignmentHeading = observer(({ ux, heading, sortKey }) => {
  const onClick = () => ux.changeRowSortingOrder(sortKey, 'score');
  return (
    // Overlay has a lot of problems when showing at the top. Putting at the bottom for now
    <OverlayTrigger
      placement="bottom"
      trigger="hover"
      overlay={
        <Popover className="gradebook-popover">

{heading.title}

        </Popover>}
    >
      <Cell>
        <ColumnHeading variant={heading.type}>
          <HeadingTop>

              <TutorLink
                to={'reviewAssignment'}
                params={{
                  courseId: ux.course.id,
                  id: heading.plan_id,
                }}
              >
                {heading.title}
              </TutorLink>

<SortIcon sort={ux.sortForColumn(sortKey, 'score')} />

          </HeadingTop>
          <HeadingMiddle>
            {moment(heading.due_at).format('MMM D')}
          </HeadingMiddle>
          <HeadingBottom>
            {false && <CornerTriangle color="blue" tooltip="Dropped" />}
            {S.numberWithOneDecimalPlace(heading.available_points)}
          </HeadingBottom>
        </ColumnHeading>
      </Cell>
    </OverlayTrigger>

  );
});

const percentOrDash = (score) => isNil(score) ? '--' : S.asPercent(score) + '%';

const StudentCell = observer(({ ux, student, striped, isLast }) => {
  return (

  );
});

const GradebookTable = observer(({ ux }) => {
  return (
    <>

      {ux.hasDroppedStudents && <DroppedNote>* Dropped students' scores are not included in the overall course averages</DroppedNote>}
      <AverageInfoModal ux={ux} />
      <SetWeightsModal ux={ux} />
    </>
  );
});
GradebookTable.propTypes = {
  ux: PropTypes.object.isRequired,
};

export default GradebookTable;
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react'

import FieldListItem from './FieldListItem'
import GroupByTimeDropdown from './GroupByTimeDropdown'

import {showFieldKeys} from 'shared/apis/metaQuery'
import showFieldKeysParser from 'shared/parsing/showFieldKeys'

const {string, shape, func} = PropTypes
const FieldList = React.createClass({
  propTypes: {
    query: shape({
      database: string,
      retentionPolicy: string,
      measurement: string,
    }).isRequired,
    onToggleField: func.isRequired,
    onGroupByTime: func.isRequired,
    applyFuncsToField: func.isRequired,
    isKapacitorRule: PropTypes.bool,
  },

  getDefaultProps() {
    return {
      isKapacitorRule: false,
    }
  },

  contextTypes: {
    source: PropTypes.shape({
      links: PropTypes.shape({
        proxy: PropTypes.string.isRequired,
      }).isRequired,
    }).isRequired,
  },

  getInitialState() {
    return {
      fields: [],
    }
  },

  componentDidMount() {
    const {database, measurement} = this.props.query
    if (!database || !measurement) {
      return
    }

    this._getFields()
  },

  componentDidUpdate(prevProps) {
    const {database, measurement, retentionPolicy} = this.props.query
    const {database: prevDB, measurement: prevMeas, retentionPolicy: prevRP} = prevProps.query
    if (!database || !measurement) {
      return
    }

    if (database === prevDB && measurement === prevMeas && retentionPolicy === prevRP) {
      return
    }

    this._getFields()
  },

  handleGroupByTime(groupBy) {
    this.props.onGroupByTime(groupBy.menuOption)
  },

  render() {
    const {query} = this.props
    const hasAggregates = query.fields.some((f) => f.funcs && f.funcs.length)
    const hasGroupByTime = query.groupBy.time

    return (

Fields

        {
          hasAggregates ?

Group by Time

                <GroupByTimeDropdown isOpen={!hasGroupByTime} selected={query.groupBy.time} onChooseGroupByTime={this.handleGroupByTime} />

            : null
        }
        {this.renderList()}

    )
  },

  renderList() {
    const {database, measurement} = this.props.query
    if (!database || !measurement) {
      return (

          No Measurement selected

      )
    }

    return (

        {this.state.fields.map((fieldFunc) => {
          const selectedField = this.props.query.fields.find((f) => f.field === fieldFunc.field)
          return (
            <FieldListItem
              key={fieldFunc.field}
              onToggleField={this.props.onToggleField}
              onApplyFuncsToField={this.props.applyFuncsToField}
              isSelected={!!selectedField}
              fieldFunc={selectedField || fieldFunc}
              isKapacitorRule={this.props.isKapacitorRule}
            />
          )
        })}

    )
  },

  _getFields() {
    const {database, measurement, retentionPolicy} = this.props.query
    const {source} = this.context
    const proxySource = source.links.proxy

    showFieldKeys(proxySource, database, measurement, retentionPolicy).then((resp) => {
      const {errors, fieldSets} = showFieldKeysParser(resp.data)
      if (errors.length) {
        // TODO: do something
      }

      this.setState({
        fields: fieldSets[measurement].map((f) => {
          return {field: f, funcs: []}
        }),
      })
    })
  },
})

export default FieldList
<commit_msg>Update appearance of group by time in field list heading<commit_after>import React, {PropTypes} from 'react'

import FieldListItem from './FieldListItem'
import GroupByTimeDropdown from './GroupByTimeDropdown'

import {showFieldKeys} from 'shared/apis/metaQuery'
import showFieldKeysParser from 'shared/parsing/showFieldKeys'

const {string, shape, func} = PropTypes
const FieldList = React.createClass({
  propTypes: {
    query: shape({
      database: string,
      retentionPolicy: string,
      measurement: string,
    }).isRequired,
    onToggleField: func.isRequired,
    onGroupByTime: func.isRequired,
    applyFuncsToField: func.isRequired,
    isKapacitorRule: PropTypes.bool,
  },

  getDefaultProps() {
    return {
      isKapacitorRule: false,
    }
  },

  contextTypes: {
    source: PropTypes.shape({
      links: PropTypes.shape({
        proxy: PropTypes.string.isRequired,
      }).isRequired,
    }).isRequired,
  },

  getInitialState() {
    return {
      fields: [],
    }
  },

  componentDidMount() {
    const {database, measurement} = this.props.query
    if (!database || !measurement) {
      return
    }

    this._getFields()
  },

  componentDidUpdate(prevProps) {
    const {database, measurement, retentionPolicy} = this.props.query
    const {database: prevDB, measurement: prevMeas, retentionPolicy: prevRP} = prevProps.query
    if (!database || !measurement) {
      return
    }

    if (database === prevDB && measurement === prevMeas && retentionPolicy === prevRP) {
      return
    }

    this._getFields()
  },

  handleGroupByTime(groupBy) {
    this.props.onGroupByTime(groupBy.menuOption)
  },

  render() {
    const {query} = this.props
    const hasAggregates = query.fields.some((f) => f.funcs && f.funcs.length)
    const hasGroupByTime = query.groupBy.time

    return (

          Fields
          {hasAggregates ?

Group by Time

              <GroupByTimeDropdown isOpen={!hasGroupByTime} selected={query.groupBy.time} onChooseGroupByTime={this.handleGroupByTime} />

          : null}

        {this.renderList()}

    )
  },

  renderList() {
    const {database, measurement} = this.props.query
    if (!database || !measurement) {
      return (

          No Measurement selected

      )
    }

    return (

        {this.state.fields.map((fieldFunc) => {
          const selectedField = this.props.query.fields.find((f) => f.field === fieldFunc.field)
          return (
            <FieldListItem
              key={fieldFunc.field}
              onToggleField={this.props.onToggleField}
              onApplyFuncsToField={this.props.applyFuncsToField}
              isSelected={!!selectedField}
              fieldFunc={selectedField || fieldFunc}
              isKapacitorRule={this.props.isKapacitorRule}
            />
          )
        })}

    )
  },

  _getFields() {
    const {database, measurement, retentionPolicy} = this.props.query
    const {source} = this.context
    const proxySource = source.links.proxy

    showFieldKeys(proxySource, database, measurement, retentionPolicy).then((resp) => {
      const {errors, fieldSets} = showFieldKeysParser(resp.data)
      if (errors.length) {
        // TODO: do something
      }

      this.setState({
        fields: fieldSets[measurement].map((f) => {
          return {field: f, funcs: []}
        }),
      })
    })
  },
})

export default FieldList
<|endoftext|>


<commit_before>/*
---

name: Scroller.Engine.Native

description: Provides a native scroller engine.

license: MIT-style license.

authors:
	- Jean-Philippe Dery (jeanphilippe.dery@gmail.com)

requires:
	- Scroller.Engine

provides:
	- Scroller.Engine.Native

...
*/

/**
 * @name  Scroller.Engine.Native
 * @class Provides an engine that uses the native scroller overflow.
 *
 * @classdesc
 *
 * [TODO: Introduction]
 * [TODO: Events]
 * [TODO: Options]
 *
 * @author  Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
 * @version 0.1
 */
Moobile.Scroller.Engine.Native = new Class( /** @lends Scroller.Engine.Native.prototype */ {

	Extends: Moobile.Scroller.Engine,

	scroller: null,

	scrolling: false,

	/**
	 * Initializes this scroller engine.
	 *
	 * This method will creates a `wrapper` element and wrap it around the
	 * given `content` element.
	 *
	 * @param {Element}	[content] The Element, element id or string.
	 * @param {Object}  [options] The options.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	initialize: function(content, options) {

		this.parent(content, options);

		this.wrapper.addClass('scroll-engine-native');

		if (this.options.scrollX)  this.wrapper.setStyle('overflow-x', 'scroll');
		if (this.options.scrollY)  this.wrapper.setStyle('overflow-y', 'scroll');
		if (this.options.momentum) this.wrapper.setStyle('-webkit-overflow-scrolling', 'touch');

		this.scroller = new Fx.Scroll(this.wrapper);

		this.wrapper.addEvent('touchstart', this.bound('onTouchStart'));
		this.wrapper.addEvent('touchend', this.bound('onTouchEnd'));
		this.wrapper.addEvent('scroll', this.bound('onScroll'));

		window.addEvent('rotate', this.bound('onWindowRotate'));

		return this;
	},

	/**
	 * Destroys this scroller engine.
	 *
	 * This method will remove the wrapper without removing the content
	 * element.
	 *
	 * @return {Scroller.Engine} This scroller engine.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	destroy: function() {
		window.removeEvent('rotate', this.bound('onWindowRotate'));
		this.scroller = null;
		this.parent();
	},

	/**
	 * Scrolls to a set of coordinates.
	 *
	 * @param {Number} x      The x coordinate.
	 * @param {Number} y      The y coordinate.
	 * @param {Number} [time] The duration of the scroll.
	 *
	 * @return {Scroller.Engine} This scroller engine.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	scrollTo: function(x, y, time) {

		time = time || 0;

		this.scroller.setOptions({duration: time});
		this.scroller.start(x, y);
		// TODO: proper events
		return this;
	},

	/**
	 * Scrolls to an element.
	 *
	 * @param {Element} element The element to scroll to.
	 * @param {Number}  [time]  The duration of the scroll.
	 *
	 * @return {Scroller.Engine} This scroller engine.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	scrollToElement: function(element, time) {

		time = time || 0;

		element = document.id(element);

		this.scroller.setOptions({duration: time});
		this.scroller.toElement(element);

		return this;
	},

	/**
	 * Refreshes this scroller engine.
	 *
	 * @return {Scroller} This scroller.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	refresh: function() {

		var wrapperSize = this.getSize();
		var contentSize = this.getScrollSize();

		if (contentSize.y <= wrapperSize.y) {
			this.content.setStyle('min-height', wrapperSize.y + 2);
		}

		return this;
	},

	/**
	 * Returns the size.
	 *
	 * This method will return the wrapper's size as an object with two keys,
	 * `x` which indicates the width and `y` which indicates the height.
	 *
	 * @return {Object} The size.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	getSize: function() {
		return this.wrapper.getSize();
	},

	/**
	 * Returns the current scroll position.
	 *
	 * This method will return the current scroll position as an object
	 * with two keys, `x` which indicates the horizontal scroll and `y` which
	 * indicates the vertical scroll of this entity.
	 *
	 * @return {Object} The scroll position.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	getScroll: function() {
		return this.wrapper.getScroll();
	},

	/**
	 * Returns size including the scrolling area.
	 *
	 * This method will return the content's size as an object with two keys,
	 * `x` which indicates the width and `y` which indicates the height.
	 *
	 * @return {Object} The size including the scrolling area.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	getScrollSize: function() {
		return this.content.getScrollSize();
	},

	onTouchStart: function() {

		var scroll = this.wrapper.getScroll();
		if (scroll.x == 0) {

			if (scroll.y <= 0) {

				// offset scroll top
				this.wrapper.scrollTo(0, 1);

			} else {

				// offset scroll bottom
				var totalSize = this.wrapper.getScrollSize();
				var frameSize = this.wrapper.getSize();
				if (frameSize.y + scroll.y >= totalSize.y) {
					this.wrapper.scrollTo(0, scroll.y - 1);
				}
			}
		}

		this.fireEvent('dragstart');
	},

	onTouchEnd: function() {
		this.fireEvent('dragend');
	},

	onScroll: function() {
		this.fireEvent('scroll');
	},

	onWindowRotate: function(e) {
		this.refresh();
	}

});

Moobile.Scroller.Engine.Native.supportsCurrentPlatform = function() {
	// TODO: Improve me, a lot.
	return /CPU OS 5_0/.test(navigator.userAgent);
};
<commit_msg>Improved the detection of native scrolling<commit_after>/*
---

name: Scroller.Engine.Native

description: Provides a native scroller engine.

license: MIT-style license.

authors:
	- Jean-Philippe Dery (jeanphilippe.dery@gmail.com)

requires:
	- Scroller.Engine

provides:
	- Scroller.Engine.Native

...
*/

/**
 * @name  Scroller.Engine.Native
 * @class Provides an engine that uses the native scroller overflow.
 *
 * @classdesc
 *
 * [TODO: Introduction]
 * [TODO: Events]
 * [TODO: Options]
 *
 * @author  Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
 * @version 0.1
 */
Moobile.Scroller.Engine.Native = new Class( /** @lends Scroller.Engine.Native.prototype */ {

	Extends: Moobile.Scroller.Engine,

	scroller: null,

	scrolling: false,

	/**
	 * Initializes this scroller engine.
	 *
	 * This method will creates a `wrapper` element and wrap it around the
	 * given `content` element.
	 *
	 * @param {Element}	[content] The Element, element id or string.
	 * @param {Object}  [options] The options.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	initialize: function(content, options) {

		this.parent(content, options);

		this.wrapper.addClass('scroll-engine-native');

		if (this.options.scrollX)  this.wrapper.setStyle('overflow-x', 'scroll');
		if (this.options.scrollY)  this.wrapper.setStyle('overflow-y', 'scroll');
		if (this.options.momentum) this.wrapper.setStyle('-webkit-overflow-scrolling', 'touch');

		this.scroller = new Fx.Scroll(this.wrapper);

		this.wrapper.addEvent('touchstart', this.bound('onTouchStart'));
		this.wrapper.addEvent('touchend', this.bound('onTouchEnd'));
		this.wrapper.addEvent('scroll', this.bound('onScroll'));

		window.addEvent('rotate', this.bound('onWindowRotate'));

		return this;
	},

	/**
	 * Destroys this scroller engine.
	 *
	 * This method will remove the wrapper without removing the content
	 * element.
	 *
	 * @return {Scroller.Engine} This scroller engine.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	destroy: function() {
		window.removeEvent('rotate', this.bound('onWindowRotate'));
		this.scroller = null;
		this.parent();
	},

	/**
	 * Scrolls to a set of coordinates.
	 *
	 * @param {Number} x      The x coordinate.
	 * @param {Number} y      The y coordinate.
	 * @param {Number} [time] The duration of the scroll.
	 *
	 * @return {Scroller.Engine} This scroller engine.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	scrollTo: function(x, y, time) {

		time = time || 0;

		this.scroller.setOptions({duration: time});
		this.scroller.start(x, y);
		// TODO: proper events
		return this;
	},

	/**
	 * Scrolls to an element.
	 *
	 * @param {Element} element The element to scroll to.
	 * @param {Number}  [time]  The duration of the scroll.
	 *
	 * @return {Scroller.Engine} This scroller engine.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	scrollToElement: function(element, time) {

		time = time || 0;

		element = document.id(element);

		this.scroller.setOptions({duration: time});
		this.scroller.toElement(element);

		return this;
	},

	/**
	 * Refreshes this scroller engine.
	 *
	 * @return {Scroller} This scroller.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	refresh: function() {

		var wrapperSize = this.getSize();
		var contentSize = this.getScrollSize();

		if (contentSize.y <= wrapperSize.y) {
			this.content.setStyle('min-height', wrapperSize.y + 2);
		}

		return this;
	},

	/**
	 * Returns the size.
	 *
	 * This method will return the wrapper's size as an object with two keys,
	 * `x` which indicates the width and `y` which indicates the height.
	 *
	 * @return {Object} The size.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	getSize: function() {
		return this.wrapper.getSize();
	},

	/**
	 * Returns the current scroll position.
	 *
	 * This method will return the current scroll position as an object
	 * with two keys, `x` which indicates the horizontal scroll and `y` which
	 * indicates the vertical scroll of this entity.
	 *
	 * @return {Object} The scroll position.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	getScroll: function() {
		return this.wrapper.getScroll();
	},

	/**
	 * Returns size including the scrolling area.
	 *
	 * This method will return the content's size as an object with two keys,
	 * `x` which indicates the width and `y` which indicates the height.
	 *
	 * @return {Object} The size including the scrolling area.
	 *
	 * @author Jean-Philippe Dery (jeanphilippe.dery@gmail.com)
	 * @since  0.1
	 */
	getScrollSize: function() {
		return this.content.getScrollSize();
	},

	onTouchStart: function() {

		var scroll = this.wrapper.getScroll();
		if (scroll.x == 0) {

			if (scroll.y <= 0) {

				// offset scroll top
				this.wrapper.scrollTo(0, 1);

			} else {

				// offset scroll bottom
				var totalSize = this.wrapper.getScrollSize();
				var frameSize = this.wrapper.getSize();
				if (frameSize.y + scroll.y >= totalSize.y) {
					this.wrapper.scrollTo(0, scroll.y - 1);
				}
			}
		}

		this.fireEvent('dragstart');
	},

	onTouchEnd: function() {
		this.fireEvent('dragend');
	},

	onScroll: function() {
		this.fireEvent('scroll');
	},

	onWindowRotate: function(e) {
		this.refresh();
	}

});

Moobile.Scroller.Engine.Native.supportsCurrentPlatform = function() {
	return 'WebkitOverflowScrolling' in new Element('div').style;
};
<|endoftext|>


<commit_before>//Extension to Ensembl.Panel.Masthead to add some dynamic behaviour to account links

Ensembl.Panel.Masthead = Ensembl.Panel.Masthead.extend({
  constructor: function (id) {
    this.base(id);

    Ensembl.EventManager.register('refreshAccountsDropdown', this, this.refreshAccountsDropdown);
  },

  init: function () {
    this.base();

    this.elLk.accountHolder   = this.el.find('div._account_holder');

    this.accountsRefreshURL   = '';
    this.accountsBookmarkData = '';

    this.refreshAccountsDropdown();
  },

  refreshAccountsDropdown: function() {
    var panel = this;

    if (this.elLk.accountHolder.length && !this.elLk.accountHolder.find('._accounts_no_user').length) {

      var hideDropdown = function(e) {
        if (!e.which || e.which === 1) {
          panel.toggleAccountsDropdown(false);
          $(document).off('click', hideDropdown);
        }
      }

      if (!this.accountsRefreshURL) {
        var form = this.elLk.accountHolder.find('form');
        this.accountsRefreshURL   = form.attr('action');
        this.accountsBookmarkData = form.serialize();
      }

      $.ajax(this.accountsRefreshURL, {
        'context': this,
        'data': this.accountsBookmarkData,
        'type': 'POST',
        'success': function(html) {
          this.elLk.accountHolder.html(html);
          this.elLk.accountLink = this.el.find('._accounts_link').on({
            'click': function(event) {
              event.preventDefault();
              if (!$(this).hasClass('selected')) {
                event.stopPropagation();
                panel.toggleAccountsDropdown(true);
                $(document).on('click', hideDropdown);
              }
            }
          });

          this.elLk.accountDropdown = this.el.find('._accounts_dropdown').on({
            'click': function(event) {
              if (event.target.nodeName !== 'A') {
                event.stopPropagation();
              }
            }
          }).find('a').on('click', hideDropdown).end();
        },
        'dataType': 'html'
      });
    }
  },

  toggleAccountsDropdown: function(flag) {
    this.elLk.accountLink.toggleClass('selected', flag);
    this.elLk.accountDropdown.toggle(flag);
    if (flag && !this.elLk.accountDropdown.data('initiated')) {
      this.elLk.accountDropdown.data('initiated', true).find('p').each(function() {
        var p = $(this);
        var checkHeight = p.children('a').hide().end().append('abc').height();
        p.children('a').last().remove().end().show();
        if (p.height() > checkHeight) {
          p.addClass('acc-bookmark-overflow');
        }
      });
    }
  }
});
<commit_msg>if userdb is not connected, disable the login link<commit_after>//Extension to Ensembl.Panel.Masthead to add some dynamic behaviour to account links

Ensembl.Panel.Masthead = Ensembl.Panel.Masthead.extend({
  constructor: function (id) {
    this.base(id);

    Ensembl.EventManager.register('refreshAccountsDropdown', this, this.refreshAccountsDropdown);
  },

  init: function () {
    this.base();

    this.elLk.accountHolder   = this.el.find('div._account_holder');

    this.accountsRefreshURL   = '';
    this.accountsBookmarkData = '';

    this.refreshAccountsDropdown();
  },

  refreshAccountsDropdown: function() {
    var panel = this;

    if (this.elLk.accountHolder.length && !this.elLk.accountHolder.find('._accounts_no_user').length) {

      var hideDropdown = function(e) {
        if (!e.which || e.which === 1) {
          panel.toggleAccountsDropdown(false);
          $(document).off('click', hideDropdown);
        }
      }

      if (!this.accountsRefreshURL) {
        var form = this.elLk.accountHolder.find('form');
        this.accountsRefreshURL   = form.attr('action');
        this.accountsBookmarkData = form.serialize();
      }

      $.ajax(this.accountsRefreshURL, {
        'context': this,
        'data': this.accountsBookmarkData,
        'type': 'POST',
        'success': function(html) {
          this.elLk.accountHolder.html(html);
          this.elLk.accountLink = this.el.find('._accounts_link').on({
            'click': function(event) {
              event.preventDefault();
              if (!$(this).hasClass('selected')) {
                event.stopPropagation();
                panel.toggleAccountsDropdown(true);
                $(document).on('click', hideDropdown);
              }
            }
          });

          this.elLk.accountDropdown = this.el.find('._accounts_dropdown').on({
            'click': function(event) {
              if (event.target.nodeName !== 'A') {
                event.stopPropagation();
              }
            }
          }).find('a').on('click', hideDropdown).end();

          this.elLk.accountHolder.find('._accounts_no_userdb').helptip().on({
            'click': function(event) {
              event.preventDefault();
            }
          });

        },
        'dataType': 'html'
      });
    }
  },

  toggleAccountsDropdown: function(flag) {
    this.elLk.accountLink.toggleClass('selected', flag);
    this.elLk.accountDropdown.toggle(flag);
    if (flag && !this.elLk.accountDropdown.data('initiated')) {
      this.elLk.accountDropdown.data('initiated', true).find('p').each(function() {
        var p = $(this);
        var checkHeight = p.children('a').hide().end().append('abc').height();
        p.children('a').last().remove().end().show();
        if (p.height() > checkHeight) {
          p.addClass('acc-bookmark-overflow');
        }
      });
    }
  }
});
<|endoftext|>


<commit_before>'use strict';

require('should');
var _ = require('lodash');

describe('Profile', function ( ) {

    var baseInputs = {
        settings: {
            insulin_action_curve: 3
        }
        , basals: [
            {minutes: 0, rate: 1}
        ]
        , targets: {
            targets: [
                { offset: 0, high: 120, low: 100 }
            ]
        }
        , temptargets: [
        ]
        , isf: {
            sensitivities: [
                { offset: 0, i: 0, x: 0, start: '00:00:00', sensitivity: 100 }
            ]
        }
        , carbratio: {
            units: 'grams'
            , schedule: [
                { offset: 0, ratio: 20 }
            ]
        }
    };

    it('should should create a profile from inputs', function () {
        var profile = require('../lib/profile')(baseInputs);
        profile.max_iob.should.equal(0);
        profile.dia.should.equal(3);
        profile.sens.should.equal(100);
        profile.current_basal.should.equal(1);
        profile.max_bg.should.equal(120);
        profile.min_bg.should.equal(100);
        profile.carb_ratio.should.equal(20);
    });

    it('should adjust carbratio with carbratio_adjustmentratio', function () {

        var profile = require('../lib/profile')(baseInputs);
        profile.carb_ratio.should.equal(20);

        var profileA = require('../lib/profile')(_.merge({}, baseInputs, {
            carbratio_adjustmentratio: .8
        }));
        profileA.carb_ratio.should.equal(16);

    });

});
<commit_msg>Coverall changes (#339)<commit_after>'use strict';

require('should');
var _ = require('lodash');

describe('Profile', function ( ) {

    var baseInputs = {
        settings: {
            insulin_action_curve: 3
        }
        , basals: [
            {minutes: 0, rate: 1}
        ]
        , targets: {
            targets: [
                { offset: 0, high: 120, low: 100 },
                { offset: 480, high: 115, low: 95}
            ]
        }
        , temptargets: [
        ]
        , isf: {
            sensitivities: [
                { offset: 0, i: 0, x: 0, start: '00:00:00', sensitivity: 100 },
                { offset: 480, i: 16, x: 1, start: '08:00:00', sensitivity: 80 }
            ]
        }
        , carbratio: {
            units: 'grams'
            , schedule: [
                { offset: 0, ratio: 20 }
            ]
        }
    };

    it('should should create a profile from inputs', function () {
        var profile = require('../lib/profile')(baseInputs);
        profile.max_iob.should.equal(0);
        profile.dia.should.equal(3);
        profile.sens.should.equal(100);
        profile.current_basal.should.equal(1);
        profile.max_bg.should.equal(120);
        profile.min_bg.should.equal(100);
        profile.carb_ratio.should.equal(20);
    });

    it('should adjust carbratio with carbratio_adjustmentratio', function () {

        var profile = require('../lib/profile')(baseInputs);
        profile.carb_ratio.should.equal(20);

        var profileA = require('../lib/profile')(_.merge({}, baseInputs, {
            carbratio_adjustmentratio: .8
        }));
        profileA.carb_ratio.should.equal(16);

    });

    it('should should honour override_high_target_with_low', function () {
        var profile = require('../lib/profile')(_.merge({}, baseInputs, {override_high_target_with_low: true}));
        profile.max_iob.should.equal(0);
        profile.dia.should.equal(3);
        profile.sens.should.equal(100);
        profile.current_basal.should.equal(1);
        profile.max_bg.should.equal(100);
        profile.min_bg.should.equal(100);
        profile.carb_ratio.should.equal(20);
    });

    var currentTime = new Date();
    var creationDate = new Date(currentTime.getTime() - (5 * 60 * 1000));

    it('should create a profile with temptarget set', function() {
        var profile = require('../lib/profile')(_.merge({}, baseInputs, { temptargets: [{'eventType':'Temporary Target', 'reason':'Eating Soon', 'targetTop':80, 'targetBottom':80, 'duration':20, 'created_at': creationDate}]}));
        profile.max_iob.should.equal(0);
        profile.dia.should.equal(3);
        profile.sens.should.equal(100);
        profile.current_basal.should.equal(1);
        profile.max_bg.should.equal(80);
        profile.min_bg.should.equal(80);
        profile.carb_ratio.should.equal(20);
        profile.temptargetSet.should.equal(true);
    });

    var pastDate = new Date(currentTime.getTime() - 90*60*1000);
    it('should create a profile ignoring an out of date temptarget', function() {
        var profile = require('../lib/profile')(_.merge({}, baseInputs, { temptargets: [{'eventType':'Temporary Target', 'reason':'Eating Soon', 'targetTop':80, 'targetBottom':80, 'duration':20, 'created_at': pastDate}]}));
        profile.max_iob.should.equal(0);
        profile.dia.should.equal(3);
        profile.sens.should.equal(100);
        profile.current_basal.should.equal(1);
        profile.max_bg.should.equal(120);
        profile.min_bg.should.equal(100);
        profile.carb_ratio.should.equal(20);
    });

    it('should create a profile ignoring a temptarget with 0 duration', function() {
        var profile = require('../lib/profile')(_.merge({}, baseInputs, { temptargets: [{'eventType':'Temporary Target', 'reason':'Eating Soon', 'targetTop':80, 'targetBottom':80, 'duration':0, 'created_at': creationDate}]}));
        profile.max_iob.should.equal(0);
        profile.dia.should.equal(3);
        profile.sens.should.equal(100);
        profile.current_basal.should.equal(1);
        profile.max_bg.should.equal(120);
        profile.min_bg.should.equal(100);
        profile.carb_ratio.should.equal(20);
    });

    it('should error with invalid DIA', function () {
        var profile = require('../lib/profile')(_.merge({}, baseInputs, {settings: {insulin_action_curve: 1}}));
        profile.should.equal(-1);
    });

    it('should error with a current basal of 0', function () {
        var profile = require('../lib/profile')(_.merge({}, baseInputs, {basals: [{minutes: 0, rate: 0}]}));
        profile.should.equal(-1);
    });

    it('should set the profile model from input', function () {
        var profile = require('../lib/profile')(_.merge({}, baseInputs, {model: 554}));
        profile.model.should.equal(554);
    });

});
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2017 Erik Nordstrøm <erik@nordstroem.no>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

Date.prototype.getWeek = function ()
{
	// https://stackoverflow.com/questions/7765767/show-week-number-with-javascript
	var onejan = new Date(this.getFullYear(),0,1);
	return Math.ceil((((this - onejan) / 86400000) + onejan.getDay()+1)/7);
};

const now = new Date();
const isoweek = now.getFullYear() + 'W' + now.getWeek();

const toprow = document.getElementById('top-row');

const canvas_isoweek = document.getElementById('isoweek');
const ctx_isoweek = canvas_isoweek.getContext('2d');

const canvas_weekdays = document.getElementById('weekdays');
const ctx_weekdays = canvas_weekdays.getContext('2d');

const canvas_toprightbar = document.getElementById('top-right-bar');
const ctx_toprightbar = canvas_toprightbar.getContext('2d');

const middlerow = document.getElementById('middle-row');

const canvas_middleleftbar = document.getElementById('middle-left-bar');
const ctx_middleleftbar = canvas_middleleftbar.getContext('2d');

const canvas = document.getElementById('content');
const ctx = canvas.getContext('2d');

const canvas_middlerightbar = document.getElementById('middle-right-bar');
const ctx_middlerightbar = canvas_middlerightbar.getContext('2d');

const bottomrow = document.getElementById('bottom-row');

const canvas_bottomleftbar = document.getElementById('bottom-left-bar');
const ctx_bottomleftbar = canvas_bottomleftbar.getContext('2d');

const canvas_wdactions = document.getElementById('weekday-actions');
const ctx_wdactions = canvas.getContext('2d');

const canvas_bottomrightbar = document.getElementById('bottom-right-bar');
const ctx_bottomrightbar = canvas_bottomrightbar.getContext('2d');

// TODO: If possible, take dpi into account.
const min_fontsize_topbar = 16;
const min_height_topbar = 2 * min_fontsize_topbar;
const max_height_topbar = 100;
const min_fontsize_qh = 12;

const days_of_week = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

const colors =
{
	'navy':    '#001f3f',
	'blue':    '#0074d9',
	'aqua':    '#7fdbff',
	'teal':    '#39cccc',
	'olive':   '#3d9970',
	'green':   '#2ecc40',
	'lime':    '#01ff70',
	'yellow':  '#ffdc00',
	'orange':  '#ff851b',
	'red':     '#ff4136',
	'maroon':  '#85144b',
	'fuchsia': '#f012be',
	'purple':  '#b10dc9',
	'black':   '#111111',
	'gray':    '#aaaaaa',
	'silver':  '#dddddd',
	'white':   '#ffffff'
};

let fontsize_px_topbar,
    fontsize_px_qh,
    pad_top_top_row,
    pad_right_top_row,
    pad_bottom_top_row,
    pad_left_top_row,
    cellwidth_top_row,
    quarter_hour_margin_top,
    quarter_hour_margin_right,
    quarter_hour_margin_bottom,
    quarter_hour_margin_left,
    quarter_hour_height,
    quarter_hour_width,
    qh_text_margin_top,
    qh_text_margin_left,
    content_margin_top,
    content_margin_bottom;

function sizeCanvases ()
{
	const total_width  = window.innerWidth;
	const total_height = window.innerHeight;

	const relaref = (total_width > total_height) ? total_height : total_width;

	const height_top_row = Math.min(Math.floor(Math.max(min_height_topbar, min_fontsize_topbar + 0.12 * relaref)), max_height_topbar);
	toprow.style.height = height_top_row + 'px';

	fontsize_px_topbar = Math.max(min_fontsize_qh, Math.floor(0.3 * height_top_row));
	fontsize_px_qh = Math.max(min_fontsize_qh, Math.floor(10 * fontsize_px_topbar / 24));

	pad_top_top_row = Math.ceil(5 * (height_top_row - fontsize_px_topbar) / 12);
	pad_bottom_top_row = Math.ceil(height_top_row - (fontsize_px_topbar + pad_top_top_row));
	pad_left_top_row = pad_top_top_row;
	pad_right_top_row = pad_top_top_row;

	const height_bottom_row = Math.floor(0.5 * height_top_row);
	bottomrow.style.height = height_bottom_row + 'px';

	const height_middle_row =
		total_height - (height_top_row + height_bottom_row);
	middlerow.style.height = height_middle_row + 'px';

	canvas_isoweek.height = height_top_row;
	canvas_weekdays.height = height_top_row;
	canvas_toprightbar.height = height_top_row;

	canvas_middleleftbar.style.top = height_top_row + 'px';
	canvas_middleleftbar.height = height_middle_row;

	canvas.style.top = height_top_row + 'px';
	canvas.height = height_middle_row;

	canvas_middlerightbar.style.top = height_top_row + 'px';
	canvas_middlerightbar.height = height_middle_row;

	canvas_bottomleftbar.height = height_bottom_row;
	canvas_wdactions.height = height_bottom_row;
	canvas_bottomrightbar.height = height_bottom_row;

	// TODO: Calculate value to use
	const isoweek_width = canvas_isoweek.width;
	canvas_isoweek.width = isoweek_width;

	// TODO: Calculate value to use
	const toprightbar_width = canvas_toprightbar.width;
	canvas_toprightbar.width = toprightbar_width;

	// TODO: Calculate value to use
	const middleleftbar_width = canvas_middleleftbar.width;
	canvas_middleleftbar.width = middleleftbar_width;

	// TODO: Calculate value to use
	const middlerightbar_width = canvas_middlerightbar.width;
	canvas_middlerightbar.width = middlerightbar_width;

	// TODO: Calculate value to use
	const bottomleftbar_width = canvas_bottomleftbar.width;
	canvas_bottomleftbar.width = bottomleftbar_width;

	// TODO: Calculate value to use
	const bottomrightbar_width = canvas_bottomrightbar.width;
	canvas_bottomrightbar.width = bottomrightbar_width;

	canvas_weekdays.style.left = isoweek_width + 'px';
	const weekdays_width =
		total_width - (isoweek_width + toprightbar_width);
	canvas_weekdays.width = weekdays_width;

	canvas.style.left = middleleftbar_width + 'px';
	const content_width =
		total_width - (middleleftbar_width + middleleftbar_width);
	canvas.width = content_width;

	cellwidth_top_row = Math.floor(content_width / 7);

	content_margin_top = Math.floor(1.2 * fontsize_px_qh);
	content_margin_bottom = 2 * fontsize_px_qh;
	qh_text_margin_top = 3;
	qh_text_margin_left = 3;
	quarter_hour_margin_top = 1;
	quarter_hour_margin_right = 2;
	quarter_hour_margin_bottom = 0;
	quarter_hour_margin_left = 0;
	quarter_hour_height = quarter_hour_margin_top + 2 * qh_text_margin_top + fontsize_px_qh + quarter_hour_margin_bottom;
	quarter_hour_width = cellwidth_top_row - (quarter_hour_margin_left + quarter_hour_margin_right);

	canvas_wdactions.style.left = bottomleftbar_width + 'px';
	const wdactions_width =
		total_width - (isoweek_width + toprightbar_width);
	canvas_wdactions.width = wdactions_width;

	const height_content = height_top_row + content_margin_top + 24 * 4 * quarter_hour_height + content_margin_bottom + height_bottom_row;
	canvas.height = height_content;
}

sizeCanvases();

function fullDrawIsoweek ()
{
	ctx_isoweek.fillStyle = colors.white;
	ctx_isoweek.font = fontsize_px_topbar + 'px sans-serif';
	ctx_isoweek.fillText(isoweek, pad_left_top_row, pad_top_top_row + fontsize_px_topbar);
}

fullDrawIsoweek();

function fullDrawWeekdays ()
{
	ctx_weekdays.fillStyle = colors.white;
	ctx_weekdays.font = fontsize_px_topbar + 'px sans-serif';
	let xpos = 0;
	for (day of days_of_week)
	{
		ctx_weekdays.fillText(day, xpos, pad_top_top_row + fontsize_px_topbar);
		xpos += cellwidth_top_row;
	}
}

fullDrawWeekdays();

function fullDrawContent ()
{
	ctx.font = fontsize_px_qh + 'px sans-serif';
	let xpos = quarter_hour_margin_left;
	for (day of days_of_week)
	{
		console.log(day);

		let ypos = content_margin_top;
		for (var i = 0 ; i < 24 * 4 ; i++)
		{
			ypos += quarter_hour_margin_top;

			ctx.fillStyle = colors.gray;
			ctx.fillRect(xpos, ypos, quarter_hour_width, quarter_hour_height);
			ctx.fillStyle = colors.white;
			ctx.fillRect(xpos + 1, ypos + 1, quarter_hour_width - 2, quarter_hour_height - 2);

			let minute = String((i * 15) % 60).padStart(2, '0');
			let hour = String(Math.floor(i / 4)).padStart(2, '0');
			let hm = hour + ':' + minute; // TODO + TZ offset

			ctx.fillStyle = colors.gray;
			ctx.fillText(hm, xpos + qh_text_margin_left, ypos + fontsize_px_qh + qh_text_margin_top);

			ypos += quarter_hour_height + quarter_hour_margin_bottom;
		}

		xpos += quarter_hour_width + quarter_hour_margin_right + quarter_hour_margin_left;
	}
}

fullDrawContent();
<commit_msg>More constraints and relationships between sizes.<commit_after>/*
 * Copyright (c) 2017 Erik Nordstrøm <erik@nordstroem.no>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

Date.prototype.getWeek = function ()
{
	// https://stackoverflow.com/questions/7765767/show-week-number-with-javascript
	var onejan = new Date(this.getFullYear(),0,1);
	return Math.ceil((((this - onejan) / 86400000) + onejan.getDay()+1)/7);
};

const now = new Date();
const isoweek = now.getFullYear() + 'W' + now.getWeek();

const toprow = document.getElementById('top-row');

const canvas_isoweek = document.getElementById('isoweek');
const ctx_isoweek = canvas_isoweek.getContext('2d');

const canvas_weekdays = document.getElementById('weekdays');
const ctx_weekdays = canvas_weekdays.getContext('2d');

const canvas_toprightbar = document.getElementById('top-right-bar');
const ctx_toprightbar = canvas_toprightbar.getContext('2d');

const middlerow = document.getElementById('middle-row');

const canvas_middleleftbar = document.getElementById('middle-left-bar');
const ctx_middleleftbar = canvas_middleleftbar.getContext('2d');

const canvas = document.getElementById('content');
const ctx = canvas.getContext('2d');

const canvas_middlerightbar = document.getElementById('middle-right-bar');
const ctx_middlerightbar = canvas_middlerightbar.getContext('2d');

const bottomrow = document.getElementById('bottom-row');

const canvas_bottomleftbar = document.getElementById('bottom-left-bar');
const ctx_bottomleftbar = canvas_bottomleftbar.getContext('2d');

const canvas_wdactions = document.getElementById('weekday-actions');
const ctx_wdactions = canvas.getContext('2d');

const canvas_bottomrightbar = document.getElementById('bottom-right-bar');
const ctx_bottomrightbar = canvas_bottomrightbar.getContext('2d');

const days_of_week = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

const colors =
{
	'navy':    '#001f3f',
	'blue':    '#0074d9',
	'aqua':    '#7fdbff',
	'teal':    '#39cccc',
	'olive':   '#3d9970',
	'green':   '#2ecc40',
	'lime':    '#01ff70',
	'yellow':  '#ffdc00',
	'orange':  '#ff851b',
	'red':     '#ff4136',
	'maroon':  '#85144b',
	'fuchsia': '#f012be',
	'purple':  '#b10dc9',
	'black':   '#111111',
	'gray':    '#aaaaaa',
	'silver':  '#dddddd',
	'white':   '#ffffff'
};

let fontsize_px_topbar,
    fontsize_px_qh,
    pad_top_top_row,
    pad_right_top_row,
    pad_bottom_top_row,
    pad_left_top_row,
    cellwidth_top_row,
    quarter_hour_margin_top,
    quarter_hour_margin_right,
    quarter_hour_margin_bottom,
    quarter_hour_margin_left,
    quarter_hour_height,
    quarter_hour_width,
    qh_text_margin_top,
    qh_text_margin_left,
    content_margin_top,
    content_margin_bottom;

function sizeCanvases ()
{
	const total_width  = window.innerWidth;
	const total_height = window.innerHeight;

	// TODO: If possible, take dpi into account.
	const min_fontsize_topbar = 16;
	const max_fontsize_topbar = 32;
	const min_height_topbar = 2 * min_fontsize_topbar;
	const max_height_topbar = 2 * max_fontsize_topbar;
	const min_fontsize_qh = 12;

	const relaref = (total_width > total_height) ? total_height : total_width;

	const height_top_row = Math.min(Math.floor(Math.max(min_height_topbar, min_fontsize_topbar + 0.12 * relaref)), max_height_topbar);
	toprow.style.height = height_top_row + 'px';

	fontsize_px_topbar = Math.max(min_fontsize_qh, Math.floor(0.3 * height_top_row));
	fontsize_px_qh = Math.max(min_fontsize_qh, Math.floor(10 * fontsize_px_topbar / 24));

	pad_top_top_row = Math.ceil(5 * (height_top_row - fontsize_px_topbar) / 12);
	pad_bottom_top_row = Math.ceil(height_top_row - (fontsize_px_topbar + pad_top_top_row));
	pad_left_top_row = pad_top_top_row;
	pad_right_top_row = pad_top_top_row;

	const height_bottom_row = Math.floor(0.5 * height_top_row);
	bottomrow.style.height = height_bottom_row + 'px';

	const height_middle_row =
		total_height - (height_top_row + height_bottom_row);
	middlerow.style.height = height_middle_row + 'px';

	canvas_isoweek.height = height_top_row;
	canvas_weekdays.height = height_top_row;
	canvas_toprightbar.height = height_top_row;

	canvas_middleleftbar.style.top = height_top_row + 'px';
	canvas_middleleftbar.height = height_middle_row;

	canvas.style.top = height_top_row + 'px';
	canvas.height = height_middle_row;

	canvas_middlerightbar.style.top = height_top_row + 'px';
	canvas_middlerightbar.height = height_middle_row;

	canvas_bottomleftbar.height = height_bottom_row;
	canvas_wdactions.height = height_bottom_row;
	canvas_bottomrightbar.height = height_bottom_row;

	ctx_isoweek.font = fontsize_px_topbar + 'px sans-serif';
	const isoweek_minwidth = ctx_isoweek.measureText(isoweek).width + 3 * pad_left_top_row;
	const middleleftbar_minwidth = 0; // TODO
	const bottomleftbar_minwidth = 0;
	const left_column_minwidth = Math.max(isoweek_minwidth, Math.max(middleleftbar_minwidth, bottomleftbar_minwidth));

	canvas_isoweek.width = left_column_minwidth;
	canvas_middleleftbar.width = left_column_minwidth;
	canvas_bottomleftbar.width = left_column_minwidth;

	canvas.style.left = left_column_minwidth + 'px';
	const content_margin_min_right = left_column_minwidth;
	const content_width_avail =
		total_width - (left_column_minwidth + content_margin_min_right);

	ctx_weekdays.font = fontsize_px_topbar + 'px sans-serif';

	const dow_maxw = Math.max.apply(null, days_of_week.map((d) => { return ctx_weekdays.measureText(d).width }));

	const cellmaxwidth_top_row = 185;
	cellwidth_top_row = Math.min(Math.max(dow_maxw, Math.floor(content_width_avail / 7)), cellmaxwidth_top_row);
	const content_width = 7 * cellwidth_top_row;

	canvas.width = content_width;

	canvas_weekdays.style.left = left_column_minwidth  + 'px';
	const weekdays_width = content_width;
	canvas_weekdays.width = weekdays_width;

	content_margin_top = Math.floor(1.2 * fontsize_px_qh);
	content_margin_bottom = 2 * fontsize_px_qh;
	qh_text_margin_top = 3;
	qh_text_margin_left = 3;
	quarter_hour_margin_top = 1;
	quarter_hour_margin_right = 2;
	quarter_hour_margin_bottom = 0;
	quarter_hour_margin_left = 0;
	quarter_hour_height = quarter_hour_margin_top + 2 * qh_text_margin_top + fontsize_px_qh + quarter_hour_margin_bottom;
	quarter_hour_width = cellwidth_top_row - (quarter_hour_margin_left + quarter_hour_margin_right);

	canvas_wdactions.style.left = left_column_minwidth + 'px';
	const wdactions_width = content_width;
	canvas_wdactions.width = wdactions_width;

	const height_content = height_top_row + content_margin_top + 24 * 4 * quarter_hour_height + content_margin_bottom + height_bottom_row;
	canvas.height = height_content;

	const right_column_minwidth = 200;
	const right_column_width = Math.max(right_column_minwidth, total_width - (left_column_minwidth + content_width));

	canvas_toprightbar.width = right_column_width;
	canvas_middlerightbar.width = right_column_width;
	canvas_bottomrightbar.width = right_column_width;
}

sizeCanvases();

function fullDrawIsoweek ()
{
	ctx_isoweek.fillStyle = colors.white;
	ctx_isoweek.font = fontsize_px_topbar + 'px sans-serif';
	ctx_isoweek.fillText(isoweek, pad_left_top_row, pad_top_top_row + fontsize_px_topbar);
}

fullDrawIsoweek();

function fullDrawWeekdays ()
{
	ctx_weekdays.fillStyle = colors.white;
	ctx_weekdays.font = fontsize_px_topbar + 'px sans-serif';
	let xpos = 0;
	for (day of days_of_week)
	{
		ctx_weekdays.fillText(day, xpos, pad_top_top_row + fontsize_px_topbar);
		xpos += cellwidth_top_row;
	}
}

fullDrawWeekdays();

function fullDrawContent ()
{
	ctx.font = fontsize_px_qh + 'px sans-serif';
	let xpos = quarter_hour_margin_left;
	for (day of days_of_week)
	{
		let ypos = content_margin_top;
		for (var i = 0 ; i < 24 * 4 ; i++)
		{
			ypos += quarter_hour_margin_top;

			ctx.fillStyle = colors.gray;
			ctx.fillRect(xpos, ypos, quarter_hour_width, quarter_hour_height);
			ctx.fillStyle = colors.white;
			ctx.fillRect(xpos + 1, ypos + 1, quarter_hour_width - 2, quarter_hour_height - 2);

			let minute = String((i * 15) % 60).padStart(2, '0');
			let hour = String(Math.floor(i / 4)).padStart(2, '0');
			let hm = hour + ':' + minute; // TODO + TZ offset

			ctx.fillStyle = colors.gray;
			ctx.fillText(hm, xpos + qh_text_margin_left, ypos + fontsize_px_qh + qh_text_margin_top);

			ypos += quarter_hour_height + quarter_hour_margin_bottom;
		}

		xpos += quarter_hour_width + quarter_hour_margin_right + quarter_hour_margin_left;
	}
}

fullDrawContent();
<|endoftext|>


<commit_before>var PopupView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render');
        this.render();
    },
    render: function () {
        $(this.el).html(ich.PopupView(this.options));
        var that = this;
        $('body').keyup(function(e) {
          if (e.keyCode == 27) { that.close() }   // esc
        });
        window.app.el.append(this.el);
        return this;
    },
    close: function() {
        this.remove();
        window.app.activePopup = false;
        if ($.isFunction(this.options.after)) {
            this.options.after(this);
        }
        return false;
    },
    events: {
        'click .close': 'close'
    }
});

var DropdownView = Backbone.View.extend({
    className: 'DropdownView',
    initialize: function() {
        _.bindAll(this, 'render', 'showContent');
        this.render();
    },
    render: function() {
        $(this.el).html(ich.DropdownView(this.options));
        window.app.el.append(this.el);
        return this;
    },
    events: {
        'click a.show': 'toggleContent'
    },
    toggleContent: function() {
        this.$('.dropdown-content').toggleClass('expanded');
        return false;
    },
    hideContent: function() {
        this.$('.dropdown-content').removeClass('expanded');
    }
});
<commit_msg>Popup menu closes when clicking on window.<commit_after>var PopupView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render');
        this.render();
    },
    render: function () {
        $(this.el).html(ich.PopupView(this.options));
        var that = this;
        $('body').keyup(function(e) {
          if (e.keyCode == 27) { that.close() }   // esc
        });
        window.app.el.append(this.el);
        return this;
    },
    close: function() {
        this.remove();
        window.app.activePopup = false;
        if ($.isFunction(this.options.after)) {
            this.options.after(this);
        }
        return false;
    },
    events: {
        'click .close': 'close'
    }
});

var DropdownView = Backbone.View.extend({
    className: 'DropdownView',
    initialize: function() {
        _.bindAll(this, 'render', 'showContent');
        this.render();
    },
    render: function() {
        $(this.el).html(ich.DropdownView(this.options));
        var that = this;
        $(window).bind('click', function() {
            that.hideContent();
        });
        return this;
    },
    events: {
        'click a.show': 'toggleContent'
    },
    toggleContent: function() {
        this.$('.dropdown-content').toggleClass('expanded');
        return false;
    },
    hideContent: function() {
        this.$('.dropdown-content').removeClass('expanded');
    }
});
<|endoftext|>


<commit_before>import React from 'react';
import {
	View,
	Text,
	StyleSheet,
	ListView,
	Dimensions,
	Animated,
	Platform,
	Easing
} from 'react-native';
import SortableList from 'react-native-sortable-list';
import Icon from 'react-native-vector-icons/MaterialIcons';
import { connect } from 'react-redux';

import css from '../../styles/css';

const deviceWidth = Dimensions.get('window').width;
const savedDataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });

class ShuttleSavedListView extends React.Component {
	componentWillMount() {
		const savedStops = this.props.savedStops.slice();
		const { closestStop } = this.props;

		savedStops.splice(closestStop.savedIndex, 0, closestStop);
		const savedObject = {};
		for (let i = 0; i < savedStops.length; ++i) {
			savedObject[i] = savedStops[i];
		}
		this.setState({ savedObject });
	}

	shouldComponentUpdate() {
		return false; // stop list from re-rendering
	}

	render() {
		return (
			<SortableList
				style={[css.main_container, css.scroll_main, css.whitebg]}
				data={this.state.savedObject}
				renderRow={
					({ data, active, disabled }) =>
						<SavedItem
							data={data}
							active={active}
						/>
				}
				onChangeOrder={(nextOrder) => { this._order = nextOrder; }}
				onReleaseRow={(key) => this.props.orderStops(this._order)}
			/>
		);
	}
}

class SavedItem extends React.Component {

	constructor(props) {
		super(props);

		this._active = new Animated.Value(0);

		this._style = {
			...Platform.select({
				ios: {
					shadowOpacity: this._active.interpolate({
						inputRange: [0, 1],
						outputRange: [0, 0.2],
					}),
					shadowRadius: this._active.interpolate({
						inputRange: [0, 1],
						outputRange: [2, 10],
					}),
				},

				android: {
					elevation: this._active.interpolate({
						inputRange: [0, 1],
						outputRange: [2, 6],
					}),
				},
			})
		};
	}

	componentWillReceiveProps(nextProps) {
		if (this.props.active !== nextProps.active) {
			Animated.timing(this._active, {
				duration: 300,
				easing: Easing.bounce,
				toValue: Number(nextProps.active),
			}).start();
		}
	}

	render() {
		const { data } = this.props;
		return (
			<Animated.View
				style={[styles.list_row, this._style]}
			>
				<Icon
					name="drag-handle"
					size={20}
				/>
				<Text style={{ margin: 7 }}>
					{
						(data.closest) ? ('Closest Stop') : (data.name.trim())
					}
				</Text>
			</Animated.View>
		);
	}
}

function mapStateToProps(state, props) {
	return {
		savedStops: state.shuttle.savedStops,
		closestStop: state.shuttle.closestStop
	};
}

function mapDispatchtoProps(dispatch) {
	return {
		orderStops: (newOrder) => {
			dispatch({ type: 'ORDER_STOPS', newOrder });
		}
	};
}

const styles = StyleSheet.create({
	list_row: { backgroundColor: '#FFF', flexDirection: 'row', alignItems: 'center', width: deviceWidth, padding: 7, borderBottomWidth: 1, borderBottomColor: '#EEE' ,
		...Platform.select({
			ios: {
				shadowOpacity: 0,
				shadowOffset: { height: 2, width: 2 },
				shadowRadius: 0,
			},

			android: {
				elevation: 0,
				marginHorizontal: 30,
			},
		})
	},
});

export default connect(
	mapStateToProps,
	mapDispatchtoProps
)(ShuttleSavedListView);

/*
<ListView
		style={[css.main_container, css.scroll_main, css.whitebg]}
		dataSource={savedDataSource.cloneWithRows(savedStops)}
		renderRow={
			(row, sectionID, rowID) =>
				<SavedItem
					data={row}
					index={rowID}
				/>
		}
	/>
 */<commit_msg>Added margin for android shadow<commit_after>import React from 'react';
import {
	Text,
	StyleSheet,
	ListView,
	Dimensions,
	Animated,
	Platform,
	Easing
} from 'react-native';
import SortableList from 'react-native-sortable-list';
import Icon from 'react-native-vector-icons/MaterialIcons';
import { connect } from 'react-redux';

import css from '../../styles/css';

const deviceWidth = Dimensions.get('window').width;

class ShuttleSavedListView extends React.Component {
	componentWillMount() {
		const savedStops = this.props.savedStops.slice();
		const { closestStop } = this.props;

		savedStops.splice(closestStop.savedIndex, 0, closestStop);
		const savedObject = {};
		for (let i = 0; i < savedStops.length; ++i) {
			savedObject[i] = savedStops[i];
		}
		this.setState({ savedObject });
	}

	shouldComponentUpdate() {
		return false; // stop list from re-rendering
	}

	render() {
		return (
			<SortableList
				style={[css.main_container, css.scroll_main, css.whitebg]}
				data={this.state.savedObject}
				renderRow={
					({ data, active, disabled }) =>
						<SavedItem
							data={data}
							active={active}
						/>
				}
				onChangeOrder={(nextOrder) => { this._order = nextOrder; }}
				onReleaseRow={(key) => this.props.orderStops(this._order)}
			/>
		);
	}
}

class SavedItem extends React.Component {

	constructor(props) {
		super(props);

		this._active = new Animated.Value(0);

		this._style = {
			...Platform.select({
				ios: {
					shadowOpacity: this._active.interpolate({
						inputRange: [0, 1],
						outputRange: [0, 0.2],
					}),
					shadowRadius: this._active.interpolate({
						inputRange: [0, 1],
						outputRange: [2, 10],
					}),
				},

				android: {
					marginTop: this._active.interpolate({
						inputRange: [0, 1],
						outputRange: [0, 10],
					}),
					marginBottom: this._active.interpolate({
						inputRange: [0, 1],
						outputRange: [0, 10],
					}),
					elevation: this._active.interpolate({
						inputRange: [0, 1],
						outputRange: [2, 6],
					}),
				},
			})
		};
	}

	componentWillReceiveProps(nextProps) {
		if (this.props.active !== nextProps.active) {
			Animated.timing(this._active, {
				duration: 300,
				easing: Easing.bounce,
				toValue: Number(nextProps.active),
			}).start();
		}
	}

	render() {
		const { data } = this.props;
		return (
			<Animated.View
				style={[styles.list_row, this._style]}
			>
				<Icon
					name="drag-handle"
					size={20}
				/>
				<Text style={{ margin: 7 }}>
					{
						(data.closest) ? ('Closest Stop') : (data.name.trim())
					}
				</Text>
			</Animated.View>
		);
	}
}

function mapStateToProps(state, props) {
	return {
		savedStops: state.shuttle.savedStops,
		closestStop: state.shuttle.closestStop
	};
}

function mapDispatchtoProps(dispatch) {
	return {
		orderStops: (newOrder) => {
			dispatch({ type: 'ORDER_STOPS', newOrder });
		}
	};
}

const styles = StyleSheet.create({
	list_row: { backgroundColor: '#FFF', flexDirection: 'row', alignItems: 'center', width: deviceWidth, padding: 7, borderBottomWidth: 1, borderBottomColor: '#EEE' ,
		...Platform.select({
			ios: {
				shadowOpacity: 0,
				shadowOffset: { height: 2, width: 2 },
				shadowRadius: 2,
			},

			android: {
				margin: 0,
				elevation: 0,
			},
		})
	},
});

export default connect(
	mapStateToProps,
	mapDispatchtoProps
)(ShuttleSavedListView);

/*
<ListView
		style={[css.main_container, css.scroll_main, css.whitebg]}
		dataSource={savedDataSource.cloneWithRows(savedStops)}
		renderRow={
			(row, sectionID, rowID) =>
				<SavedItem
					data={row}
					index={rowID}
				/>
		}
	/>
 */<|endoftext|>