<commit_before>import React from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import viewportDimensions from 'viewport-dimensions';
import Tone from 'tone';
import lamejs from 'lamejs';
import ReactAudioPlayer from 'react-audio-player';
import axios from 'axios';

import styles from './Recorder.css'

const resolution = 256;

let dataBuffer;
let mp3Encoder;

function inititializeEncoder(sampleRate) {
  mp3Encoder = new lamejs.Mp3Encoder(1, sampleRate, 128);
  clearBuffer();
}

function clearBuffer() {
  dataBuffer = [];
}

function encode(arrayBuffer, bufferSize) {
  const samplesMono = convertBuffer(arrayBuffer);
  let remaining = samplesMono.length;
  for (let i = 0; remaining >= 0; i += bufferSize) {
    const left = samplesMono.subarray(i, i + bufferSize);
    const mp3buf = mp3Encoder.encodeBuffer(left);
    appendToBuffer(mp3buf);
    remaining -= bufferSize;
  }
}

function appendToBuffer(mp3Buf) {
  dataBuffer.push(new Int8Array(mp3Buf));
}

function convertBuffer(arrayBuffer) {
  // need to clone the incoming buffer otherwise we end up with
  // samples reflecting the sound coming from the microphone at the instant we stopped recording
  const data = new Float32Array(arrayBuffer);
  const output = new Int16Array(arrayBuffer.length);
  floatTo16BitPCM(data, output);
  return output;
}

function floatTo16BitPCM(input, output) {
  for (let i = 0; i < input.length; i++) {
    const s = Math.max(-1, Math.min(1, input[i]));
    output[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
  }
}

function finishRecording() {
  appendToBuffer(mp3Encoder.flush());
  return new Blob(dataBuffer, { type: 'audio/mp3' });
}

class Recorder extends React.Component {
  constructor(props) {
    super(props);

    this.prompts = {
      START: this._renderInitializingRecorderPrompt.bind(this),
      STOP: this._renderStopRecordingPrompt.bind(this),
      SAVE_PENDING: this._renderSaveRecordingPrompt.bind(this),
      SAVE_SUCCESS: this._renderSaveSuccessPrompt.bind(this),
      SAVE_ERROR: this._renderSaveErrorPrompt.bind(this),
    };

    this.state = {
      isRecording: false,
      disableRecording: true,
      currentPrompt: this.prompts.START,
      userMediaSupported: Tone.UserMedia.supported,
      // @todo remove
      blob: undefined
    };

    if (Tone.UserMedia.supported) {
      this.userMedia = new Tone.UserMedia();

      this.analyser = new Tone.Analyser({
        type : 'waveform',
        size : resolution
      });

      this.userMedia.connect(this.analyser);
    }

    this._renderUserMediaSupported = this._renderUserMediaSupported.bind(this);
    this._renderStartRecordingPrompt = this._renderStartRecordingPrompt.bind(this);
    this._startRecording = this._startRecording.bind(this);
    this._stopRecording = this._stopRecording.bind(this);
    this._drawBlobs = this._drawBlobs.bind(this);
    this._openRecorderAndBeginDrawingWaves = this._openRecorderAndBeginDrawingWaves.bind(this);
    this._drawWave = this._drawWave.bind(this);
    this._renderUserMediaNotSupported = this._renderUserMediaNotSupported.bind(this);
    this._saveRecording = this._saveRecording.bind(this);
  }

  _openRecorderAndBeginDrawingWaves() {
    this.canvasContext = this.canvas.getContext('2d');

    // @todo have these resize with window resize
    this.canvasContext.canvas.width = this.canvas.width;
    this.canvasContext.canvas.height = this.canvas.height;

    this._drawWave();
  }

  _drawWave() {
    const canvasWidth = this.canvasContext.canvas.width;
    const canvasHeight = this.canvasContext.canvas.height;

    let x;
    let y;
    let isWhite = false;

    // @todo do we need to be able to clean this up? or will we beable to do a redirect to another page to clear this work?
    requestAnimationFrame(this._drawWave);

    // draw the waveform
    const values = this.analyser.analyse();

    this.canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
    this.canvasContext.beginPath();
    this.canvasContext.lineJoin = 'round';
    if(this.state.isRecording) {
      this.canvasContext.lineWidth = 5;
      this.canvasContext.strokeStyle = 'red';
    }
    else {
      this.canvasContext.lineWidth = 2;
      this.canvasContext.strokeStyle = '#CCCCCC';
    }
    this.canvasContext.moveTo((values[0] / 255) * canvasWidth, canvasHeight);

    const now = Date.now();

    for (let i = values.length; i > 0; i--){
      if(this.state.isRecording) {
        if ((1-i/values.length) > ((now - this.state.recordingStartTime)/10000) && !isWhite) {
          isWhite = true;
          this.canvasContext.stroke();
          this.canvasContext.lineWidth = 2;
          this.canvasContext.strokeStyle = '#CCCCCC';
          this.canvasContext.beginPath();
          this.canvasContext.moveTo(x, y);
          this.canvasContext.lineJoin = 'round';
        }
      }
      const val = values[i] / (resolution - 1);
      x = val * canvasWidth;
      y = (i / (resolution - 1)) * canvasHeight;
      this.canvasContext.lineTo(x, y);
    }

    this.canvasContext.stroke();
  }

  _renderInitializingRecorderPrompt() {
    return (

        {
          this.state.disableRecording
            ?
Waiting on encoder to initialize

            : this._renderStartRecordingPrompt()
        }

    );
  }

  _renderStartRecordingPrompt() {
    return (

        Start

    );
  }

  _renderStopRecordingPrompt() {
    return (

        Stop

    );
  }

  _renderSaveRecordingPrompt() {
    this._saveRecording();
    return
 saving recording...
;
  }

  _renderSaveErrorPrompt() {
    return
Ice Cream melted :(
;
  }

  _renderSaveSuccessPrompt() {
    return
Taco crunchy!
;
  }

  _renderUserMediaSupported() {
    // "- 5" to keep canvas within viewport i.e. no scrollbars
    const width = viewportDimensions
      ? viewportDimensions.width() && viewportDimensions.width() - 5
      : 300;
    const height = viewportDimensions
      ? viewportDimensions.height() && viewportDimensions.height() - 5
      : 300;

    return (

        {this.state.currentPrompt()}
        { /* @todo remove this */ this._drawBlobs()}
        <canvas
          className={styles.container}
          width={width}
          height={height}
          ref={(canvas) => { this.canvas = canvas; }}
        />

    );
  }

  _drawBlobs() {
    return (

        {
          this.state.blob
            ? <ReactAudioPlayer src={window.URL.createObjectURL(this.state.blob)} controls />
            : null
        }

    );
  }

  _renderUserMediaNotSupported() {
    return (
      // @todo replace with imported css

        WebAudio is not supported by this browser.

        Please upgrade this browser's version or switch to a browser that supports this technology.

        i.e. internet explorer, safari, and all iOS-based browsers will not be able to run this application

    );
  }

  _startRecording() {
    if (this.processor) {
      this.userMedia.connect(this.processor);
      // if the ScriptProcessorNode is not connected to an output the "onaudioprocess" event is not triggered in chrome
      this.processor.connect(this.context.destination);

      this.setState({
        recordingStartTime: Date.now(),
        isRecording: true
      });
    }
    else {
      // @todo log and metric
      console.error('this.processor has not been initialized!');
    }

    this.setState({ currentPrompt: this.prompts.STOP });
  }

  _stopRecording() {
    if (this.processor) {
      this.processor.disconnect();
      console.log('Recorder has stopped recording');
      const blob = finishRecording();
      this.setState({
        currentPrompt: this.prompts.SAVE_PENDING,
        blob });
    }
    else {
      console.log('Error trying to stop recorder');
    }
  }

  _saveRecording() {
    const data = new FormData();
    data.append('file', new File([this.state.blob], 'sample.mp3'));

    const config = {
      onUploadProgress: (progressEvent) => {
        console.log(
          `upload progress: ${Math.round(progressEvent.loaded * 100 / progressEvent.total)}`,
        );
      },
    };

    axios
      .post('/api/sample', data, config)
      .then(() => this.setState({ currentPrompt: this.prompts.SAVE_SUCCESS }))
      .catch((err) => {
        // @todo log error
        this.setState({ currentPrompt: this.prompts.SAVE_ERROR });
      });
  }

  componentDidMount() {
    // opening the input asks the user to activate their mic
    this.userMedia.open()
      .then(() => {
        // save a reference to the AudioContext
        this.context = this.userMedia.context._context;

        inititializeEncoder(this.context.sampleRate)

        // a bufferSize of 0 instructs the browser to choose the best bufferSize
        this.processor = this.context.createScriptProcessor(0, 1, 1);
        const bufferSize = this.processor.bufferSize;

        this.processor.onaudioprocess = (event) => {
          const arrayBuffer = event.inputBuffer.getChannelData(0);
          // @todo move to webworker
          encode(arrayBuffer, bufferSize);
        };

        // @todo tweak this for best performance
        // @see{https://tonejs.github.io/docs/#Context}
        // this.userMedia.context.latencyHint = 'balanced';

        // overlay 'start recording' mask
        this.setState({ disableRecording: false });
        this._openRecorderAndBeginDrawingWaves();
      })
      .catch(err => {
        console.error(err);
        // @todo log and metric
        // @todo show user a userful view
        alert('user rejected userMedia request!');
      });
  }

  render() {
    return (
      // @todo replace with imported css

        {
          this.state.userMediaSupported
            ? this._renderUserMediaSupported()
            : this._renderUserMediaNotSupported()
        }

    );
  }
}

export default withStyles(styles)(Recorder)<commit_msg>handled case where client does not give microphone permission<commit_after>import React from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import viewportDimensions from 'viewport-dimensions';
import Tone from 'tone';
import lamejs from 'lamejs';
import ReactAudioPlayer from 'react-audio-player';
import axios from 'axios';

import styles from './Recorder.css'

const resolution = 256;

let dataBuffer;
let mp3Encoder;

function inititializeEncoder(sampleRate) {
  mp3Encoder = new lamejs.Mp3Encoder(1, sampleRate, 128);
  clearBuffer();
}

function clearBuffer() {
  dataBuffer = [];
}

function encode(arrayBuffer, bufferSize) {
  const samplesMono = convertBuffer(arrayBuffer);
  let remaining = samplesMono.length;
  for (let i = 0; remaining >= 0; i += bufferSize) {
    const left = samplesMono.subarray(i, i + bufferSize);
    const mp3buf = mp3Encoder.encodeBuffer(left);
    appendToBuffer(mp3buf);
    remaining -= bufferSize;
  }
}

function appendToBuffer(mp3Buf) {
  dataBuffer.push(new Int8Array(mp3Buf));
}

function convertBuffer(arrayBuffer) {
  // need to clone the incoming buffer otherwise we end up with
  // samples reflecting the sound coming from the microphone at the instant we stopped recording
  const data = new Float32Array(arrayBuffer);
  const output = new Int16Array(arrayBuffer.length);
  floatTo16BitPCM(data, output);
  return output;
}

function floatTo16BitPCM(input, output) {
  for (let i = 0; i < input.length; i++) {
    const s = Math.max(-1, Math.min(1, input[i]));
    output[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
  }
}

function finishRecording() {
  appendToBuffer(mp3Encoder.flush());
  return new Blob(dataBuffer, { type: 'audio/mp3' });
}

class Recorder extends React.Component {
  constructor(props) {
    super(props);

    this.prompts = {
      START: this._renderInitializingRecorderPrompt.bind(this),
      STOP: this._renderStopRecordingPrompt.bind(this),
      SAVE_PENDING: this._renderSaveRecordingPrompt.bind(this),
      SAVE_SUCCESS: this._renderSaveSuccessPrompt.bind(this),
      SAVE_ERROR: this._renderSaveErrorPrompt.bind(this),
      USER_MEDIA_DENIED: this._renderUserMediaDenied.bind(this)
    };

    this.state = {
      isRecording: false,
      disableRecording: true,
      currentPrompt: this.prompts.START,
      userMediaSupported: Tone.UserMedia.supported,
      // @todo remove
      blob: undefined
    };

    if (Tone.UserMedia.supported) {
      this.userMedia = new Tone.UserMedia();

      this.analyser = new Tone.Analyser({
        type : 'waveform',
        size : resolution
      });

      this.userMedia.connect(this.analyser);
    }

    this._renderUserMediaSupported = this._renderUserMediaSupported.bind(this);
    this._renderStartRecordingPrompt = this._renderStartRecordingPrompt.bind(this);
    this._startRecording = this._startRecording.bind(this);
    this._stopRecording = this._stopRecording.bind(this);
    this._drawBlobs = this._drawBlobs.bind(this);
    this._openRecorderAndBeginDrawingWaves = this._openRecorderAndBeginDrawingWaves.bind(this);
    this._drawWave = this._drawWave.bind(this);
    this._renderUserMediaNotSupported = this._renderUserMediaNotSupported.bind(this);
    this._saveRecording = this._saveRecording.bind(this);
  }

  _renderUserMediaDenied() {
      return (

Welp, you blew it!

      );
  }

  _openRecorderAndBeginDrawingWaves() {
    this.canvasContext = this.canvas.getContext('2d');

    // @todo have these resize with window resize
    this.canvasContext.canvas.width = this.canvas.width;
    this.canvasContext.canvas.height = this.canvas.height;

    this._drawWave();
  }

  _drawWave() {
    const canvasWidth = this.canvasContext.canvas.width;
    const canvasHeight = this.canvasContext.canvas.height;

    let x;
    let y;
    let isWhite = false;

    // @todo do we need to be able to clean this up? or will we beable to do a redirect to another page to clear this work?
    requestAnimationFrame(this._drawWave);

    // draw the waveform
    const values = this.analyser.analyse();

    this.canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
    this.canvasContext.beginPath();
    this.canvasContext.lineJoin = 'round';
    if(this.state.isRecording) {
      this.canvasContext.lineWidth = 5;
      this.canvasContext.strokeStyle = 'red';
    }
    else {
      this.canvasContext.lineWidth = 2;
      this.canvasContext.strokeStyle = '#CCCCCC';
    }
    this.canvasContext.moveTo((values[0] / 255) * canvasWidth, canvasHeight);

    const now = Date.now();

    for (let i = values.length; i > 0; i--){
      if(this.state.isRecording) {
        if ((1-i/values.length) > ((now - this.state.recordingStartTime)/10000) && !isWhite) {
          isWhite = true;
          this.canvasContext.stroke();
          this.canvasContext.lineWidth = 2;
          this.canvasContext.strokeStyle = '#CCCCCC';
          this.canvasContext.beginPath();
          this.canvasContext.moveTo(x, y);
          this.canvasContext.lineJoin = 'round';
        }
      }
      const val = values[i] / (resolution - 1);
      x = val * canvasWidth;
      y = (i / (resolution - 1)) * canvasHeight;
      this.canvasContext.lineTo(x, y);
    }

    this.canvasContext.stroke();
  }

  _renderInitializingRecorderPrompt() {
    return (

        {
          this.state.disableRecording
            ?
Waiting on encoder to initialize

            : this._renderStartRecordingPrompt()
        }

    );
  }

  _renderStartRecordingPrompt() {
    return (

        Start

    );
  }

  _renderStopRecordingPrompt() {
    return (

        Stop

    );
  }

  _renderSaveRecordingPrompt() {
    this._saveRecording();
    return
 saving recording...
;
  }

  _renderSaveErrorPrompt() {
    return
Ice Cream melted :(
;
  }

  _renderSaveSuccessPrompt() {
    return
Taco crunchy!
;
  }

  _renderUserMediaSupported() {
    // "- 5" to keep canvas within viewport i.e. no scrollbars
    const width = viewportDimensions
      ? viewportDimensions.width() && viewportDimensions.width() - 5
      : 300;
    const height = viewportDimensions
      ? viewportDimensions.height() && viewportDimensions.height() - 5
      : 300;

    return (

        {this.state.currentPrompt()}
        { /* @todo remove this */ this._drawBlobs()}
        <canvas
          className={styles.container}
          width={width}
          height={height}
          ref={(canvas) => { this.canvas = canvas; }}
        />

    );
  }

  _drawBlobs() {
    return (

        {
          this.state.blob
            ? <ReactAudioPlayer src={window.URL.createObjectURL(this.state.blob)} controls />
            : null
        }

    );
  }

  _renderUserMediaNotSupported() {
    return (
      // @todo replace with imported css

        WebAudio is not supported by this browser.

        Please upgrade this browser's version or switch to a browser that supports this technology.

        i.e. internet explorer, safari, and all iOS-based browsers will not be able to run this application

    );
  }

  _startRecording() {
    if (this.processor) {
      this.userMedia.connect(this.processor);
      // if the ScriptProcessorNode is not connected to an output the "onaudioprocess" event is not triggered in chrome
      this.processor.connect(this.context.destination);

      this.setState({
        recordingStartTime: Date.now(),
        isRecording: true
      });
    }
    else {
      // @todo log and metric
      console.error('this.processor has not been initialized!');
    }

    this.setState({ currentPrompt: this.prompts.STOP });
  }

  _stopRecording() {
    if (this.processor) {
      this.processor.disconnect();
      console.log('Recorder has stopped recording');
      const blob = finishRecording();
      this.setState({
        currentPrompt: this.prompts.SAVE_PENDING,
        blob });
    }
    else {
      console.log('Error trying to stop recorder');
    }
  }

  _saveRecording() {
    const data = new FormData();
    data.append('file', new File([this.state.blob], 'sample.mp3'));

    const config = {
      onUploadProgress: (progressEvent) => {
        console.log(
          `upload progress: ${Math.round(progressEvent.loaded * 100 / progressEvent.total)}`,
        );
      },
    };

    axios
      .post('/api/sample', data, config)
      .then(() => this.setState({ currentPrompt: this.prompts.SAVE_SUCCESS }))
      .catch((err) => {
        // @todo log error
        this.setState({ currentPrompt: this.prompts.SAVE_ERROR });
      });
  }

  componentDidMount() {
    // opening the input asks the user to activate their mic
    this.userMedia.open()
      .then(() => {
        // save a reference to the AudioContext
        this.context = this.userMedia.context._context;

        inititializeEncoder(this.context.sampleRate)

        // a bufferSize of 0 instructs the browser to choose the best bufferSize
        this.processor = this.context.createScriptProcessor(0, 1, 1);
        const bufferSize = this.processor.bufferSize;

        this.processor.onaudioprocess = (event) => {
          const arrayBuffer = event.inputBuffer.getChannelData(0);
          // @todo move to webworker
          encode(arrayBuffer, bufferSize);
        };

        // @todo tweak this for best performance
        // @see{https://tonejs.github.io/docs/#Context}
        // this.userMedia.context.latencyHint = 'balanced';

        // overlay 'start recording' mask
        this.setState({ disableRecording: false });
        this._openRecorderAndBeginDrawingWaves();
      })
      .catch(err => {
        console.error(err);
        // @todo log and metric
        this.setState({ currentPrompt: this.prompts.USER_MEDIA_DENIED });
      });
  }

  render() {
    return (
      // @todo replace with imported css

        {
          this.state.userMediaSupported
            ? this._renderUserMediaSupported()
            : this._renderUserMediaNotSupported()
        }

    );
  }
}

export default withStyles(styles)(Recorder)<|endoftext|>


<commit_before>import Immutable                 from 'immutable';
import _                         from 'lodash';
import * as AssessmentSelectors  from "./assessment";

describe('isFirstPage', () => {
  it('should return true on first question', () => {
    const state = {
      assessmentProgress: Immutable.fromJS({currentItemIndex: 0})
    };
    const result = AssessmentSelectors.isFirstPage(state);

    expect(result).toEqual(true);
  });

  it('should return false otherwise', () => {
    const state = {
      assessmentProgress: Immutable.fromJS({currentItemIndex: 1})
    };
    const result = AssessmentSelectors.isFirstPage(state);

    expect(result).toEqual(false);
  });
});

describe('isLastPage', () => {
  it('should return true on last question', () => {
    const currentItemIndex = 8;
    const numItems = 10;
    const itemsPerPage = 2;
    const result = AssessmentSelectors._isLastPage(currentItemIndex, numItems, itemsPerPage);

    expect(result).toEqual(true);
  });
  it('should return false otherwise', () => {
    const currentItemIndex = 7;
    const numItems = 10;
    const itemsPerPage = 2;
    const result = AssessmentSelectors._isLastPage(currentItemIndex, numItems, itemsPerPage);

    expect(result).toEqual(false);
  });
});

describe('isNextUnlocked', () => {
  const createResponse = (i, responses) => {
    responses[i] = {
      answerIds:["1", "2"],
      correct:true,
      feedback:"
Feedbac
"
    };
  };
  describe('unlockNext is ON_CORRECT', () => {
    var nextUnlocked, responses, questionsPerPage;
    beforeEach(() => {
      nextUnlocked = "ON_CORRECT";
      responses = {};
      questionsPerPage = 12;
      _.range(12).forEach((i) =>{createResponse(i, responses)}); //TODO doc
    });

    it('should return true when all answers in current page are correct', () => {
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage, -1);
      expect(result).toEqual(true);
    });

    it('should return false when there is an incorrect answer', () => {
      responses[3].correct = false;
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage, -1);
      expect(result).toEqual(false);
    });

    it('should return false when there is an unanswered question', () => {
      delete responses[4];
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage, -1);
      expect(result).toEqual(false);
    });
  });

  describe('unlockNext is ON_ANSWER_CHECK', () => {
    var nextUnlocked, responses, questionsPerPage;
    beforeEach(() => {
      nextUnlocked = "ON_ANSWER_CHECK";
      responses = {};
      questionsPerPage = 12;
      _.range(12).forEach((i) =>{createResponse(i, responses)}); //TODO doc
    });

    it('should return true when all answers have been checked', () => {
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage, -1);
      expect(result).toEqual(true);
    });
    it('should return false otherwise', () => {
      delete responses[2];
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage, -1);
      expect(result).toEqual(false);
    });
  });

  describe('ALWAYS', () => {
    var nextUnlocked, responses, questionsPerPage;
    beforeEach(() => {
      nextUnlocked = "ALWAYS";
      responses = {};
      questionsPerPage = 12;
    });

    it('should return true if no questions are answered', () => {
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage);
      expect(result).toEqual(true);
    });
  });
});

describe('currentItems', () => {
  var allQuestions, currentItemIndex, questionsPerPage, assessmentLoaded;
  beforeEach(() => {
    allQuestions = [{id:0}, {id:1}, {id:2}, {id:3}, {id:4}, {id:5}, {id:6}, {id:7}, {id:8}, {id:9}, {id:10}];
    currentItemIndex = 6;
    questionsPerPage = 3;
    assessmentLoaded = true;
  });
  it('returns all items on current page', () => {
    const result = AssessmentSelectors._currentItems(allQuestions, currentItemIndex, questionsPerPage, assessmentLoaded);
    expect(result).toEqual([{id:6}, {id:7}, {id:8}]);
  });
  it('returns empty array if the assessment hasnt loaded', () => {
    assessmentLoaded = false;
    const result = AssessmentSelectors._currentItems(allQuestions, currentItemIndex, questionsPerPage, assessmentLoaded);
    expect(result).toEqual([]);
  });
});
<commit_msg>More semicolons<commit_after>import Immutable                 from 'immutable';
import _                         from 'lodash';
import * as AssessmentSelectors  from "./assessment";

describe('isFirstPage', () => {
  it('should return true on first question', () => {
    const state = {
      assessmentProgress: Immutable.fromJS({currentItemIndex: 0})
    };
    const result = AssessmentSelectors.isFirstPage(state);

    expect(result).toEqual(true);
  });

  it('should return false otherwise', () => {
    const state = {
      assessmentProgress: Immutable.fromJS({currentItemIndex: 1})
    };
    const result = AssessmentSelectors.isFirstPage(state);

    expect(result).toEqual(false);
  });
});

describe('isLastPage', () => {
  it('should return true on last question', () => {
    const currentItemIndex = 8;
    const numItems = 10;
    const itemsPerPage = 2;
    const result = AssessmentSelectors._isLastPage(currentItemIndex, numItems, itemsPerPage);

    expect(result).toEqual(true);
  });
  it('should return false otherwise', () => {
    const currentItemIndex = 7;
    const numItems = 10;
    const itemsPerPage = 2;
    const result = AssessmentSelectors._isLastPage(currentItemIndex, numItems, itemsPerPage);

    expect(result).toEqual(false);
  });
});

describe('isNextUnlocked', () => {
  const createResponse = (i, responses) => {
    responses[i] = {
      answerIds:["1", "2"],
      correct:true,
      feedback:"
Feedbac
"
    };
  };
  describe('unlockNext is ON_CORRECT', () => {
    var nextUnlocked, responses, questionsPerPage;
    beforeEach(() => {
      nextUnlocked = "ON_CORRECT";
      responses = {};
      questionsPerPage = 12;
      _.range(12).forEach((i) =>{ createResponse(i, responses); }); //TODO doc
    });

    it('should return true when all answers in current page are correct', () => {
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage, -1);
      expect(result).toEqual(true);
    });

    it('should return false when there is an incorrect answer', () => {
      responses[3].correct = false;
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage, -1);
      expect(result).toEqual(false);
    });

    it('should return false when there is an unanswered question', () => {
      delete responses[4];
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage, -1);
      expect(result).toEqual(false);
    });
  });

  describe('unlockNext is ON_ANSWER_CHECK', () => {
    var nextUnlocked, responses, questionsPerPage;
    beforeEach(() => {
      nextUnlocked = "ON_ANSWER_CHECK";
      responses = {};
      questionsPerPage = 12;
      _.range(12).forEach((i) =>{ createResponse(i, responses); }); //TODO doc
    });

    it('should return true when all answers have been checked', () => {
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage, -1);
      expect(result).toEqual(true);
    });
    it('should return false otherwise', () => {
      delete responses[2];
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage, -1);
      expect(result).toEqual(false);
    });
  });

  describe('ALWAYS', () => {
    var nextUnlocked, responses, questionsPerPage;
    beforeEach(() => {
      nextUnlocked = "ALWAYS";
      responses = {};
      questionsPerPage = 12;
    });

    it('should return true if no questions are answered', () => {
      const result = AssessmentSelectors._isNextUnlocked(nextUnlocked, responses, questionsPerPage);
      expect(result).toEqual(true);
    });
  });
});

describe('currentItems', () => {
  var allQuestions, currentItemIndex, questionsPerPage, assessmentLoaded;
  beforeEach(() => {
    allQuestions = [{id:0}, {id:1}, {id:2}, {id:3}, {id:4}, {id:5}, {id:6}, {id:7}, {id:8}, {id:9}, {id:10}];
    currentItemIndex = 6;
    questionsPerPage = 3;
    assessmentLoaded = true;
  });
  it('returns all items on current page', () => {
    const result = AssessmentSelectors._currentItems(allQuestions, currentItemIndex, questionsPerPage, assessmentLoaded);
    expect(result).toEqual([{id:6}, {id:7}, {id:8}]);
  });
  it('returns empty array if the assessment hasnt loaded', () => {
    assessmentLoaded = false;
    const result = AssessmentSelectors._currentItems(allQuestions, currentItemIndex, questionsPerPage, assessmentLoaded);
    expect(result).toEqual([]);
  });
});
<|endoftext|>


<commit_before>(function() {
  'use strict';

  angular.module('pub.documents.controllers', [])

    .value('fonts', [{name: 'Source Serif Pro'}, {name: 'Source Sans Pro'}])

    .value('fontWeights', [{weight: '400'}, {weight: '500'}, {weight: '600'}])

    .value('objAnchors', {
      size: 10,
      points: [
        {coordinate: 'nw', x: 0, y: 0},
        {coordinate: 'n', x: 0.5, y: 0},
        {coordinate: 'ne', x: 1, y: 0},
        {coordinate: 'w', x: 0, y: 0.5},
        {coordinate: 'e', x: 1, y: 0.5},
        {coordinate: 'sw', x: 0, y: 1},
        {coordinate: 's', x: 0.5, y: 1},
        {coordinate: 'se', x: 1, y: 1}
      ]
    })

    .value('colors', [
      '#f6f8fb', '#e7eaed',
      '#cdd2d9', '#abb3bd',
      '#656e78', '#434a54',
      '#d94656', '#ea5569',
      '#e85945', '#fa6f57',
      '#f5bb4f', '#fecd5f',
      '#8ec159', '#a2d36e',
      '#3fbc9c', '#4fcfae',
      '#42b0d9', '#55c2e8',
      '#4e8bda', '#609eeb',
      '#967dda', '#ac95eb',
      '#d673ad', '#eb89c0'
    ])

    .controller('DocumentsController', [
      '$scope',
      '$state',
      '$location',
      'Restangular',
      'documentServices',
      'authentication',
      'documents',
      'fonts',
      function($scope, $state, $location, Restangular, documentServices, authentication, documents, fonts) {
        var documentsApi = Restangular.all('documents');

        $scope.updateAuthenticationStatus();
        $scope.documents = documents;
        $scope.dpi = 72;
        $scope.iconDpi = 15;
        $scope.newDocumentModalVisible = false;
        $scope.sortFilter = 'name';
        $scope.sortReverse = false;
        $scope.fonts = fonts;

        $scope.updateSortFilter = function(sortFilter, sortReverse) {
          $scope.sortFilter = sortFilter;
          $scope.sortReverse = sortReverse;
        };

        $scope.newDocument = function() {
          documentsApi.post({
            _user: $scope.user._id,
            name: $scope.name,
            width: $scope.width,
            height: $scope.height,
            shapes: []
          }).then(function(res) {
            $scope.newDocumentModal()
            $scope.documents.push(res)
          });
        };

        $scope.viewUser = function() {
          $state.go('pub.user');
        };

        $scope.newDocumentModal = function() {
          $scope.newDocumentModalVisible = !$scope.newDocumentModalVisible;
          $scope.name = "";
          $scope.width = "";
          $scope.height = "";
        };

        $scope.documentIconSize = function(doc) {
          var iconDpi = 15,
            iconWidth = Math.max(Math.min(doc.width * iconDpi, 200), 30),
            iconHeight = (iconWidth / doc.width) * doc.height;

          return {width: iconWidth + 'px', height: iconHeight + 'px'};
        };
      }
    ])

    .controller('DocumentController', [
      '$scope',
      '$state',
      '$window',
      '$timeout',
      'documentServices',
      'doc',
      function($scope, $state, $window, $timeout, documentServices, doc) {
        $scope.doc = doc;
        $scope.selectedObj = null;
        $scope.showCanvasGrid = true;
        $scope.snapToGrid = true;
        $scope.currentInspector = 'document';
        $scope.clipboard = null;
        $scope.showInspector = true;
        $scope.zoomLevel = 1;

        $scope.updateDocument = function(closeDocumentView) {
          $scope.doc.put();

          if (closeDocumentView) {
            var idx = _.findIndex($scope.documents, { _id : $scope.doc._id });
            $scope.documents[idx] = $scope.doc;
            $scope.showAllDocuments();
          }
        };

        $scope.showAllDocuments = function() {
          $state.go('pub.documents');
        };

        $scope.shiftLayer = function(offset) {
          $scope.doc.shapes.moveElement($scope.selectedObj, offset);
        };

        $scope.toggleCanvasGrid = function() {
          $scope.showCanvasGrid = !$scope.showCanvasGrid;
        };

        $scope.toggleSnapToGrid = function() {
          $scope.snapToGrid = !$scope.snapToGrid;
        };

        $scope.downloadPdf = function() {
          $scope.doc.put().then(function() {
            var documentPdfRoute = '/documents/' + $scope.doc._id + '/pdf';
            window.open(documentPdfRoute, '_blank', '');
          });
        };

        $scope.deleteDocument = function() {
          _.remove($scope.documents, function(doc) {
            return doc._id === $scope.doc._id;
          })
          $scope.doc.remove();
          $scope.deleteModal();
          $scope.showAllDocuments();
        };

        $scope.toggleInspector = function(inspector) {
          $scope.currentInspector = inspector;
        };

        $scope.svgObjectSelected = function(obj) {
          $scope.selectedObj = obj;

          if (!obj && $scope.currentInspector === 'color') {
            $scope.toggleInspector('shape');
          }
        };

        $scope.cutObj = function() {
          $scope.clipboard = angular.copy($scope.selectedObj);
          var objIdx = $scope.doc.shapes.indexOf($scope.selectedObj);
          $scope.doc.shapes.splice(objIdx, 1);
          $scope.selectedObj = null;
        };

        $scope.deleteObj = function() {
          var objIdx = $scope.doc.shapes.indexOf($scope.selectedObj);
          $scope.doc.shapes.splice(objIdx, 1);
          $scope.selectedObj = null;
        };

        $scope.copyObj = function() {
          $scope.clipboard = angular.copy($scope.selectedObj);
        };

        $scope.pasteObj = function() {
          var duplicateObj = angular.copy($scope.clipboard);
          duplicateObj._id = undefined;
          duplicateObj.x += 0.25;
          duplicateObj.y += 0.25;
          $scope.doc.shapes.push(duplicateObj);
        };

        $scope.updateColor = function(color, property) {
          $scope.selectedObj[property] = color;
        };

        $scope.addObject = function(objType) {
          $scope.doc.shapes.push(documentServices.newShape(objType));
        };
      }
    ])

    .controller('DocumentCanvasController', [
      '$scope',
      'documentServices',
      'objAnchors',
      function($scope, documentServices, objAnchors) {
        $scope.objAnchors = objAnchors;

        $scope.xAxisRange = function() {
          return _.range($scope.doc.width / 0.25);
        };

        $scope.yAxisRange = function() {
          return _.range($scope.doc.height / 0.25);
        };

        $scope.unitDivider = function() {
          return ($scope.zoomLevel > 1) ? 0.5 : 1;
        };
      }
    ])
}());
<commit_msg>Fixed PDF downloading on Safari.<commit_after>(function() {
  'use strict';

  angular.module('pub.documents.controllers', [])

    .value('fonts', [{name: 'Source Serif Pro'}, {name: 'Source Sans Pro'}])

    .value('fontWeights', [{weight: '400'}, {weight: '500'}, {weight: '600'}])

    .value('objAnchors', {
      size: 10,
      points: [
        {coordinate: 'nw', x: 0, y: 0},
        {coordinate: 'n', x: 0.5, y: 0},
        {coordinate: 'ne', x: 1, y: 0},
        {coordinate: 'w', x: 0, y: 0.5},
        {coordinate: 'e', x: 1, y: 0.5},
        {coordinate: 'sw', x: 0, y: 1},
        {coordinate: 's', x: 0.5, y: 1},
        {coordinate: 'se', x: 1, y: 1}
      ]
    })

    .value('colors', [
      '#f6f8fb', '#e7eaed',
      '#cdd2d9', '#abb3bd',
      '#656e78', '#434a54',
      '#d94656', '#ea5569',
      '#e85945', '#fa6f57',
      '#f5bb4f', '#fecd5f',
      '#8ec159', '#a2d36e',
      '#3fbc9c', '#4fcfae',
      '#42b0d9', '#55c2e8',
      '#4e8bda', '#609eeb',
      '#967dda', '#ac95eb',
      '#d673ad', '#eb89c0'
    ])

    .controller('DocumentsController', [
      '$scope',
      '$state',
      '$location',
      'Restangular',
      'documentServices',
      'authentication',
      'documents',
      'fonts',
      function($scope, $state, $location, Restangular, documentServices, authentication, documents, fonts) {
        var documentsApi = Restangular.all('documents');

        $scope.updateAuthenticationStatus();
        $scope.documents = documents;
        $scope.dpi = 72;
        $scope.iconDpi = 15;
        $scope.newDocumentModalVisible = false;
        $scope.sortFilter = 'name';
        $scope.sortReverse = false;
        $scope.fonts = fonts;

        $scope.updateSortFilter = function(sortFilter, sortReverse) {
          $scope.sortFilter = sortFilter;
          $scope.sortReverse = sortReverse;
        };

        $scope.newDocument = function() {
          documentsApi.post({
            _user: $scope.user._id,
            name: $scope.name,
            width: $scope.width,
            height: $scope.height,
            shapes: []
          }).then(function(res) {
            $scope.newDocumentModal()
            $scope.documents.push(res)
          });
        };

        $scope.viewUser = function() {
          $state.go('pub.user');
        };

        $scope.newDocumentModal = function() {
          $scope.newDocumentModalVisible = !$scope.newDocumentModalVisible;
          $scope.name = "";
          $scope.width = "";
          $scope.height = "";
        };

        $scope.documentIconSize = function(doc) {
          var iconDpi = 15,
            iconWidth = Math.max(Math.min(doc.width * iconDpi, 200), 30),
            iconHeight = (iconWidth / doc.width) * doc.height;

          return {width: iconWidth + 'px', height: iconHeight + 'px'};
        };
      }
    ])

    .controller('DocumentController', [
      '$scope',
      '$state',
      '$window',
      '$timeout',
      'documentServices',
      'doc',
      function($scope, $state, $window, $timeout, documentServices, doc) {
        $scope.doc = doc;
        $scope.selectedObj = null;
        $scope.showCanvasGrid = true;
        $scope.snapToGrid = true;
        $scope.currentInspector = 'document';
        $scope.clipboard = null;
        $scope.showInspector = true;
        $scope.zoomLevel = 1;

        $scope.updateDocument = function(closeDocumentView) {
          $scope.doc.put();

          if (closeDocumentView) {
            var idx = _.findIndex($scope.documents, { _id : $scope.doc._id });
            $scope.documents[idx] = $scope.doc;
            $scope.showAllDocuments();
          }
        };

        $scope.showAllDocuments = function() {
          $state.go('pub.documents');
        };

        $scope.shiftLayer = function(offset) {
          $scope.doc.shapes.moveElement($scope.selectedObj, offset);
        };

        $scope.toggleCanvasGrid = function() {
          $scope.showCanvasGrid = !$scope.showCanvasGrid;
        };

        $scope.toggleSnapToGrid = function() {
          $scope.snapToGrid = !$scope.snapToGrid;
        };

        $scope.downloadPdf = function() {
          $scope.doc.put().then(function() {
            var documentPdfRoute = '/documents/' + $scope.doc._id + '/pdf';
            $window.location = documentPdfRoute;
          });
        };

        $scope.deleteDocument = function() {
          _.remove($scope.documents, function(doc) {
            return doc._id === $scope.doc._id;
          })
          $scope.doc.remove();
          $scope.deleteModal();
          $scope.showAllDocuments();
        };

        $scope.toggleInspector = function(inspector) {
          $scope.currentInspector = inspector;
        };

        $scope.svgObjectSelected = function(obj) {
          $scope.selectedObj = obj;

          if (!obj && $scope.currentInspector === 'color') {
            $scope.toggleInspector('shape');
          }
        };

        $scope.cutObj = function() {
          $scope.clipboard = angular.copy($scope.selectedObj);
          var objIdx = $scope.doc.shapes.indexOf($scope.selectedObj);
          $scope.doc.shapes.splice(objIdx, 1);
          $scope.selectedObj = null;
        };

        $scope.deleteObj = function() {
          var objIdx = $scope.doc.shapes.indexOf($scope.selectedObj);
          $scope.doc.shapes.splice(objIdx, 1);
          $scope.selectedObj = null;
        };

        $scope.copyObj = function() {
          $scope.clipboard = angular.copy($scope.selectedObj);
        };

        $scope.pasteObj = function() {
          var duplicateObj = angular.copy($scope.clipboard);
          duplicateObj._id = undefined;
          duplicateObj.x += 0.25;
          duplicateObj.y += 0.25;
          $scope.doc.shapes.push(duplicateObj);
        };

        $scope.updateColor = function(color, property) {
          $scope.selectedObj[property] = color;
        };

        $scope.addObject = function(objType) {
          $scope.doc.shapes.push(documentServices.newShape(objType));
        };
      }
    ])

    .controller('DocumentCanvasController', [
      '$scope',
      'documentServices',
      'objAnchors',
      function($scope, documentServices, objAnchors) {
        $scope.objAnchors = objAnchors;

        $scope.xAxisRange = function() {
          return _.range($scope.doc.width / 0.25);
        };

        $scope.yAxisRange = function() {
          return _.range($scope.doc.height / 0.25);
        };

        $scope.unitDivider = function() {
          return ($scope.zoomLevel > 1) ? 0.5 : 1;
        };
      }
    ])
}());
<|endoftext|>


<commit_before>var http = require('http');
var fs = require('fs');

var server = http.createServer(function (req, res) {
  fs.readFile('cookie.html', function(err, content) {
    if (!err) {
      var token = new Buffer(Math.random() * 0xC00//KIE
                             + "Here's a cookie as my token of appreciation!"
                             + new Date().getTime()).toString('base64');

      res.writeHead(201, {
        'Content-Length': content.length,
        'Content-Type': 'text/html',
        'Set-Cookie': 'appreciationToken="' + token + '"'
      });

      res.end(content);
    } else {
      res.writeHead(500);
      res.end();
    }
  });
}).listen('0xC00')//KIE
<commit_msg>Fix port<commit_after>var http = require('http');
var fs = require('fs');

var server = http.createServer(function (req, res) {
  fs.readFile('cookie.html', function(err, content) {
    if (!err) {
      var token = new Buffer(Math.random() * 0xC00//KIE
                             + "Here's a cookie as my token of appreciation!"
                             + new Date().getTime()).toString('base64');

      res.writeHead(201, {
        'Content-Length': content.length,
        'Content-Type': 'text/html',
        'Set-Cookie': 'appreciationToken="' + token + '"'
      });

      res.end(content);
    } else {
      res.writeHead(500);
      res.end();
    }
  });
}).listen(8072)
<|endoftext|>


<commit_before>
import config from '../config'

const logLevels = {}
logLevels.LOG = 'log'
logLevels.DEBUG = 'debug'
logLevels.INFO = 'info'
logLevels.WARN = 'warn'
logLevels.ERROR = 'error'
logLevels.FATAL = 'fatal'
const logLevelsOrder = [
  logLevels.DEBUG,
  logLevels.LOG,
  logLevels.INFO,
  logLevels.WARN,
  logLevels.ERROR,
  logLevels.FATAL
]

// TODO: set up logging via Sentry.
const logger = {}

logger.log = (msg) => {
  log(msg, logLevels.LOG)
}

logger.debug = (msg) => {
  log(msg, logLevels.DEBUG)
}

logger.info = (msg) => {
  log(msg, logLevels.INFO)
}

logger.warn = (msg) => {
  log(msg, logLevels.WARN)
}

logger.error = (msg) => {
  log(msg, logLevels.ERROR)
}

logger.fatal = (msg) => {
  log(msg, logLevels.FATAL)
}

/*
 * Log the error, adding the error ID to the error message.
 * @param {object} err - The error.
 * @param {string} errId - The error ID
 * @return {null}
 */
export const logErrorWithId = (err, errId) => {
  err.message = `${err.message}: Error ID ${errId}`
  logger.error(err)
}

export const shouldLog = (logLevel, globalLogLevel) => {
  return (
    logLevelsOrder.indexOf(logLevel) >=
    logLevelsOrder.indexOf(globalLogLevel)
  )
}

const log = (msg, logLevel) => {
  if (!shouldLog(logLevel, config.LOG_LEVEL)) { return }
  console.log(msg)
}

export default logger
<commit_msg>Lint fix.<commit_after>
import config from '../config'

const logLevels = {}
logLevels.LOG = 'log'
logLevels.DEBUG = 'debug'
logLevels.INFO = 'info'
logLevels.WARN = 'warn'
logLevels.ERROR = 'error'
logLevels.FATAL = 'fatal'
const logLevelsOrder = [
  logLevels.DEBUG,
  logLevels.LOG,
  logLevels.INFO,
  logLevels.WARN,
  logLevels.ERROR,
  logLevels.FATAL
]

// TODO: set up logging via Sentry.
const logger = {}

logger.log = (msg) => {
  log(msg, logLevels.LOG)
}

logger.debug = (msg) => {
  log(msg, logLevels.DEBUG)
}

logger.info = (msg) => {
  log(msg, logLevels.INFO)
}

logger.warn = (msg) => {
  log(msg, logLevels.WARN)
}

logger.error = (msg) => {
  log(msg, logLevels.ERROR)
}

logger.fatal = (msg) => {
  log(msg, logLevels.FATAL)
}

/*
 * Log the error, adding the error ID to the error message.
 * @param {object} err - The error.
 * @param {string} errId - The error ID
 * @return {null}
 */
export const logErrorWithId = (err, errId) => {
  err.message = `${err.message}: Error ID ${errId}`
  logger.error(err)
}

export const shouldLog = (logLevel, globalLogLevel) => {
  return (
    logLevelsOrder.indexOf(logLevel) >=
    logLevelsOrder.indexOf(globalLogLevel)
  )
}

const log = (msg, logLevel) => {
  if (!shouldLog(logLevel, config.LOG_LEVEL)) { return }
  console.log(msg)
}

export default logger
<|endoftext|>


<commit_before>/*! CSS3 Finalize - v1.1 - 2013-03-11 - Grid Layout Polyfill
* https://github.com/codler/Grid-Layout-Polyfill
* Copyright (c) 2013 Han Lin Yap http://yap.nu; http://creativecommons.org/licenses/by-sa/3.0/ */
function cssTextToObj(text) {
	var block = text.split(/({[^{}]*})/);

	// fixes recursive block at end
	if (block[block.length - 1].indexOf('}') == -1) {
		block.pop();
	}
	var objCss = [];
	var recusiveBlock = false;
	var t;
	var tt = 0;
	var ttt;
	var i = 0;
	while (i < block.length) {
		if (i % 2 === 0) {
			var selector = $.trim(block[i]);
			if (recusiveBlock) {
				if (selector.indexOf('}') != -1) {
					selector = selector.substr(1);
					block[i] = selector;

					ttt = block.splice(tt, i - tt);
					ttt.shift();
					ttt.unshift(t[1]);
					objCss[objCss.length - 1].attributes = cssTextToObj(ttt.join(''));
					recusiveBlock = false;
					i = tt;
					continue;
				}
			} else {

				if (selector.indexOf('{') != -1) {
					t = selector.split('{');
					selector = $.trim(t[0]);
					recusiveBlock = true;
					tt = i;
				}
				if (selector !== "") {
					objCss.push({
						'selector': selector
					});
				}
			}
		} else {
			if (!recusiveBlock) {
				objCss[objCss.length - 1].attributes = cssTextAttributeToObj(block[i].substr(1, block[i].length - 2));
			}
		}
		i++;
	}
	return objCss;
}

function cssTextAttributeToObj(text) {
	// Data URI fix
	var attribute;
	text = text.replace(/url\(([^)]+)\)/g, function (url) {
		return url.replace(/;/g, '[cssFinalize]');
	});
	attribute = text.split(/(:[^;]*;?)/);

	attribute.pop();
	var objAttribute = {};
	$.map(attribute, function (n, i) {
		if (i % 2 == 1) {
			objAttribute[$.trim(attribute[i - 1])] = $.trim(n.substr(1).replace(';', '').replace(/url\(([^)]+)\)/g, function (url) {
				return url.replace(/\[cssFinalize\]/g, ';');
			}));
		}
	});
	return objAttribute;
}

jQuery(function ($) {

	if (document.createElement('div').style['msGridRowAlign'] === '') {
		return false;
	}

	var reSelectorTag = /(^|\s)(?:\w+)/g;
	var reSelectorClass = /\.[\w\d_-]+/g;
	var reSelectorId = /#[\w\d_-]+/g;
	var getCSSRuleSpecificity = function (selector) {
		var match = selector.match(reSelectorTag);
		var tagCount = match ? match.length : 0;

		match = selector.match(reSelectorClass);
		var classCount = match ? match.length : 0;

		match = selector.match(reSelectorId);
		var idCount = match ? match.length : 0;

		return tagCount + 10 * classCount + 100 * idCount;
	};

	console.clear();

	var objCss = cssTextToObj($('style').text());
	console.log(objCss);

	/* { selector, attributes, tracks : ([index-x/row][index-y/col] : { x, y }) } */
	var grids = findGrids(objCss);
	console.log(grids);

	function findGrids(objCss) {
		var grids = [];
		$.each(objCss, function (i, block) {
			if (block.attributes) {
				if (block.attributes.display == '-ms-grid') {
					grids.push({
						selector: block.selector,
						attributes: block.attributes,
						tracks: extractTracks(block.attributes)
					});

					//grids.push(block);
				}
			}
		});
		return grids;
	}

	function extractTracks(attrs) {
		var cols = attrs['-ms-grid-columns'].split(' '),
			rows = attrs['-ms-grid-rows'].split(' ');
		var tracks = [];
		$.each(rows, function (x, rv) {
			tracks[x] = [];
			$.each(cols, function (y, cv) {
				tracks[x][y] = {
					x: cv,
					y: rv
				}
			});
		})
		return tracks;
	}

	// apply css
	$.each(grids, function (i, block) {
		var gridSize = calculateTrackSpanLength(block.tracks, 1, 1, block.tracks.length, block.tracks[0].length);
		$(block.selector).css({
			'position' : 'relative',
			'box-sizing': 'border-box',
			width: (block.attributes.display == '-ms-grid') ? '100%' : gridSize.x,
			height: gridSize.y
		});

		/*block.tracks = */normalizeFractionWidth($(block.selector).outerWidth(), block.tracks);
		normalizeFractionHeight($(block.selector).outerHeight(), block.tracks);

		console.log($(block.selector).outerWidth());
		console.log($(block.selector).outerHeight());
		console.log(block.tracks);

		$(block.selector).children().css({
			'box-sizing': 'border-box',
				'position': 'absolute',
			top: 0,
			left: 0,
			width: block.tracks[0][0].x,
			height: block.tracks[0][0].y
		}).each(function (i, e) {
			var gridItem = $(this);

			var selectors = findDefinedSelectors(gridItem);

			// sort specify
			selectors.sort(function(a, b) {
				a = getCSSRuleSpecificity(a)
				b = getCSSRuleSpecificity(b)
				if (a < b) {
					 return -1;
				} else if(a > b) {
					 return 1;
				} else {
					 return 0;
				}
			});
			console.log(selectors);
			// TODO: merge all attr to find other same attributes

			var attributes = getAttributesBySelector(objCss, selectors.pop());
			if (!attributes) return true;

			var row = attributes['-ms-grid-row'] || 1;
			var column = attributes['-ms-grid-column'] || 1;
			var columnSpan = attributes['-ms-grid-column-span'] || 1;
			var rowSpan = attributes['-ms-grid-row-span'] || 1;

			var size = calculateTrackSpanLength(block.tracks, row, column, rowSpan, columnSpan);
			var pos = calculateTrackSpanLength(block.tracks, 1, 1, row - 1, column - 1);
			console.log(row, column, columnSpan, rowSpan);
			console.log(size);
			console.log(pos);
			$(this).css({
				top: pos.y,
				left: pos.x,
				width: size.x,
				height: size.y
			})
		});

	});

	function normalizeFractionWidth(width, tracks) {

		for (var r = 0; r < tracks.length; r++) {
			var fractions = [];
			var totalWidthWithoutFractions = width;
			for (var c = 0; c < tracks[r].length; c++) {
				if (tracks[r][c].x.indexOf('fr') !== -1) {
					fractions.push(parseFloat(tracks[r][c].x));
					//console.log(tracks[r]);
				} else if (tracks[r][c].x.indexOf('px') !== -1) {
					totalWidthWithoutFractions -= parseFloat(tracks[r][c].x);
				}
			}

			// TODO: reduce do not exist in IE8 and lower.
			var sumFraction = fractions.reduce(function(a, b) {
			    return parseInt(a, 10) + parseInt(b, 10);
			}, 0);

			for (var c = 0; c < tracks[r].length; c++) {
				if (tracks[r][c].x.indexOf('fr') !== -1) {
					//console.log(totalWidthWithoutFractions, sumFraction, parseFloat(tracks[r][c].x));
					tracks[r][c].x = '' + totalWidthWithoutFractions / (sumFraction / parseFloat(tracks[r][c].x));
				}
			}

		};
	}

	function normalizeFractionHeight(height, tracks) {

		for (var c = 0; c < tracks[0].length; c++) {
			var fractions = [];
			var totalHeightWithoutFractions = height;
			for (var r = 0; r < tracks.length; r++) {
				if (tracks[r][c].y.indexOf('fr') !== -1) {
					fractions.push(parseFloat(tracks[r][c].y));
					//console.log(tracks[r]);
				} else if (tracks[r][c].y.indexOf('px') !== -1) {
					console.log(tracks[r][c].y);
					totalHeightWithoutFractions -= parseFloat(tracks[r][c].y);
				}
			}

			// TODO: reduce do not exist in IE8 and lower.
			var sumFraction = fractions.reduce(function(a, b) {
			    return parseInt(a, 10) + parseInt(b, 10);
			}, 0);

			for (var r = 0; r < tracks.length; r++) {
				if (tracks[r][c].y.indexOf('fr') !== -1) {
					console.log(totalHeightWithoutFractions, sumFraction, parseFloat(tracks[r][c].y));
					tracks[r][c].y = '' + totalHeightWithoutFractions / (sumFraction / parseFloat(tracks[r][c].y));
				}
			}

		};
	}

	function getAttributesBySelector(objCss, selector) {
		var found;
		$.each(objCss, function (i, block) {
			if (block.selector == selector) {
				found = block.attributes;
				return false;
			}
		})
		return found;
	}

	function calculateTrackSpanLength(tracks, row, column, rowSpan, columnSpan) {
		row--;
		column--;
		var length = {
			x: 0,
			y: 0
		};
		for (var r = 0; r < rowSpan; r++) {
			// TODO calc "fr"-unit
			if (tracks[row + r][column].y.indexOf('fr') === -1) {
				length.y += parseFloat(tracks[row + r][column].y);
			}
		}
		for (var c = 0; c < columnSpan; c++) {
			// TODO calc "fr"-unit
			if (tracks[row][column + c].x.indexOf('fr') === -1) {
				length.x += parseFloat(tracks[row][column + c].x);
			}

		}
		return length;
	}

	function findDefinedSelectors(element) {
		var selectors = [];
		for (var x = 0; x < document.styleSheets.length; x++) {
			var rules = document.styleSheets[x].cssRules;
			for (var i = 0; i < rules.length; i++) {

				try {
					if (element.is(rules[i].selectorText)) {
						selectors.push(rules[i].selectorText);
					}
				} catch (e) {}
			}
		}
		return selectors;
	}
});<commit_msg>Fractions on -ms-grid-rows<commit_after>/*! CSS3 Finalize - v1.1 - 2013-03-11 - Grid Layout Polyfill
* https://github.com/codler/Grid-Layout-Polyfill
* Copyright (c) 2013 Han Lin Yap http://yap.nu; http://creativecommons.org/licenses/by-sa/3.0/ */
// Avoid `console` errors in browsers that lack a console.
(function() {
    var method;
    var noop = function () {};
    var methods = [
        'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
        'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
        'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
        'timeStamp', 'trace', 'warn'
    ];
    var length = methods.length;
    var console = (window.console = window.console || {});

    while (length--) {
        method = methods[length];

        // Only stub undefined methods.
        if (!console[method]) {
            console[method] = noop;
        }
    }
}());
if (!Array.prototype.reduce) {

	Array.prototype.reduce = function(callbackfn /*, initialValue */) {

		// step 1
		if (this == null) {
			throw new TypeError("can't convert " + this + " to object");
		}
		var O = Object(this);

		// steps 2 & 3
		var len = O.length >>> 0;

		// step 4
		if (typeof callbackfn != "function") {
			throw new TypeError(callbackfn + " is not a function");
		}

		// step 5
		if (len === 0 && arguments.length < 2) {
			throw new TypeError('reduce of empty array with no initial value');
		}

		// step 6
		var k = 0;

		// step 7
		var accumulator;
		if (arguments.length > 1) {
			accumulator = arguments[1];
		}
		// step 8
		else {
			var kPresent = false;
			while ((!kPresent) && (k < len)) {
				kPresent = k in O;
				if (kPresent) {
					accumulator = O[k];
				}
				k++;
			}
			if (!kPresent) {
				throw new TypeError('reduce of empty array with no initial value');
			}
		}

		// step 9
		while (k < len) {
			if (k in O) {
				accumulator = callbackfn.call(undefined, accumulator, O[k], k, O);
			}
			k++;
		}

		// step 10
		return accumulator;
	};

}
function cssTextToObj(text) {
	var block = text.split(/({[^{}]*})/);

	// fixes recursive block at end
	if (block[block.length - 1].indexOf('}') == -1) {
		block.pop();
	}
	var objCss = [];
	var recusiveBlock = false;
	var t;
	var tt = 0;
	var ttt;
	var i = 0;
	while (i < block.length) {
		if (i % 2 === 0) {
			var selector = $.trim(block[i]);
			if (recusiveBlock) {
				if (selector.indexOf('}') != -1) {
					selector = selector.substr(1);
					block[i] = selector;

					ttt = block.splice(tt, i - tt);
					ttt.shift();
					ttt.unshift(t[1]);
					objCss[objCss.length - 1].attributes = cssTextToObj(ttt.join(''));
					recusiveBlock = false;
					i = tt;
					continue;
				}
			} else {

				if (selector.indexOf('{') != -1) {
					t = selector.split('{');
					selector = $.trim(t[0]);
					recusiveBlock = true;
					tt = i;
				}
				if (selector !== "") {
					objCss.push({
						'selector': selector
					});
				}
			}
		} else {
			if (!recusiveBlock) {
				objCss[objCss.length - 1].attributes = cssTextAttributeToObj(block[i].substr(1, block[i].length - 2));
			}
		}
		i++;
	}
	return objCss;
}

function cssTextAttributeToObj(text) {
	// Data URI fix
	var attribute;
	text = text.replace(/url\(([^)]+)\)/g, function (url) {
		return url.replace(/;/g, '[cssFinalize]');
	});
	attribute = text.split(/(:[^;]*;?)/);

	attribute.pop();
	var objAttribute = {};
	$.map(attribute, function (n, i) {
		if (i % 2 == 1) {
			objAttribute[$.trim(attribute[i - 1])] = $.trim(n.substr(1).replace(';', '').replace(/url\(([^)]+)\)/g, function (url) {
				return url.replace(/\[cssFinalize\]/g, ';');
			}));
		}
	});
	return objAttribute;
}

jQuery(function ($) {

	if (document.createElement('div').style['msGridRowAlign'] === '') {
		return false;
	}

	var reSelectorTag = /(^|\s)(?:\w+)/g;
	var reSelectorClass = /\.[\w\d_-]+/g;
	var reSelectorId = /#[\w\d_-]+/g;
	var getCSSRuleSpecificity = function (selector) {
		var match = selector.match(reSelectorTag);
		var tagCount = match ? match.length : 0;

		match = selector.match(reSelectorClass);
		var classCount = match ? match.length : 0;

		match = selector.match(reSelectorId);
		var idCount = match ? match.length : 0;

		return tagCount + 10 * classCount + 100 * idCount;
	};

	console.clear();

	var objCss = cssTextToObj($('style').html());
	console.log(objCss);

	/* { selector, attributes, tracks : ([index-x/row][index-y/col] : { x, y }) } */
	var grids = findGrids(objCss);
	console.log(grids);

	function findGrids(objCss) {
		var grids = [];
		$.each(objCss, function (i, block) {
			if (block.attributes) {
				if (block.attributes.display == '-ms-grid') {
					grids.push({
						selector: block.selector,
						attributes: block.attributes,
						tracks: extractTracks(block.attributes)
					});

					//grids.push(block);
				}
			}
		});
		return grids;
	}

	function extractTracks(attrs) {
		var cols = attrs['-ms-grid-columns'].split(' '),
			rows = attrs['-ms-grid-rows'].split(' ');
		var tracks = [];
		$.each(rows, function (x, rv) {
			tracks[x] = [];
			$.each(cols, function (y, cv) {
				tracks[x][y] = {
					x: cv,
					y: rv
				}
			});
		})
		return tracks;
	}

	// apply css
	$.each(grids, function (i, block) {
		var gridSize = calculateTrackSpanLength(block.tracks, 1, 1, block.tracks.length, block.tracks[0].length);
		$(block.selector).css({
			'position' : 'relative',
			'box-sizing': 'border-box',
			width: (block.attributes.display == '-ms-grid') ? '100%' : gridSize.x,
			height: gridSize.y
		});

		/*block.tracks = */normalizeFractionWidth($(block.selector).outerWidth(), block.tracks);

		console.log($(block.selector).outerWidth());
		console.log($(block.selector).outerHeight());
		console.log(block.tracks);

		$(block.selector).children().css({
			'box-sizing': 'border-box',
				'position': 'absolute',
			top: 0,
			left: 0,
			width: block.tracks[0][0].x,
			height: block.tracks[0][0].y
		}).each(function (i, e) {
			var gridItem = $(this);

			var selectors = findDefinedSelectors(gridItem);

			// sort specify
			selectors.sort(function(a, b) {
				a = getCSSRuleSpecificity(a)
				b = getCSSRuleSpecificity(b)
				if (a < b) {
					 return -1;
				} else if(a > b) {
					 return 1;
				} else {
					 return 0;
				}
			});
			console.log(selectors);
			// TODO: merge all attr to find other same attributes

			var attributes = getAttributesBySelector(objCss, selectors.pop());
			if (!attributes) return true;

			var row = attributes['-ms-grid-row'] || 1;
			var column = attributes['-ms-grid-column'] || 1;

			block.tracks[row-1][column-1].item = gridItem;
		});

		$(block.selector).children().css({
			'box-sizing': 'border-box',
				'position': 'absolute',
			top: 0,
			left: 0,
			width: block.tracks[0][0].x,
			height: block.tracks[0][0].y
		}).each(function (i, e) {
			var gridItem = $(this);

			var selectors = findDefinedSelectors(gridItem);

			// sort specify
			selectors.sort(function(a, b) {
				a = getCSSRuleSpecificity(a)
				b = getCSSRuleSpecificity(b)
				if (a < b) {
					 return -1;
				} else if(a > b) {
					 return 1;
				} else {
					 return 0;
				}
			});
			console.log(selectors);
			// TODO: merge all attr to find other same attributes

			var attributes = getAttributesBySelector(objCss, selectors.pop());
			if (!attributes) return true;

			var row = attributes['-ms-grid-row'] || 1;
			var column = attributes['-ms-grid-column'] || 1;
			var columnSpan = attributes['-ms-grid-column-span'] || 1;
			var rowSpan = attributes['-ms-grid-row-span'] || 1;

			var size = calculateTrackSpanLength(block.tracks, row, column, rowSpan, columnSpan);
			var pos = calculateTrackSpanLength(block.tracks, 1, 1, row - 1, column - 1);
			console.log(row, column, columnSpan, rowSpan);
			console.log(size);
			console.log(pos);
			$(this).css({
				//top: pos.y,
				//left: pos.x,
				width: size.x,
				//height: size.y
			})
		})
		var realHeight = normalizeFractionHeight($(block.selector).outerHeight(), block.tracks);
		$(block.selector).css({
			height: realHeight
		});

		$(block.selector).children().each(function (i, e) {
			var gridItem = $(this);

			var selectors = findDefinedSelectors(gridItem);

			// sort specify
			selectors.sort(function(a, b) {
				a = getCSSRuleSpecificity(a)
				b = getCSSRuleSpecificity(b)
				if (a < b) {
					 return -1;
				} else if(a > b) {
					 return 1;
				} else {
					 return 0;
				}
			});
			console.log(selectors);
			// TODO: merge all attr to find other same attributes

			var attributes = getAttributesBySelector(objCss, selectors.pop());
			if (!attributes) return true;

			var row = attributes['-ms-grid-row'] || 1;
			var column = attributes['-ms-grid-column'] || 1;
			var columnSpan = attributes['-ms-grid-column-span'] || 1;
			var rowSpan = attributes['-ms-grid-row-span'] || 1;

			var size = calculateTrackSpanLength(block.tracks, row, column, rowSpan, columnSpan);
			var pos = calculateTrackSpanLength(block.tracks, 1, 1, row - 1, column - 1);
			console.log(row, column, columnSpan, rowSpan);
			console.log(size);
			console.log(pos);
			$(this).css({
				top: pos.y,
				left: pos.x,
				width: size.x,
				height: size.y
			})
		});

	});

	function normalizeFractionWidth(width, tracks) {

		for (var r = 0; r < tracks.length; r++) {
			var fractions = [];
			var totalWidthWithoutFractions = width;
			for (var c = 0; c < tracks[r].length; c++) {
				if (tracks[r][c].x.indexOf('fr') !== -1) {
					fractions.push(parseFloat(tracks[r][c].x));
					//console.log(tracks[r]);
				} else if (tracks[r][c].x.indexOf('px') !== -1) {
					totalWidthWithoutFractions -= parseFloat(tracks[r][c].x);
				}
			}

			// TODO: reduce do not exist in IE8 and lower.
			var sumFraction = fractions.reduce(function(a, b) {
				return parseInt(a, 10) + parseInt(b, 10);
			}, 0);

			for (var c = 0; c < tracks[r].length; c++) {
				if (tracks[r][c].x.indexOf('fr') !== -1) {
					//console.log(totalWidthWithoutFractions, sumFraction, parseFloat(tracks[r][c].x));
					tracks[r][c].x = '' + totalWidthWithoutFractions / (sumFraction / parseFloat(tracks[r][c].x));
				}
			}

		};
	}

	function normalizeFractionHeight(height, tracks) {
		var realHeight = height;
		var readRealHeight = false

		for (var c = 0; c < tracks[0].length; c++) {
			var fractions = [];
			var totalHeightWithoutFractions = height;
			for (var r = 0; r < tracks.length; r++) {
				if (tracks[r][c].y.indexOf('fr') !== -1) {
					fractions.push(parseFloat(tracks[r][c].y));
					//tracks[r][c].fractionY = parseFloat(tracks[r][c].y);
					if (tracks[r][c].item) {
						tracks[r][c].item.height('auto');
						totalHeightWithoutFractions += tracks[r][c].item.outerHeight(true);
						if (!readRealHeight) {
							realHeight += tracks[r][c].item.outerHeight(true);
							readRealHeight = true;
						}
					}
					//console.log(tracks[r]);
				} else if (tracks[r][c].y.indexOf('px') !== -1) {
					console.log(tracks[r][c].y);
					totalHeightWithoutFractions -= parseFloat(tracks[r][c].y);
				}
			}

			// TODO: reduce do not exist in IE8 and lower.
			var sumFraction = fractions.reduce(function(a, b) {
				return parseInt(a, 10) + parseInt(b, 10);
			}, 0);

			for (var r = 0; r < tracks.length; r++) {
				if (tracks[r][c].y.indexOf('fr') !== -1) {
					console.log(totalHeightWithoutFractions, sumFraction, parseFloat(tracks[r][c].y));
					tracks[r][c].y = '' + totalHeightWithoutFractions / (sumFraction / parseFloat(tracks[r][c].y));
				}
			}

		};
		return realHeight;
	}

	function getAttributesBySelector(objCss, selector) {
		var found;
		$.each(objCss, function (i, block) {
			if (block.selector == selector) {
				found = block.attributes;
				return false;
			}
		})
		return found;
	}

	function calculateTrackSpanLength(tracks, row, column, rowSpan, columnSpan) {
		row--;
		column--;
		var length = {
			x: 0,
			y: 0
		};
		for (var r = 0; r < rowSpan; r++) {
			// TODO calc "fr"-unit
			if (tracks[row + r][column].y.indexOf('fr') === -1) {
				length.y += parseFloat(tracks[row + r][column].y);
			}
		}
		for (var c = 0; c < columnSpan; c++) {
			// TODO calc "fr"-unit
			if (tracks[row][column + c].x.indexOf('fr') === -1) {
				length.x += parseFloat(tracks[row][column + c].x);
			}

		}
		return length;
	}

	function findDefinedSelectors(element) {
		var selectors = [];
		for (var x = 0; x < document.styleSheets.length; x++) {
			var rules = document.styleSheets[x].cssRules;
			for (var i = 0; i < rules.length; i++) {

				try {
					if (element.is(rules[i].selectorText)) {
						selectors.push(rules[i].selectorText);
					}
				} catch (e) {}
			}
		}
		return selectors;
	}
});<|endoftext|>


<commit_before>import React, {PropTypes} from 'react'
import {Button, Table} from 'react-bootstrap'
import autobind from 'autobind-decorator'

import ValidatableFormWrapper from 'components/ValidatableFormWrapper'
import Fieldset from 'components/Fieldset'
import Form from 'components/Form'
import ButtonToggleGroup from 'components/ButtonToggleGroup'
import Autocomplete from 'components/Autocomplete'
import PoamsSelector from 'components/PoamsSelector'
import LinkTo from 'components/LinkTo'
import History from 'components/History'
import Messages from 'components/Messages'

import API from 'api'
import dict from 'dictionary'
import {Position, Organization} from 'models'

import DictionaryLabelField from '../../HOC/DictionaryLabelField'

import REMOVE_ICON from 'resources/delete.png'

export default class OrganizationForm extends ValidatableFormWrapper {
	static propTypes = {
		organization: PropTypes.object,
		edit: PropTypes.bool,
	}

	static contextTypes = {
		currentUser: PropTypes.object.isRequired,
	}

	constructor(props) {
		super(props)
		this.state = {
			error: null,
		}
	}

	render() {
		let {organization, edit} = this.props
		let {approvalSteps} = organization
		let currentUser = this.context.currentUser
		let isAdmin = currentUser && currentUser.isAdmin()
		let isPrincipalOrg = (organization.type === "PRINCIPAL_ORG")
		const {ValidatableForm, RequiredField} = this
		let [labelLongName, placeholderLongName, labelIdentificationCode, placeholderIdentificationCode] = isPrincipalOrg
			? [dict.lookup('PRINCIPAL_ORG_LABEL_LONGNAME'),
			   dict.lookup('PRINCIPAL_ORG_PLACEHOLDER_LONGNAME'),
			   dict.lookup('PRINCIPAL_ORG_LABEL_IDENTIFICATIONCODE'),
			   dict.lookup('PRINCIPAL_ORG_PLACEHOLDER_IDENTIFICATIONCODE')]
			: [dict.lookup('ADVISOR_ORG_LABEL_LONGNAME'),
			   dict.lookup('ADVISOR_ORG_PLACEHOLDER_LONGNAME'),
			   dict.lookup('ADVISOR_ORG_LABEL_IDENTIFICATIONCODE'),
			   dict.lookup('ADVISOR_ORG_PLACEHOLDER_IDENTIFICATIONCODE')]

		const IdentificationCodeFieldWithLabel = DictionaryLabelField(labelIdentificationCode)(Form.Field)
		return <ValidatableForm formFor={organization}
			onChange={this.onChange}
			onSubmit={this.onSubmit}
			submitText="Save organization"
				   horizontal>

			<Messages error={this.state.error} />

			<Fieldset title={edit ? `Edit Organization ${organization.shortName}` : "Create a new Organization"}>
				<Form.Field id="type">
					<ButtonToggleGroup>
						<Button id="advisorOrgButton" disabled={!isAdmin} value="ADVISOR_ORG">{dict.lookup('ADVISOR_ORG_NAME')}</Button>
						<Button id="principalOrgButton" disabled={!isAdmin} value="PRINCIPAL_ORG">{dict.lookup('PRINCIPAL_ORG_NAME')}</Button>
					</ButtonToggleGroup>
				</Form.Field>

				<Form.Field id="parentOrg" label="Parent organization">
					<Autocomplete valueKey="shortName" disabled={isPrincipalOrg && !isAdmin}
						placeholder="Start typing to search for a higher level organization..."
						url="/api/organizations/search"
						queryParams={{type: organization.type}}
					/>
				</Form.Field>

				<RequiredField id="shortName" label="Name" placeholder="e.g. EF1.1" />
				<Form.Field id="longName" disabled={isPrincipalOrg && !isAdmin} label={labelLongName} placeholder={placeholderLongName} />
				 <IdentificationCodeFieldWithLabel id="identificationCode" disabled={!isAdmin} placeholder={placeholderIdentificationCode} />
			</Fieldset>

			{organization.isAdvisorOrg() &&

				<Fieldset title="Approval process">
					<Button className="pull-right" onClick={this.addApprovalStep} bsStyle="primary" id="addApprovalStepButton" >
						Add an Approval Step
					</Button>

					{approvalSteps && approvalSteps.map((step, index) =>
						this.renderApprovalStep(step, index)
					)}
				</Fieldset>

				<PoamsSelector poams={organization.poams} onChange={this.onChange} />

}
		</ValidatableForm>
	}

	renderApprovalStep(step, index) {
		const approvers = step.approvers
		const { RequiredField } = this

		return <Fieldset title={`Step ${index + 1}`} key={index}>
			<Button className="pull-right" onClick={this.removeApprovalStep.bind(this, index)}>
				X
			</Button>

			<RequiredField id="approvalStepName"
				label="Step name"
				value={step.name}
				onChange={(event) => this.setStepName(index, event)} />

			<Form.Field id="addApprover" label="Add an approver" value={approvers}>
				<Autocomplete valueKey="name"
					placeholder="Search for the approver's position"
					objectType={Position}
					fields="id, name, code, type, person { id, name, rank }"
					template={pos => {
						let components = []
						pos.person && components.push(pos.person.name)
						pos.name && components.push(pos.name)
						pos.code && components.push(pos.code)
						return {components.join(' - ')}
					}}
					queryParams={{type: [Position.TYPE.ADVISOR, Position.TYPE.SUPER_USER, Position.TYPE.ADMINISTRATOR], matchPersonName: true}}
					onChange={this.addApprover.bind(this, index)}
					clearOnSelect={true} />

							Name
							Position

						{approvers.map((approver, approverIndex) =>
							<tr key={approver.id} id={`step_${index}_approver_${approverIndex}`} >
								<LinkTo person={approver.person} target="_blank" />
								<LinkTo position={approver} target="_blank" />

						)}

			</Form.Field>
		</Fieldset>
	}

	@autobind
	addApprover(index, position) {
		if (!position || !position.id) {
			return
		}

		let org = this.props.organization
		let step = org.approvalSteps[index]
		let newApprovers = step.approvers.slice()
		newApprovers.push(position)
		step.approvers = newApprovers

		this.onChange()
	}

	@autobind
	removeApprover(approver, index) {
		let step = this.props.organization.approvalSteps[index]
		let approvers = step.approvers
		let approverIndex = approvers.findIndex(m => m.id === approver.id )

		if (approverIndex !== -1) {
			approvers.splice(approverIndex, 1)
			this.onChange()
		}
	}

	@autobind
	setStepName(index, event) {
		let name = event && event.target ? event.target.value : event
		let step = this.props.organization.approvalSteps[index]
		step.name = name

		this.onChange()
	}

	@autobind
	addApprovalStep() {
		let org = this.props.organization
		let approvalSteps = org.approvalSteps || []
		approvalSteps.push({name: '', approvers: []})

		this.onChange()
	}

	@autobind
	removeApprovalStep(index) {
		let steps = this.props.organization.approvalSteps
		steps.splice(index, 1)
		this.onChange()
	}

	@autobind
	onChange() {
		this.forceUpdate()
	}

	@autobind
	onSubmit(event) {
		let organization = Object.without(this.props.organization, 'childrenOrgs', 'positions', 'approvalStepName')
		if (organization.parentOrg) {
			organization.parentOrg = {id: organization.parentOrg.id}
		}

		let url = `/api/organizations/${this.props.edit ? 'update' : 'new'}`
		API.send(url, organization, {disableSubmits: true})
			.then(response => {
				if (response.code) {
					throw response.code
				}

				if (response.id) {
					organization.id = response.id
				}

				History.replace(Organization.pathForEdit(organization), false)
				History.push(Organization.pathFor(organization), {
					success: 'Organization saved successfully',
					skipPageLeaveWarning: true
				})
			}).catch(error => {
				this.setState({error})
				window.scrollTo(0, 0)
			})
	}
}
<commit_msg>NCI-Agency/anet#311 Refactor organization form fields<commit_after>import React, {PropTypes} from 'react'
import {Button, Table} from 'react-bootstrap'
import autobind from 'autobind-decorator'

import ValidatableFormWrapper from 'components/ValidatableFormWrapper'
import Fieldset from 'components/Fieldset'
import Form from 'components/Form'
import ButtonToggleGroup from 'components/ButtonToggleGroup'
import Autocomplete from 'components/Autocomplete'
import PoamsSelector from 'components/PoamsSelector'
import LinkTo from 'components/LinkTo'
import History from 'components/History'
import Messages from 'components/Messages'

import API from 'api'
import dict from 'dictionary'
import {Position, Organization} from 'models'

import DictionaryLabelField from '../../HOC/DictionaryLabelField'

import REMOVE_ICON from 'resources/delete.png'

export default class OrganizationForm extends ValidatableFormWrapper {
	static propTypes = {
		organization: PropTypes.object,
		edit: PropTypes.bool,
	}

	static contextTypes = {
		currentUser: PropTypes.object.isRequired,
	}

	constructor(props) {
		super(props)
		this.state = {
			error: null,
		}
	}

	render() {
		let {organization, edit} = this.props
		let {approvalSteps} = organization
		let currentUser = this.context.currentUser
		let isAdmin = currentUser && currentUser.isAdmin()
		let isPrincipalOrg = (organization.type === "PRINCIPAL_ORG")
		const {ValidatableForm, RequiredField} = this
		const [labelLongName, placeholderLongName, labelIdentificationCode, placeholderIdentificationCode] = isPrincipalOrg
			? [dict.lookup('field_labels').PRINCIPAL_ORG_LABEL_LONGNAME,
			   dict.lookup('PRINCIPAL_ORG_PLACEHOLDER_LONGNAME'),
			   dict.lookup('field_labels').PRINCIPAL_ORG_LABEL_IDENTIFICATIONCODE,
			   dict.lookup('PRINCIPAL_ORG_PLACEHOLDER_IDENTIFICATIONCODE')]
			: [dict.lookup('field_labels').ADVISOR_ORG_LABEL_LONGNAME,
			   dict.lookup('ADVISOR_ORG_PLACEHOLDER_LONGNAME'),
			   dict.lookup('field_labels').ADVISOR_ORG_LABEL_IDENTIFICATIONCODE,
			   dict.lookup('ADVISOR_ORG_PLACEHOLDER_IDENTIFICATIONCODE')]

		const IdentificationCodeFieldWithLabel = DictionaryLabelField(labelIdentificationCode)(Form.Field)
		const LongNameWithLabel = DictionaryLabelField(labelLongName)(Form.Field)

		return <ValidatableForm formFor={organization}
			onChange={this.onChange}
			onSubmit={this.onSubmit}
			submitText="Save organization"
				   horizontal>

			<Messages error={this.state.error} />

			<Fieldset title={edit ? `Edit Organization ${organization.shortName}` : "Create a new Organization"}>
				<Form.Field id="type">
					<ButtonToggleGroup>
						<Button id="advisorOrgButton" disabled={!isAdmin} value="ADVISOR_ORG">{dict.lookup('ADVISOR_ORG_NAME')}</Button>
						<Button id="principalOrgButton" disabled={!isAdmin} value="PRINCIPAL_ORG">{dict.lookup('PRINCIPAL_ORG_NAME')}</Button>
					</ButtonToggleGroup>
				</Form.Field>

				<Form.Field id="parentOrg" label="Parent organization">
					<Autocomplete valueKey="shortName" disabled={isPrincipalOrg && !isAdmin}
						placeholder="Start typing to search for a higher level organization..."
						url="/api/organizations/search"
						queryParams={{type: organization.type}}
					/>
				</Form.Field>

				<RequiredField id="shortName" label="Name" placeholder="e.g. EF1.1" />
				<LongNameWithLabel id="longName" disabled={isPrincipalOrg && !isAdmin} label={labelLongName} placeholder={placeholderLongName} />
				<IdentificationCodeFieldWithLabel id="identificationCode" disabled={!isAdmin} placeholder={placeholderIdentificationCode} />
			</Fieldset>

			{organization.isAdvisorOrg() &&

				<Fieldset title="Approval process">
					<Button className="pull-right" onClick={this.addApprovalStep} bsStyle="primary" id="addApprovalStepButton" >
						Add an Approval Step
					</Button>

					{approvalSteps && approvalSteps.map((step, index) =>
						this.renderApprovalStep(step, index)
					)}
				</Fieldset>

				<PoamsSelector poams={organization.poams} onChange={this.onChange} />

}
		</ValidatableForm>
	}

	renderApprovalStep(step, index) {
		const approvers = step.approvers
		const { RequiredField } = this

		return <Fieldset title={`Step ${index + 1}`} key={index}>
			<Button className="pull-right" onClick={this.removeApprovalStep.bind(this, index)}>
				X
			</Button>

			<RequiredField id="approvalStepName"
				label="Step name"
				value={step.name}
				onChange={(event) => this.setStepName(index, event)} />

			<Form.Field id="addApprover" label="Add an approver" value={approvers}>
				<Autocomplete valueKey="name"
					placeholder="Search for the approver's position"
					objectType={Position}
					fields="id, name, code, type, person { id, name, rank }"
					template={pos => {
						let components = []
						pos.person && components.push(pos.person.name)
						pos.name && components.push(pos.name)
						pos.code && components.push(pos.code)
						return {components.join(' - ')}
					}}
					queryParams={{type: [Position.TYPE.ADVISOR, Position.TYPE.SUPER_USER, Position.TYPE.ADMINISTRATOR], matchPersonName: true}}
					onChange={this.addApprover.bind(this, index)}
					clearOnSelect={true} />

							Name
							Position

						{approvers.map((approver, approverIndex) =>
							<tr key={approver.id} id={`step_${index}_approver_${approverIndex}`} >
								<LinkTo person={approver.person} target="_blank" />
								<LinkTo position={approver} target="_blank" />

						)}

			</Form.Field>
		</Fieldset>
	}

	@autobind
	addApprover(index, position) {
		if (!position || !position.id) {
			return
		}

		let org = this.props.organization
		let step = org.approvalSteps[index]
		let newApprovers = step.approvers.slice()
		newApprovers.push(position)
		step.approvers = newApprovers

		this.onChange()
	}

	@autobind
	removeApprover(approver, index) {
		let step = this.props.organization.approvalSteps[index]
		let approvers = step.approvers
		let approverIndex = approvers.findIndex(m => m.id === approver.id )

		if (approverIndex !== -1) {
			approvers.splice(approverIndex, 1)
			this.onChange()
		}
	}

	@autobind
	setStepName(index, event) {
		let name = event && event.target ? event.target.value : event
		let step = this.props.organization.approvalSteps[index]
		step.name = name

		this.onChange()
	}

	@autobind
	addApprovalStep() {
		let org = this.props.organization
		let approvalSteps = org.approvalSteps || []
		approvalSteps.push({name: '', approvers: []})

		this.onChange()
	}

	@autobind
	removeApprovalStep(index) {
		let steps = this.props.organization.approvalSteps
		steps.splice(index, 1)
		this.onChange()
	}

	@autobind
	onChange() {
		this.forceUpdate()
	}

	@autobind
	onSubmit(event) {
		let organization = Object.without(this.props.organization, 'childrenOrgs', 'positions', 'approvalStepName')
		if (organization.parentOrg) {
			organization.parentOrg = {id: organization.parentOrg.id}
		}

		let url = `/api/organizations/${this.props.edit ? 'update' : 'new'}`
		API.send(url, organization, {disableSubmits: true})
			.then(response => {
				if (response.code) {
					throw response.code
				}

				if (response.id) {
					organization.id = response.id
				}

				History.replace(Organization.pathForEdit(organization), false)
				History.push(Organization.pathFor(organization), {
					success: 'Organization saved successfully',
					skipPageLeaveWarning: true
				})
			}).catch(error => {
				this.setState({error})
				window.scrollTo(0, 0)
			})
	}
}
<|endoftext|>


<commit_before>var argv, http, url;
argv = require("optimist").default("port", 80).alias("p", "port").argv;
http = require("http");
url = require("url");
http.createServer(function (req, res) {
    var parsedUrl, proxyUrl;
    // Enable Cross-Origin Resource Sharing (CORS).
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
    parsedUrl = url.parse(req.url, true);
    if (parsedUrl.query.hasOwnProperty("url")) {
        proxyUrl = url.parse(parsedUrl.query.url);
        var proxyOptions = {
            "host": proxyUrl.host,
            "port": proxyUrl.port || 80,
            "path": proxyUrl.pathname,
            "method": req.method,
            "headers": {}
        };
        // Copy some useful HTTP headers.
        if (req.headers.hasOwnProperty("accept")) proxyOptions.headers["Accept"] = req.headers["accept"];
        // url.parse does not parse localhost correctly.
        if (proxyOptions.host.indexOf("localhost") !== -1) {
            proxyOptions.host = proxyOptions.host.split(":")[0];
        }
        // url.parse does not parse complex pathnames correctly.
        proxyOptions.path += req.url.split(proxyOptions.path)[1];
        var proxyReq = http.request(proxyOptions, function (proxyRes) {
            var resHeaders;
            resHeaders = {};
            if (proxyRes.headers.hasOwnProperty("content-type")) resHeaders["Content-Type"] = proxyRes.headers["content-type"];
            res.writeHead(200, resHeaders);
            // end() is called on res when proxyRes emits end.
            proxyRes.pipe(res);
        });
        proxyReq.on("error", function (e) {
            res.writeHead(500);
            res.end("Captain, we crashed: " + e.code + "\n");
        });
        proxyReq.end();
    } else {
        res.writeHead(400);
        res.end("\"url\", did you pass it?\n");
    }
}).listen(argv.port);
console.log("Server running at http://127.0.0.1:" + argv.port + "/");
<commit_msg>OPTIONS is needed for some non-simple HTTP methods.<commit_after>var argv, http, url;
argv = require("optimist").default("port", 80).alias("p", "port").argv;
http = require("http");
url = require("url");
http.createServer(function (req, res) {
    var parsedUrl, proxyUrl;
    // Enable Cross-Origin Resource Sharing (CORS).
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
    parsedUrl = url.parse(req.url, true);
    if (parsedUrl.query.hasOwnProperty("url")) {
        proxyUrl = url.parse(parsedUrl.query.url);
        var proxyOptions = {
            "host": proxyUrl.host,
            "port": proxyUrl.port || 80,
            "path": proxyUrl.pathname,
            "method": req.method,
            "headers": {}
        };
        // Copy some useful HTTP headers.
        if (req.headers.hasOwnProperty("accept")) proxyOptions.headers["Accept"] = req.headers["accept"];
        // url.parse does not parse localhost correctly.
        if (proxyOptions.host.indexOf("localhost") !== -1) {
            proxyOptions.host = proxyOptions.host.split(":")[0];
        }
        // url.parse does not parse complex pathnames correctly.
        proxyOptions.path += req.url.split(proxyOptions.path)[1];
        var proxyReq = http.request(proxyOptions, function (proxyRes) {
            var resHeaders;
            resHeaders = {};
            if (proxyRes.headers.hasOwnProperty("content-type")) resHeaders["Content-Type"] = proxyRes.headers["content-type"];
            res.writeHead(200, resHeaders);
            // end() is called on res when proxyRes emits end.
            proxyRes.pipe(res);
        });
        proxyReq.on("error", function (e) {
            res.writeHead(500);
            res.end("Captain, we crashed: " + e.code + "\n");
        });
        proxyReq.end();
    } else {
        res.writeHead(400);
        res.end("\"url\", did you pass it?\n");
    }
}).listen(argv.port);
console.log("Server running at http://127.0.0.1:" + argv.port + "/");
<|endoftext|>


<commit_before>const cheerio = require('cheerio');
const iconv = require('iconv-lite');
const fs = require('fs');
const request = require('./request');
const urls = require('./urls');
const course = require('./course');
const folder = require('./folder');
const file = require('./file');
const format = require('./format');

class Course {
  constructor(id, acronym, name) {
    this.id = id;
    this.acronym = acronym;
    this.name = name;
    this.url = urls.courseURL(id);
    this.folders = {};
    this.files = {};
  }

  scrap() {
    this.folders = {};
    this.files = {};
    return new Promise((res, rej) => {
      request({url: this.url, encoding: null}, (err, http, body) => {
        if (err) {
          rej(err);
        }
        console.log(`Parsing ${this.path()}`);
        const newFolders = this.searchFoldersAndFiles(body, this);
        if (newFolders.length === 0) {
          return res(this);
        }
        Promise.all(
          newFolders.map(folder => this.scrapFolder(folder)),
        ).then(() => {
          res(this);
        });
      });
    });
  }

  scrapFolder(folder) {
    return new Promise((res, rej) => {
      request({url: folder.url, encoding: null}, (err, http, body) => {
        if (err) {
          rej(err);
        }
        console.log(`Parsing ${folder.name}`);
        const newFolders = this.searchFoldersAndFiles(body, folder);
        if (newFolders.length === 0) {
          return res();
        }
        Promise.all(
          newFolders.map(folder => this.scrapFolder(folder)),
        ).then(() => {
          res();
        });
      });
    });
  }

  searchFoldersAndFiles(body, parent) {
    const $ = cheerio.load(iconv.decode(body, 'ISO-8859-1'));
    const newFolders = [];
    $('a').each((i, l) => {
      const link = $(l).attr('href');
      const name = $(l).text();
      if (link.indexOf('acc_carp') !== -1) {
        const linkId = link.match(/id_carpeta=\d+/g)[0].split('=')[1];
        if (this.folders[linkId]) {
          return;
        }
        const newFolder = new folder(
          linkId,
          name,
          urls.courseFolderURL(link),
          parent,
        );
        newFolders.push(newFolder);
        this.folders[linkId] = newFolder;
      } else if (link.indexOf('id_archivo') !== -1) {
        const linkId = link.match(/id_archivo=\d+/g)[0].split('=')[1];
        if (this.files[linkId]) {
          return;
        }
        const newFile = new file(
          linkId,
          name,
          urls.courseFileURL(link),
          parent,
        );
        this.files[linkId] = newFile;
      }
    });
    return newFolders;
  }

  path(path = '') {
    const parentPath = (path !== '' && path + '/') || '';
    return parentPath + this.acronym + ' ' + format.nameCleaned(this.name);
  }

  createFolder(path) {
    try {
      fs.mkdirSync(this.path(path));
    } catch (err) {}
  }
}

module.exports = Course;
<commit_msg>Delete stupid require<commit_after>const cheerio = require('cheerio');
const iconv = require('iconv-lite');
const fs = require('fs');
const request = require('./request');
const urls = require('./urls');
const folder = require('./folder');
const file = require('./file');
const format = require('./format');

class Course {
  constructor(id, acronym, name) {
    this.id = id;
    this.acronym = acronym;
    this.name = name;
    this.url = urls.courseURL(id);
    this.folders = {};
    this.files = {};
  }

  scrap() {
    this.folders = {};
    this.files = {};
    return new Promise((res, rej) => {
      request({url: this.url, encoding: null}, (err, http, body) => {
        if (err) {
          rej(err);
        }
        console.log(`Parsing ${this.path()}`);
        const newFolders = this.searchFoldersAndFiles(body, this);
        if (newFolders.length === 0) {
          return res(this);
        }
        Promise.all(
          newFolders.map(folder => this.scrapFolder(folder)),
        ).then(() => {
          res(this);
        });
      });
    });
  }

  scrapFolder(folder) {
    return new Promise((res, rej) => {
      request({url: folder.url, encoding: null}, (err, http, body) => {
        if (err) {
          rej(err);
        }
        console.log(`Parsing ${folder.name}`);
        const newFolders = this.searchFoldersAndFiles(body, folder);
        if (newFolders.length === 0) {
          return res();
        }
        Promise.all(
          newFolders.map(folder => this.scrapFolder(folder)),
        ).then(() => {
          res();
        });
      });
    });
  }

  searchFoldersAndFiles(body, parent) {
    const $ = cheerio.load(iconv.decode(body, 'ISO-8859-1'));
    const newFolders = [];
    $('a').each((i, l) => {
      const link = $(l).attr('href');
      const name = $(l).text();
      if (link.indexOf('acc_carp') !== -1) {
        const linkId = link.match(/id_carpeta=\d+/g)[0].split('=')[1];
        if (this.folders[linkId]) {
          return;
        }
        const newFolder = new folder(
          linkId,
          name,
          urls.courseFolderURL(link),
          parent,
        );
        newFolders.push(newFolder);
        this.folders[linkId] = newFolder;
      } else if (link.indexOf('id_archivo') !== -1) {
        const linkId = link.match(/id_archivo=\d+/g)[0].split('=')[1];
        if (this.files[linkId]) {
          return;
        }
        const newFile = new file(
          linkId,
          name,
          urls.courseFileURL(link),
          parent,
        );
        this.files[linkId] = newFile;
      }
    });
    return newFolders;
  }

  path(path = '') {
    const parentPath = (path !== '' && path + '/') || '';
    return parentPath + this.acronym + ' ' + format.nameCleaned(this.name);
  }

  createFolder(path) {
    try {
      fs.mkdirSync(this.path(path));
    } catch (err) {}
  }
}

module.exports = Course;
<|endoftext|>