<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Spring, animated } from 'react-spring';
import { TimingAnimation, Easing } from 'react-spring/dist/addons.cjs';

// Components
import StepsMainNav from './StepsMainNav';
import StepHeaderNavButton from './StepHeaderNavButton';

class Steps extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentStep: props.currentStep,
      autoSlide: props.autoSlide
    };
  }

  componentDidMount() {
    const { autoSlide, currentStep } = this.state;
    const { steps, autoSlideDelay } = this.props;
    if (autoSlide) {
      this.autoSlideInterval = setInterval(() => {
        if (autoSlide) {
          const nextStep = (currentStep + 1) % steps.length;
          this.setState({ currentStep: nextStep });
        }
      }, autoSlideDelay);
    }
  }

  componentWillUnmount() {
    if (this.autoSlideInterval) {
      clearInterval(this.autoSlideInterval);
    }
  }

  goToStep = (i, stopAutoSliding = true) => {
    if (i < this.props.steps.length && i > -1) {
      if (stopAutoSliding) {
        this.setState({ currentStep: i, autoSlide: false });
      } else {
        this.setState({ currentStep: i });
      }
    }
  };

  createSteps = () => {
    const { vertical, steps } = this.props;
    const numSteps = steps.length;
    const stepsComponents = [];
    const currentStep = this.state.currentStep;
    for (let i = 0; i < numSteps; i += 1) {
      let springTo = { opacity: 0.2 };
      let springFrom = { opacity: 1 };
      if (!vertical) {
        springTo.height = 0;
        springFrom.height = 'auto';
      }
      if (currentStep === i) {
        springTo = { opacity: 1 };
        springFrom = { opacity: 0.2 };
        if (!vertical) {
          springTo.height = 'auto';
          springFrom.height = 200;
        }
      }
      const step = (
        <Spring
          native
          from={springFrom}
          to={springTo}
          key={`animated-step-content-${i}`}
          impl={TimingAnimation}
          config={{ duration: 900, easing: Easing.out(Easing.cubic) }}
        >
          {style => (
            <animated.div className="single-step-content" style={{ ...style }}>

                {steps[i].prevStepBtn && (
                  <StepHeaderNavButton
                    isReachableByKeyboard={i === currentStep}
                    isDisabled={i === 0}
                    targetStep={i - 1}
                    onClick={this.goToStep}
                  >
                    {steps[i].prevStepBtn}
                  </StepHeaderNavButton>
                )}

                {steps[i].headerContent}

                {steps[i].nextStepBtn && (
                  <StepHeaderNavButton
                    isReachableByKeyboard={i === currentStep}
                    isDisabled={i === numSteps - 1}
                    targetStep={i + 1}
                    onClick={this.goToStep}
                  >
                    {steps[i].nextStepBtn}
                  </StepHeaderNavButton>
                )}

              {steps[i].content}
            </animated.div>
          )}
        </Spring>
      );
      stepsComponents.push(step);
    }
    return stepsComponents;
  };

  render() {
    let containerClasses = 'steps-container';
    let springTo;
    const { currentStep } = this.state;
    const { height, pulseNextStep, vertical, steps } = this.props;
    if (vertical) {
      containerClasses += ' vertical';
      springTo = { left: 0, top: `-${currentStep * 100}%` };
    } else {
      springTo = { left: `-${currentStep * 100}%`, top: 0 };
    }
    return (

        <StepsMainNav
          currentStep={currentStep}
          totalNumSteps={steps.length}
          onClick={this.goToStep}
          vertical={vertical}
          pulseNextStep={pulseNextStep}
        />

        <Spring
          native
          to={springTo}
          impl={TimingAnimation}
          config={{ duration: 625, easing: Easing.out(Easing.cubic) }}
        >
          {style => (
            <animated.div className={'steps-content-container'} style={{ ...style }}>
              {this.createSteps()}
            </animated.div>
          )}
        </Spring>

        {vertical ?
 : null}

    );
  }
}

Steps.defaultProps = {
  currentStep: 0,
  vertical: false,
  pulseNextStep: true,
  autoSlide: false,
  autoSlideDelay: 4000,
  height: 'auto',
  steps: {
    prevStepBtn: null,
    nextStepBtn: null,
    headerContent: null
  }
};

Steps.propTypes = {
  currentStep: PropTypes.number,
  vertical: PropTypes.bool,
  height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  pulseNextStep: PropTypes.bool,
  autoSlide: PropTypes.bool,
  autoSlideDelay: PropTypes.number,
  steps: PropTypes.arrayOf(
    PropTypes.shape({
      content: PropTypes.node.isRequired,
      headerContent: PropTypes.node,
      prevStepBtn: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
      nextStepBtn: PropTypes.oneOfType([PropTypes.node, PropTypes.string])
    })
  ).isRequired
};

export default Steps;
<commit_msg>fixed autoslide bug on steps component (#450)<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Spring, animated } from 'react-spring';
import { TimingAnimation, Easing } from 'react-spring/dist/addons.cjs';

// Components
import StepsMainNav from './StepsMainNav';
import StepHeaderNavButton from './StepHeaderNavButton';

class Steps extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentStep: props.currentStep,
      autoSlide: props.autoSlide
    };
  }

  componentDidMount() {
    const { autoSlide } = this.state;
    const { autoSlideDelay } = this.props;
    if (autoSlide) {
      this.autoSlideInterval = setInterval(() => {
        if (autoSlide) {
          const nextStep = (this.state.currentStep + 1) % this.props.steps.length;
          this.setState({ currentStep: nextStep });
        }
      }, autoSlideDelay);
    }
  }

  componentWillUnmount() {
    if (this.autoSlideInterval) {
      clearInterval(this.autoSlideInterval);
    }
  }

  goToStep = (i, stopAutoSliding = true) => {
    if (i < this.props.steps.length && i > -1) {
      if (stopAutoSliding) {
        this.setState({ currentStep: i, autoSlide: false });
      } else {
        this.setState({ currentStep: i });
      }
    }
  };

  createSteps = () => {
    const { vertical, steps } = this.props;
    const numSteps = steps.length;
    const stepsComponents = [];
    const currentStep = this.state.currentStep;
    for (let i = 0; i < numSteps; i += 1) {
      let springTo = { opacity: 0.2 };
      let springFrom = { opacity: 1 };
      if (!vertical) {
        springTo.height = 0;
        springFrom.height = 'auto';
      }
      if (currentStep === i) {
        springTo = { opacity: 1 };
        springFrom = { opacity: 0.2 };
        if (!vertical) {
          springTo.height = 'auto';
          springFrom.height = 200;
        }
      }
      const step = (
        <Spring
          native
          from={springFrom}
          to={springTo}
          key={`animated-step-content-${i}`}
          impl={TimingAnimation}
          config={{ duration: 900, easing: Easing.out(Easing.cubic) }}
        >
          {style => (
            <animated.div className="single-step-content" style={{ ...style }}>

                {steps[i].prevStepBtn && (
                  <StepHeaderNavButton
                    isReachableByKeyboard={i === currentStep}
                    isDisabled={i === 0}
                    targetStep={i - 1}
                    onClick={this.goToStep}
                  >
                    {steps[i].prevStepBtn}
                  </StepHeaderNavButton>
                )}

                {steps[i].headerContent}

                {steps[i].nextStepBtn && (
                  <StepHeaderNavButton
                    isReachableByKeyboard={i === currentStep}
                    isDisabled={i === numSteps - 1}
                    targetStep={i + 1}
                    onClick={this.goToStep}
                  >
                    {steps[i].nextStepBtn}
                  </StepHeaderNavButton>
                )}

              {steps[i].content}
            </animated.div>
          )}
        </Spring>
      );
      stepsComponents.push(step);
    }
    return stepsComponents;
  };

  render() {
    let containerClasses = 'steps-container';
    let springTo;
    const { currentStep } = this.state;
    const { height, pulseNextStep, vertical, steps } = this.props;
    if (vertical) {
      containerClasses += ' vertical';
      springTo = { left: 0, top: `-${currentStep * 100}%` };
    } else {
      springTo = { left: `-${currentStep * 100}%`, top: 0 };
    }
    return (

        <StepsMainNav
          currentStep={currentStep}
          totalNumSteps={steps.length}
          onClick={this.goToStep}
          vertical={vertical}
          pulseNextStep={pulseNextStep}
        />

        <Spring
          native
          to={springTo}
          impl={TimingAnimation}
          config={{ duration: 625, easing: Easing.out(Easing.cubic) }}
        >
          {style => (
            <animated.div className={'steps-content-container'} style={{ ...style }}>
              {this.createSteps()}
            </animated.div>
          )}
        </Spring>

        {vertical ?
 : null}

    );
  }
}

Steps.defaultProps = {
  currentStep: 0,
  vertical: false,
  pulseNextStep: true,
  autoSlide: false,
  autoSlideDelay: 4000,
  height: 'auto',
  steps: {
    prevStepBtn: null,
    nextStepBtn: null,
    headerContent: null
  }
};

Steps.propTypes = {
  currentStep: PropTypes.number,
  vertical: PropTypes.bool,
  height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  pulseNextStep: PropTypes.bool,
  autoSlide: PropTypes.bool,
  autoSlideDelay: PropTypes.number,
  steps: PropTypes.arrayOf(
    PropTypes.shape({
      content: PropTypes.node.isRequired,
      headerContent: PropTypes.node,
      prevStepBtn: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
      nextStepBtn: PropTypes.oneOfType([PropTypes.node, PropTypes.string])
    })
  ).isRequired
};

export default Steps;
<|endoftext|>


<commit_before>/* This contains the textarea binding for ShareDB. This binding is really
 * simple, and a bit slow on big texuments (Its O(N). However, it requires no
 * changes to the DOM and no heavy libraries like ace. It works for any kind of
 * text input field.
 *
 * You probably want to use this binding for small fields on forms and such.
 * For code editors or rich text editors or whatever, I recommend something
 * heavier.
 */

/* applyChange creates the edits to convert oldVal -> newVal.
 *
 * This function should be called every time the text element is changed.
 * Because changes are always localised, the diffing is quite easy. We simply
 * scan in from the start and scan in from the end to isolate the edited range,
 * then delete everything that was removed & add everything that was added.
 * This wouldn't work for complex changes, but this function should be called
 * on keystroke - so the edits will mostly just be single character changes.
 * Sometimes they'll paste text over other text, but even then the diff
 * generated by this algorithm is correct.
 *
 * This algorithm is O(N). I suspect you could speed it up somehow using regular expressions.
 */
let applyChange = function (doc, oldVal, newVal) {

  // Strings are immutable and have reference equality. I think this test is O(1), so its worth doing.
  if (oldVal === newVal) return;

  let commonStart = 0;
  while (oldVal.charAt(commonStart) === newVal.charAt(commonStart)) {
    commonStart++;
  }

  let commonEnd = 0;
  while (oldVal.charAt(oldVal.length - 1 - commonEnd) === newVal.charAt(newVal.length - 1 - commonEnd) &&
  commonEnd + commonStart < oldVal.length && commonEnd + commonStart < newVal.length) {
    commonEnd++;
  }

  if (oldVal.length !== commonStart + commonEnd) {
    // doc.api.remove(commonStart, oldVal.length - commonStart - commonEnd);
    let op = [commonStart, '', {
      d: oldVal.length - commonStart - commonEnd
    }];
    doc.submitOp(op);
  }

  if (newVal.length !== commonStart + commonEnd) {
    // doc.insert(commonStart, newVal.slice(commonStart, newVal.length - commonEnd));
    doc.submitOp([commonStart, newVal.slice(commonStart, newVal.length - commonEnd)]);
  }
};

// Attach a textarea to a document's editing context.
//
// The context is optional, and will be created from the document if its not
// specified.
export const attachTextarea = function (elem, doc) {
  // if (!doc) doc = this.createContext();
  // if (!doc.provides.text) throw new Error('Cannot attach to non-text document');

  elem.value = doc.data;

  // The current value of the element's text is stored so we can quickly check
  // if its been changed in the event handlers. This is mostly for browsers on
  // windows, where the content contains \r\n newlines. applyChange() is only
  // called after the \r\n newlines are converted, and that check is quite
  // slow. So we also cache the string before conversion so we can do a quick
  // check incase the conversion isn't needed.

  let prevValue;
  let newSelection = [];

    /*
      ADD-IN
      Enabling the tab key on the textarea.
   */

  elem.onkeydown = e => {
    if (e.key === 'Tab') { // Tab was pressed

      e.preventDefault();

      // Get caret position/selection
      let val = elem.value,
        start = elem.selectionStart,
        end = elem.selectionEnd;

      // set textarea value to: text before caret + tab + text after caret
      elem.value = val.substring(0, start) + '\t' + val.substring(end);

      // put caret at right position again
      elem.selectionStart = elem.selectionEnd = start + 1;

      // prevent the focus lose
      return false;
    }
  };

  // Replace the content of the text area with newText, and transform the
  // current cursor by the specified function.
  let replaceText = function (newText, transformCursor) {
    if (transformCursor) {
      newSelection = [transformCursor(elem.selectionStart), transformCursor(elem.selectionEnd)];
    }

    // Fixate the window's scroll while we set the element's value. Otherwise
    // the browser scrolls to the element.
    let scrollTop = elem.scrollTop;
    elem.value = newText;
    prevValue = elem.value; // Not done on one line so the browser can do newline conversion.
    if (elem.scrollTop !== scrollTop) elem.scrollTop = scrollTop;

    // Setting the selection moves the cursor. We'll just have to let your
    // cursor drift if the element isn't active, though usually users don't
    // care.
    if (newSelection && window.document.activeElement === elem) {
      elem.selectionStart = newSelection[0];
      elem.selectionEnd = newSelection[1];
    }
  };

  replaceText(doc.data);

  // *** remote -> local changes

  let onInsert = function (pos, text) {
    let transformCursor = function (cursor) {
      return pos < cursor ? cursor + text.length : cursor;
    };

    // Remove any window-style newline characters. Windows inserts these, and
    // they mess up the generated diff.
    let prev = elem.value.replace(/\r\n/g, '\n');
    replaceText(prev.slice(0, pos) + text + prev.slice(pos), transformCursor);
  };

  let onRemove = function (pos, length) {
    let transformCursor = function (cursor) {
      // If the cursor is inside the deleted region, we only want to move back to the start
      // of the region. Hence the Math.min.
      return pos < cursor ? cursor - Math.min(length, cursor - pos) : cursor;
    };

    let prev = elem.value.replace(/\r\n/g, '\n');
    replaceText(prev.slice(0, pos) + prev.slice(pos + length), transformCursor);
  };

  let _updateField = function(fields, value) {
    if (typeof value === 'number') {
      fields.pos = value;
    } else if (typeof value === 'string') {
      fields.insertStr = value;
    } else if (typeof value === 'object' && value.d !== undefined) {
      fields.delNum = value.d;
    }
  };

  doc.on('op', function (op, localContext) {
    if (localContext === true) {
      return;
    }
    let fields = {pos: 0, insertStr: '', delNum: 0};

    for (let i = 0; i < op.length; i++) {
      _updateField(fields, op[i]);
    }

    if (fields.insertStr.length > 0) {
      // insert
      onInsert(fields.pos, fields.insertStr);
    }
    if (fields.delNum > 0) {
      // delete
      onRemove(fields.pos, fields.delNum);
    }
  });

  // *** local -> remote changes

  // This function generates operations from the changed content in the textarea.
  let genOp = function (event) {
    // In a timeout so the browser has time to propogate the event's changes to the DOM.
    setTimeout(function () {
      if (elem.value !== prevValue) {
        prevValue = elem.value;
        applyChange(doc, doc.data, elem.value.replace(/\r\n/g, '\n'));
      }
    }, 0);
  };

  let eventNames = ['textInput', 'keydown', 'keyup', 'select', 'cut', 'paste'];
  for (let i = 0; i < eventNames.length; i++) {
    let e = eventNames[i];
    if (elem.addEventListener) {
      elem.addEventListener(e, genOp, false);
    } else {
      elem.attachEvent('on' + e, genOp);
    }
  }

  doc.detach = function () {
    for (let i = 0; i < eventNames.length; i++) {
      let e = eventNames[i];
      if (elem.removeEventListener) {
        elem.removeEventListener(e, genOp, false);
      } else {
        elem.detachEvent('on' + e, genOp);
      }
    }
  };

  return doc;
};

// (() => {
//   let el = document.getElementById('sharedTextArea');
//   el.onkeydown = (e) => {
//     if (e.keyCode === 9) { // tab was pressed
//
//       // Get caret position/selection
//       let val = this.value,
//         start = this.selectionStart,
//         end = this.selectionEnd;
//
//       // set textarea value to: text before caret + tab + text after caret
//       this.value = val.substring(0, start) + '\t' + val.substring(end);
//
//       // put caret at right position again
//       this.selectionStart = this.selectionEnd = start + 1;
//
//       // prevent the focus lose
//       return false;
//     }
//   };
// })();

<commit_msg>Fixed the comments.<commit_after>/* This contains the textarea binding for ShareDB. This binding is really
 * simple, and a bit slow on big texuments (Its O(N). However, it requires no
 * changes to the DOM and no heavy libraries like ace. It works for any kind of
 * text input field.
 *
 * You probably want to use this binding for small fields on forms and such.
 * For code editors or rich text editors or whatever, I recommend something
 * heavier.
 */

/* applyChange creates the edits to convert oldVal -> newVal.
 *
 * This function should be called every time the text element is changed.
 * Because changes are always localised, the diffing is quite easy. We simply
 * scan in from the start and scan in from the end to isolate the edited range,
 * then delete everything that was removed & add everything that was added.
 * This wouldn't work for complex changes, but this function should be called
 * on keystroke - so the edits will mostly just be single character changes.
 * Sometimes they'll paste text over other text, but even then the diff
 * generated by this algorithm is correct.
 *
 * This algorithm is O(N). I suspect you could speed it up somehow using regular expressions.
 */
let applyChange = function (doc, oldVal, newVal) {

  // Strings are immutable and have reference equality. I think this test is O(1), so its worth doing.
  if (oldVal === newVal) return;

  let commonStart = 0;
  while (oldVal.charAt(commonStart) === newVal.charAt(commonStart)) {
    commonStart++;
  }

  let commonEnd = 0;
  while (oldVal.charAt(oldVal.length - 1 - commonEnd) === newVal.charAt(newVal.length - 1 - commonEnd) &&
  commonEnd + commonStart < oldVal.length && commonEnd + commonStart < newVal.length) {
    commonEnd++;
  }

  if (oldVal.length !== commonStart + commonEnd) {
    // doc.api.remove(commonStart, oldVal.length - commonStart - commonEnd);
    let op = [commonStart, '', {
      d: oldVal.length - commonStart - commonEnd
    }];
    doc.submitOp(op);
  }

  if (newVal.length !== commonStart + commonEnd) {
    // doc.insert(commonStart, newVal.slice(commonStart, newVal.length - commonEnd));
    doc.submitOp([commonStart, newVal.slice(commonStart, newVal.length - commonEnd)]);
  }
};

// Attach a textarea to a document's editing context.
//
// The context is optional, and will be created from the document if its not
// specified.
export const attachTextarea = function (elem, doc) {
  // if (!doc) doc = this.createContext();
  // if (!doc.provides.text) throw new Error('Cannot attach to non-text document');

  elem.value = doc.data;

  // The current value of the element's text is stored so we can quickly check
  // if its been changed in the event handlers. This is mostly for browsers on
  // windows, where the content contains \r\n newlines. applyChange() is only
  // called after the \r\n newlines are converted, and that check is quite
  // slow. So we also cache the string before conversion so we can do a quick
  // check in case the conversion isn't needed.

  let prevValue;
  let newSelection = [];

    /*
      ADD-IN
      Enabling the tab key on the textarea.
   */

  elem.onkeydown = e => {
    // Tab was pressed
    if (e.key === 'Tab') {

      // Prevent normal behavior of switching element focus.
      e.preventDefault();

      // Get caret position/selection
      let val = elem.value,
        start = elem.selectionStart,
        end = elem.selectionEnd;

      // Set textarea value to: text before caret + tab + text after caret
      elem.value = val.substring(0, start) + '\t' + val.substring(end);

      // Put caret at right position again
      elem.selectionStart = elem.selectionEnd = start + 1;

      // Prevent the focus lose
      return false;
    }
  };

  /* Replace the content of the text area with newText, and transform the
   current cursor by the specified function. */

  let replaceText = function (newText, transformCursor) {
    if (transformCursor) {
      newSelection = [transformCursor(elem.selectionStart), transformCursor(elem.selectionEnd)];
    }

    // Fixate the window's scroll while we set the element's value. Otherwise
    // the browser scrolls to the element.
    let scrollTop = elem.scrollTop;
    elem.value = newText;
    prevValue = elem.value; // Not done on one line so the browser can do newline conversion.
    if (elem.scrollTop !== scrollTop) elem.scrollTop = scrollTop;

    // Setting the selection moves the cursor. We'll just have to let your
    // cursor drift if the element isn't active, though usually users don't
    // care.
    if (newSelection && window.document.activeElement === elem) {
      elem.selectionStart = newSelection[0];
      elem.selectionEnd = newSelection[1];
    }
  };

  replaceText(doc.data);

  // *** remote -> local changes

  let onInsert = function (pos, text) {
    let transformCursor = function (cursor) {
      return pos < cursor ? cursor + text.length : cursor;
    };

    // Remove any window-style newline characters. Windows inserts these, and
    // they mess up the generated diff.
    let prev = elem.value.replace(/\r\n/g, '\n');
    replaceText(prev.slice(0, pos) + text + prev.slice(pos), transformCursor);
  };

  let onRemove = function (pos, length) {
    let transformCursor = function (cursor) {
      // If the cursor is inside the deleted region, we only want to move back to the start
      // of the region. Hence the Math.min.
      return pos < cursor ? cursor - Math.min(length, cursor - pos) : cursor;
    };

    let prev = elem.value.replace(/\r\n/g, '\n');
    replaceText(prev.slice(0, pos) + prev.slice(pos + length), transformCursor);
  };

  let _updateField = function(fields, value) {
    if (typeof value === 'number') {
      fields.pos = value;
    } else if (typeof value === 'string') {
      fields.insertStr = value;
    } else if (typeof value === 'object' && value.d !== undefined) {
      fields.delNum = value.d;
    }
  };

  doc.on('op', function (op, localContext) {
    if (localContext === true) {
      return;
    }
    let fields = {pos: 0, insertStr: '', delNum: 0};

    for (let i = 0; i < op.length; i++) {
      _updateField(fields, op[i]);
    }

    if (fields.insertStr.length > 0) {
      // insert
      onInsert(fields.pos, fields.insertStr);
    }
    if (fields.delNum > 0) {
      // delete
      onRemove(fields.pos, fields.delNum);
    }
  });

  // *** local -> remote changes

  // This function generates operations from the changed content in the textarea.
  let genOp = function (event) {
    // In a timeout so the browser has time to propogate the event's changes to the DOM.
    setTimeout(function () {
      if (elem.value !== prevValue) {
        prevValue = elem.value;
        applyChange(doc, doc.data, elem.value.replace(/\r\n/g, '\n'));
      }
    }, 0);
  };

  let eventNames = ['textInput', 'keydown', 'keyup', 'select', 'cut', 'paste'];
  for (let i = 0; i < eventNames.length; i++) {
    let e = eventNames[i];
    if (elem.addEventListener) {
      elem.addEventListener(e, genOp, false);
    } else {
      elem.attachEvent('on' + e, genOp);
    }
  }

  doc.detach = function () {
    for (let i = 0; i < eventNames.length; i++) {
      let e = eventNames[i];
      if (elem.removeEventListener) {
        elem.removeEventListener(e, genOp, false);
      } else {
        elem.detachEvent('on' + e, genOp);
      }
    }
  };

  return doc;
};

// (() => {
//   let el = document.getElementById('sharedTextArea');
//   el.onkeydown = (e) => {
//     if (e.keyCode === 9) { // tab was pressed
//
//       // Get caret position/selection
//       let val = this.value,
//         start = this.selectionStart,
//         end = this.selectionEnd;
//
//       // set textarea value to: text before caret + tab + text after caret
//       this.value = val.substring(0, start) + '\t' + val.substring(end);
//
//       // put caret at right position again
//       this.selectionStart = this.selectionEnd = start + 1;
//
//       // prevent the focus lose
//       return false;
//     }
//   };
// })();

<|endoftext|>


<commit_before>import assert from 'assert'
import blocksReducer from '../reducers'
import {
	ADD_BLOCK, REMOVE_BLOCK, MOVE_BLOCK,
	BLOCK_PARENT, BLOCK_PREV_SIBLING, BLOCK_NEXT_SIBLING,
} from '../constants'

function createDummyBlock(id)
{
	return {
		id: String(id),
		blockTypeId: '1',
		enabled: true,
		data: {},
		errors: [],
		template: {
			html: '',
			css: '',
			js: '',
		},
	}
}

describe('Reducers', function()
{
	describe('blocksReducer()', function()
	{
		describe('ADD_BLOCK', function()
		{
			it('should add a block to the store', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: { block: createDummyBlock('1') },
				}

				const initialState = {
					collection: {},
					structure: [],
				}

				const expectedState = {
					collection: { '1': createDummyBlock('1') },
					structure: [ { id: '1', level: 1 } ],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block as the only child to another', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('2'),
						relatedBlockId: '1',
						relatedBlockType: BLOCK_PARENT,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
					},
					structure: [
						{ id: '1', level: 1 },
					],
				}

				const expectedState = {
					collection: Object.assign({ '2': createDummyBlock('2') }, initialState.collection),
					structure: [ ...initialState.structure, { id: '2', level: 2 } ],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block as a child to another', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('3'),
						relatedBlockId: '1',
						relatedBlockType: BLOCK_PARENT,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
					],
				}

				const expectedState = {
					collection: Object.assign({ '3': createDummyBlock('3') }, initialState.collection),
					structure: [ ...initialState.structure, { id: '3', level: 2 } ],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block before another', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('4'),
						relatedBlockId: '1',
						relatedBlockType: BLOCK_NEXT_SIBLING,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
						'3': createDummyBlock('3'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
					],
				}

				const expectedState = {
					collection: Object.assign({ '4': createDummyBlock('4') }, initialState.collection),
					structure: [
						{ id: '4', level: 1 },
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
					],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block before another under a parent', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('4'),
						relatedBlockId: '2',
						relatedBlockType: BLOCK_NEXT_SIBLING,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
						'3': createDummyBlock('3'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
					],
				}

				const expectedState = {
					collection: Object.assign({ '4': createDummyBlock('4') }, initialState.collection),
					structure: [
						{ id: '1', level: 1 },
						{ id: '4', level: 2 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
					],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block after another', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('2'),
						relatedBlockId: '1',
						relatedBlockType: BLOCK_PREV_SIBLING,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
					},
					structure: [
						{ id: '1', level: 1 },
					],
				}

				const expectedState = {
					collection: Object.assign({ '2': createDummyBlock('2') }, initialState.collection),
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 1 },
					],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block after another with descendants', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('4'),
						relatedBlockId: '1',
						relatedBlockType: BLOCK_PREV_SIBLING,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
						'3': createDummyBlock('3'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
					],
				}

				const expectedState = {
					collection: Object.assign({ '4': createDummyBlock('4') }, initialState.collection),
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
						{ id: '4', level: 1 },
					],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})
		})
	})
})
<commit_msg>Cleaned up some of the tests<commit_after>import assert from 'assert'
import blocksReducer from '../reducers'
import {
	ADD_BLOCK, REMOVE_BLOCK, MOVE_BLOCK,
	BLOCK_PARENT, BLOCK_PREV_SIBLING, BLOCK_NEXT_SIBLING,
} from '../constants'

function createDummyBlock(id)
{
	return {
		id: String(id),
		blockTypeId: '1',
		enabled: true,
		data: {},
		errors: [],
		template: {
			html: '',
			css: '',
			js: '',
		},
	}
}

describe('Reducers', function()
{
	describe('blocksReducer()', function()
	{
		describe('ADD_BLOCK', function()
		{
			it('should add a block to the store', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: { block: createDummyBlock('1') },
				}

				const initialState = {
					collection: {},
					structure: [],
				}

				const expectedState = {
					collection: { '1': createDummyBlock('1') },
					structure: [ { id: '1', level: 1 } ],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block as the only child to another', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('2'),
						relatedBlockId: '1',
						relatedBlockType: BLOCK_PARENT,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
					},
					structure: [
						{ id: '1', level: 1 },
					],
				}

				const expectedState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
					],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block as a child to another', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('3'),
						relatedBlockId: '1',
						relatedBlockType: BLOCK_PARENT,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
					],
				}

				const expectedState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
						'3': createDummyBlock('3'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
					],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block before another', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('4'),
						relatedBlockId: '1',
						relatedBlockType: BLOCK_NEXT_SIBLING,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
						'3': createDummyBlock('3'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
					],
				}

				const expectedState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
						'3': createDummyBlock('3'),
						'4': createDummyBlock('4'),
					},
					structure: [
						{ id: '4', level: 1 },
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
					],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block before another under a parent', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('4'),
						relatedBlockId: '2',
						relatedBlockType: BLOCK_NEXT_SIBLING,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
						'3': createDummyBlock('3'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
					],
				}

				const expectedState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
						'3': createDummyBlock('3'),
						'4': createDummyBlock('4'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '4', level: 2 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
					],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block after another', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('2'),
						relatedBlockId: '1',
						relatedBlockType: BLOCK_PREV_SIBLING,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
					},
					structure: [
						{ id: '1', level: 1 },
					],
				}

				const expectedState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 1 },
					],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})

			it('should add a block after another with descendants', function()
			{
				const action = {
					type: ADD_BLOCK,
					payload: {
						block: createDummyBlock('4'),
						relatedBlockId: '1',
						relatedBlockType: BLOCK_PREV_SIBLING,
					}
				}

				const initialState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
						'3': createDummyBlock('3'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
					],
				}

				const expectedState = {
					collection: {
						'1': createDummyBlock('1'),
						'2': createDummyBlock('2'),
						'3': createDummyBlock('3'),
						'4': createDummyBlock('4'),
					},
					structure: [
						{ id: '1', level: 1 },
						{ id: '2', level: 2 },
						{ id: '3', level: 2 },
						{ id: '4', level: 1 },
					],
				}

				assert.deepEqual(blocksReducer(initialState, action), expectedState)
			})
		})
	})
})
<|endoftext|>


<commit_before>AutoForm.hooks({
    'events-edit-form': {
        onSuccess: function (operation, result, template) {
            slidePanel.closePanel();
            Materialize.toast('Event updated successfully!', 4000);
            Session.set("selected", result._id)
        },
        onError: function(formType, error) {
            console.error(error);
        }
    }
});

Template.eventsEdit.helpers({
    selectedEventDoc: function(){
        return Events.findOne(Session.get('selected'));
    }
});

Template.eventsEdit.rendered = function() {
    $('select').material_select();
    var selectedEvent = Events.findOne(Session.get('selected'));
    if (selectedEvent != null) {
        this.$('input[name="coordinates.lat"]').val(selectedEvent.coordinates.lat);
        this.$('input[name="coordinates.lng"]').val(selectedEvent.coordinates.lng);
    }
};

Template.eventsNew.destroyed = function() {
    // Can do some cleanup in here
};<commit_msg>fixed form layout issues after upgrading packages (duplicated caret sign on edit event form)<commit_after>AutoForm.hooks({
    'events-edit-form': {
        onSuccess: function (operation, result, template) {
            slidePanel.closePanel();
            Materialize.toast('Event updated successfully!', 4000);
            Session.set("selected", result._id)
        },
        onError: function(formType, error) {
            console.error(error);
        }
    }
});

Template.eventsEdit.helpers({
    selectedEventDoc: function(){
        return Events.findOne(Session.get('selected'));
    }
});

Template.eventsEdit.rendered = function() {
    var selectedEvent = Events.findOne(Session.get('selected'));
    if (selectedEvent != null) {
        this.$('input[name="coordinates.lat"]').val(selectedEvent.coordinates.lat);
        this.$('input[name="coordinates.lng"]').val(selectedEvent.coordinates.lng);
    }
};

Template.eventsNew.destroyed = function() {
    // Can do some cleanup in here
};<|endoftext|>


<commit_before>// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Unchecked conversions to create values of goog.html types from
 * plain strings.  Use of these functions could potentially result in instances
 * of goog.html types that violate their type contracts, and hence result in
 * security vulnerabilties.
 *
 * Therefore, all uses of the methods herein must be carefully security
 * reviewed.  Avoid use of the methods in this file whenever possible; instead
 * prefer to create instances of goog.html types using inherently safe builders
 * or template systems.
 *
 *
 *
 * @visibility {//closure/goog/html:approved_for_unchecked_conversion}
 * @visibility {//closure/goog/bin/sizetests:__pkg__}
 */

goog.provide('goog.html.uncheckedconversions');

goog.require('goog.asserts');
goog.require('goog.html.SafeHtml');
goog.require('goog.html.SafeScript');
goog.require('goog.html.SafeStyle');
goog.require('goog.html.SafeStyleSheet');
goog.require('goog.html.SafeUrl');
goog.require('goog.html.TrustedResourceUrl');
goog.require('goog.string');
goog.require('goog.string.Const');

/**
 * Performs an "unchecked conversion" to SafeHtml from a plain string that is
 * known to satisfy the SafeHtml type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code html} satisfies the SafeHtml type contract in all
 * possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} html A string that is claimed to adhere to the SafeHtml
 *     contract.
 * @param {?goog.i18n.bidi.Dir=} opt_dir The optional directionality of the
 *     SafeHtml to be constructed. A null or undefined value signifies an
 *     unknown directionality.
 * @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml
 *     object.
 * @suppress {visibility} For access to SafeHtml.create...  Note that this
 *     use is appropriate since this method is intended to be "package private"
 *     within goog.html.  DO NOT call SafeHtml.create... from outside this
 *     package; use appropriate wrappers instead.
 */
goog.html.uncheckedconversions.safeHtmlFromStringKnownToSatisfyTypeContract =
    function(justification, html, opt_dir) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
      html, opt_dir || null);
};

/**
 * Performs an "unchecked conversion" to SafeScript from a plain string that is
 * known to satisfy the SafeScript type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code script} satisfies the SafeScript type contract in
 * all possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} script The string to wrap as a SafeScript.
 * @return {!goog.html.SafeScript} The value of {@code script}, wrapped in a
 *     SafeScript object.
 */
goog.html.uncheckedconversions.safeScriptFromStringKnownToSatisfyTypeContract =
    function(justification, script) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmpty(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
      script);
};

/**
 * Performs an "unchecked conversion" to SafeStyle from a plain string that is
 * known to satisfy the SafeStyle type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code style} satisfies the SafeUrl type contract in all
 * possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} style The string to wrap as a SafeStyle.
 * @return {!goog.html.SafeStyle} The value of {@code style}, wrapped in a
 *     SafeStyle object.
 */
goog.html.uncheckedconversions.safeStyleFromStringKnownToSatisfyTypeContract =
    function(justification, style) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
      style);
};

/**
 * Performs an "unchecked conversion" to SafeStyleSheet from a plain string
 * that is known to satisfy the SafeStyleSheet type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code styleSheet} satisfies the SafeUrl type contract in
 * all possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} styleSheet The string to wrap as a SafeStyleSheet.
 * @return {!goog.html.SafeStyleSheet} The value of {@code styleSheet}, wrapped
 *     in a SafeStyleSheet object.
 */
goog.html.uncheckedconversions
    .safeStyleSheetFromStringKnownToSatisfyTypeContract = function(
    justification, styleSheet) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.SafeStyleSheet
      .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet);
};

/**
 * Performs an "unchecked conversion" to SafeUrl from a plain string that is
 * known to satisfy the SafeUrl type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code url} satisfies the SafeUrl type contract in all
 * possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} url The string to wrap as a SafeUrl.
 * @return {!goog.html.SafeUrl} The value of {@code url}, wrapped in a SafeUrl
 *     object.
 */
goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract =
    function(justification, url) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
};

/**
 * Performs an "unchecked conversion" to TrustedResourceUrl from a plain string
 * that is known to satisfy the TrustedResourceUrl type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code url} satisfies the TrustedResourceUrl type contract
 * in all possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} url The string to wrap as a TrustedResourceUrl.
 * @return {!goog.html.TrustedResourceUrl} The value of {@code url}, wrapped in
 *     a TrustedResourceUrl object.
 */
goog.html.uncheckedconversions
    .trustedResourceUrlFromStringKnownToSatisfyTypeContract = function(
    justification, url) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.TrustedResourceUrl
      .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url);
};
<commit_msg>Remove unnecessary @suppress annotation. Function being called is nowadays @package, not @private.<commit_after>// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Unchecked conversions to create values of goog.html types from
 * plain strings.  Use of these functions could potentially result in instances
 * of goog.html types that violate their type contracts, and hence result in
 * security vulnerabilties.
 *
 * Therefore, all uses of the methods herein must be carefully security
 * reviewed.  Avoid use of the methods in this file whenever possible; instead
 * prefer to create instances of goog.html types using inherently safe builders
 * or template systems.
 *
 *
 *
 * @visibility {//closure/goog/html:approved_for_unchecked_conversion}
 * @visibility {//closure/goog/bin/sizetests:__pkg__}
 */

goog.provide('goog.html.uncheckedconversions');

goog.require('goog.asserts');
goog.require('goog.html.SafeHtml');
goog.require('goog.html.SafeScript');
goog.require('goog.html.SafeStyle');
goog.require('goog.html.SafeStyleSheet');
goog.require('goog.html.SafeUrl');
goog.require('goog.html.TrustedResourceUrl');
goog.require('goog.string');
goog.require('goog.string.Const');

/**
 * Performs an "unchecked conversion" to SafeHtml from a plain string that is
 * known to satisfy the SafeHtml type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code html} satisfies the SafeHtml type contract in all
 * possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} html A string that is claimed to adhere to the SafeHtml
 *     contract.
 * @param {?goog.i18n.bidi.Dir=} opt_dir The optional directionality of the
 *     SafeHtml to be constructed. A null or undefined value signifies an
 *     unknown directionality.
 * @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml
 *     object.
 */
goog.html.uncheckedconversions.safeHtmlFromStringKnownToSatisfyTypeContract =
    function(justification, html, opt_dir) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
      html, opt_dir || null);
};

/**
 * Performs an "unchecked conversion" to SafeScript from a plain string that is
 * known to satisfy the SafeScript type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code script} satisfies the SafeScript type contract in
 * all possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} script The string to wrap as a SafeScript.
 * @return {!goog.html.SafeScript} The value of {@code script}, wrapped in a
 *     SafeScript object.
 */
goog.html.uncheckedconversions.safeScriptFromStringKnownToSatisfyTypeContract =
    function(justification, script) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmpty(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
      script);
};

/**
 * Performs an "unchecked conversion" to SafeStyle from a plain string that is
 * known to satisfy the SafeStyle type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code style} satisfies the SafeUrl type contract in all
 * possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} style The string to wrap as a SafeStyle.
 * @return {!goog.html.SafeStyle} The value of {@code style}, wrapped in a
 *     SafeStyle object.
 */
goog.html.uncheckedconversions.safeStyleFromStringKnownToSatisfyTypeContract =
    function(justification, style) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
      style);
};

/**
 * Performs an "unchecked conversion" to SafeStyleSheet from a plain string
 * that is known to satisfy the SafeStyleSheet type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code styleSheet} satisfies the SafeUrl type contract in
 * all possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} styleSheet The string to wrap as a SafeStyleSheet.
 * @return {!goog.html.SafeStyleSheet} The value of {@code styleSheet}, wrapped
 *     in a SafeStyleSheet object.
 */
goog.html.uncheckedconversions
    .safeStyleSheetFromStringKnownToSatisfyTypeContract = function(
    justification, styleSheet) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.SafeStyleSheet
      .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet);
};

/**
 * Performs an "unchecked conversion" to SafeUrl from a plain string that is
 * known to satisfy the SafeUrl type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code url} satisfies the SafeUrl type contract in all
 * possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} url The string to wrap as a SafeUrl.
 * @return {!goog.html.SafeUrl} The value of {@code url}, wrapped in a SafeUrl
 *     object.
 */
goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract =
    function(justification, url) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
};

/**
 * Performs an "unchecked conversion" to TrustedResourceUrl from a plain string
 * that is known to satisfy the TrustedResourceUrl type contract.
 *
 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
 * that the value of {@code url} satisfies the TrustedResourceUrl type contract
 * in all possible program states.
 *
 *
 * @param {!goog.string.Const} justification A constant string explaining why
 *     this use of this method is safe. May include a security review ticket
 *     number.
 * @param {string} url The string to wrap as a TrustedResourceUrl.
 * @return {!goog.html.TrustedResourceUrl} The value of {@code url}, wrapped in
 *     a TrustedResourceUrl object.
 */
goog.html.uncheckedconversions
    .trustedResourceUrlFromStringKnownToSatisfyTypeContract = function(
    justification, url) {
  // unwrap() called inside an assert so that justification can be optimized
  // away in production code.
  goog.asserts.assertString(
      goog.string.Const.unwrap(justification), 'must provide justification');
  goog.asserts.assert(
      !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
      'must provide non-empty justification');
  return goog.html.TrustedResourceUrl
      .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url);
};
<|endoftext|>


<commit_before>var findSide = require('./find-side');

// determine the intersection point of a line segment with a line
// original: http://paulbourke.net/geometry/pointlineplane/pdb.c
var EPS = 0.000001;

// first four arguments are the line (aka plane): x1, y1, x2, y2
// the segment is defined by the last four: x3, y3, x4, y4
function segline(x1, y1, x2, y2, x3, y3, x4, y4) {
  var denom  = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);

  // never return NaN
  if ((denom === 0) || isNaN(denom)) {
    return;
  }

  var numera = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
  var numerb = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);

  // are the line coincident?
  if (Math.abs(numera) < EPS && Math.abs(numerb) < EPS && Math.abs(denom) < EPS) {
    return [
      (x1 + x2) / 2,
      (y1 + y2) / 2
    ];
  }

  // are the line parallel?
  if (Math.abs(denom) < EPS) {
    return; // no intersection
  }

  // is the intersection along the the segment?
  var mua = numera / denom;
  var mub = numerb / denom;

  if ((mua < 0) && (mua > 1) || (mub < 0) || (mub > 1)) {
    return;
  }

  return [
    x1 + (mua) * (x2 - x1),
    y1 + (mua) * (y2 - y1)
  ]
}

function nick (poly, nickingPlanes) {
  poly.splice(0, 0, poly[poly.length - 1]);

  var result = [];
  for (var x = 0; x < poly.length - 1; x++) {
    var lastX = poly[x][0];
    var lastY = poly[x][1];
    var currentX = poly[x + 1][0];
    var currentY = poly[x + 1][1];

    var intersection = segline(nickingPlanes[0], nickingPlanes[1], nickingPlanes[2], nickingPlanes[3], currentX, currentY, lastX, lastY);

    if (Array.isArray(intersection)) {
      result.push([intersection[0], intersection[1], 'nicked']);
    }
    result.push([currentX, currentY]);
  }

  return result;
}

function clip (poly, cuttingPlane) {
  var left = [];
  var right = [];

  var lastPointSide;
  if (!poly || !poly[0]) return {left: undefined, right: undefined};

  for (var i = 0; i < poly.length; i++) {
    var lastX = poly[i][0];
    var lastY = poly[i][1];
    var n = (i + 1) % poly.length;
    var currentX = poly[n][0];
    var currentY = poly[n][1];

    var side = findSide(cuttingPlane[0], cuttingPlane[1], cuttingPlane[2], cuttingPlane[3], currentX, currentY);

    // does the current line segment intersect the cuttingPlane?
    if ((lastPointSide !== 0) && (side !== 0)) {
      var intersection = segline(cuttingPlane[0], cuttingPlane[1], cuttingPlane[2], cuttingPlane[3], currentX, currentY, lastX, lastY);

      if (Array.isArray(intersection)) {
        left.push([intersection[0], intersection[1]]);
        right.push([intersection[0], intersection[1]]);
      }
    }

    if (side < 0) {
      left.push([currentX, currentY]);
    }
    else if (side === 0) {
      left.push([currentX, currentY]);
      right.push([currentX, currentY]);
    }
    else {
      right.push([currentX, currentY]);
    }

    // set lastPointSide for next iteration
    lastPointSide = side;
  }
console.log('i:', i)
console.log('n:', n)
console.log('poly:', poly)

  return {
    left: left,
    right: right,
    edges: [i, n]
  }
}

exports.nick = nick;
exports.clip = clip;
<commit_msg>removed logging statements from clip()<commit_after>var findSide = require('./find-side');

// determine the intersection point of a line segment with a line
// original: http://paulbourke.net/geometry/pointlineplane/pdb.c
var EPS = 0.000001;

// first four arguments are the line (aka plane): x1, y1, x2, y2
// the segment is defined by the last four: x3, y3, x4, y4
function segline(x1, y1, x2, y2, x3, y3, x4, y4) {
  var denom  = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);

  // never return NaN
  if ((denom === 0) || isNaN(denom)) {
    return;
  }

  var numera = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
  var numerb = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);

  // are the line coincident?
  if (Math.abs(numera) < EPS && Math.abs(numerb) < EPS && Math.abs(denom) < EPS) {
    return [
      (x1 + x2) / 2,
      (y1 + y2) / 2
    ];
  }

  // are the line parallel?
  if (Math.abs(denom) < EPS) {
    return; // no intersection
  }

  // is the intersection along the the segment?
  var mua = numera / denom;
  var mub = numerb / denom;

  if ((mua < 0) && (mua > 1) || (mub < 0) || (mub > 1)) {
    return;
  }

  return [
    x1 + (mua) * (x2 - x1),
    y1 + (mua) * (y2 - y1)
  ]
}

function nick (poly, nickingPlanes) {
  poly.splice(0, 0, poly[poly.length - 1]);

  var result = [];
  for (var x = 0; x < poly.length - 1; x++) {
    var lastX = poly[x][0];
    var lastY = poly[x][1];
    var currentX = poly[x + 1][0];
    var currentY = poly[x + 1][1];

    var intersection = segline(nickingPlanes[0], nickingPlanes[1], nickingPlanes[2], nickingPlanes[3], currentX, currentY, lastX, lastY);

    if (Array.isArray(intersection)) {
      result.push([intersection[0], intersection[1], 'nicked']);
    }
    result.push([currentX, currentY]);
  }

  return result;
}

function clip (poly, cuttingPlane) {
  var left = [];
  var right = [];

  var lastPointSide;
  if (!poly || !poly[0]) return {left: undefined, right: undefined};

  for (var i = 0; i < poly.length; i++) {
    var lastX = poly[i][0];
    var lastY = poly[i][1];
    var n = (i + 1) % poly.length;
    var currentX = poly[n][0];
    var currentY = poly[n][1];

    var side = findSide(cuttingPlane[0], cuttingPlane[1], cuttingPlane[2], cuttingPlane[3], currentX, currentY);

    // does the current line segment intersect the cuttingPlane?
    if ((lastPointSide !== 0) && (side !== 0)) {
      var intersection = segline(cuttingPlane[0], cuttingPlane[1], cuttingPlane[2], cuttingPlane[3], currentX, currentY, lastX, lastY);

      if (Array.isArray(intersection)) {
        left.push([intersection[0], intersection[1]]);
        right.push([intersection[0], intersection[1]]);
      }
    }

    if (side < 0) {
      left.push([currentX, currentY]);
    }
    else if (side === 0) {
      left.push([currentX, currentY]);
      right.push([currentX, currentY]);
    }
    else {
      right.push([currentX, currentY]);
    }

    // set lastPointSide for next iteration
    lastPointSide = side;
  }

  return {
    left: left,
    right: right,
    edges: [i, n]
  }
}

exports.nick = nick;
exports.clip = clip;
<|endoftext|>


<commit_before>import './3d-force-graph.css';

import './threeGlobal';
import 'three/examples/js/controls/TrackBallControls';

import graph from 'ngraph.graph';
import forcelayout from 'ngraph.forcelayout';
import forcelayout3d from 'ngraph.forcelayout3d';
const ngraph = { graph, forcelayout, forcelayout3d };

import * as SWC from 'swc';

//

const CAMERA_DISTANCE2NODES_FACTOR = 150;

export default SWC.createComponent({

	props: [
		new SWC.Prop('width', window.innerWidth),
		new SWC.Prop('height', window.innerHeight),
		new SWC.Prop('jsonUrl'),
		new SWC.Prop('graphData', {
			nodes: [],
			links: []
		}),
		new SWC.Prop('numDimensions', 3),
		new SWC.Prop('nodeRelSize', 4), // volume per val unit
		new SWC.Prop('lineOpacity', 0.2),
		new SWC.Prop('autoColorBy'),
		new SWC.Prop('idField', 'id'),
		new SWC.Prop('valField', 'val'),
		new SWC.Prop('nameField', 'name'),
		new SWC.Prop('colorField', 'color'),
		new SWC.Prop('linkSourceField', 'source'),
		new SWC.Prop('linkTargetField', 'target'),
		new SWC.Prop('warmupTicks', 0), // how many times to tick the force engine at init before starting to render
		new SWC.Prop('cooldownTicks', Infinity),
		new SWC.Prop('cooldownTime', 15000) // ms
	],

	init: (domNode, state) => {
		// Wipe DOM
		domNode.innerHTML = '';

		// Add nav info section
		let navInfo;
		domNode.appendChild(navInfo = document.createElement('div'));
		navInfo.className = 'graph-nav-info';
		navInfo.textContent = "MOVE mouse & press LEFT/A: rotate, MIDDLE/S: zoom, RIGHT/D: pan";

		// Setup tooltip
		const toolTipElem = document.createElement('div');
		toolTipElem.classList.add('graph-tooltip');
		domNode.appendChild(toolTipElem);

		// Capture mouse coords on move
		const raycaster = new THREE.Raycaster();
		const mousePos = new THREE.Vector2();
		mousePos.x = -2; // Initialize off canvas
		mousePos.y = -2;
		domNode.addEventListener("mousemove", ev => {
			// update the mouse pos
			const offset = getOffset(domNode),
				relPos = {
					x: ev.pageX - offset.left,
					y: ev.pageY - offset.top
				};
			mousePos.x = (relPos.x / state.width) * 2 - 1;
			mousePos.y = -(relPos.y / state.height) * 2 + 1;

			// Move tooltip
			toolTipElem.style.top = (relPos.y - 40) + 'px';
			toolTipElem.style.left = (relPos.x - 20) + 'px';

			function getOffset(el) {
				const rect = el.getBoundingClientRect(),
					scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
					scrollTop = window.pageYOffset || document.documentElement.scrollTop;
				return { top: rect.top + scrollTop, left: rect.left + scrollLeft };
			}
		}, false);

		// Setup renderer
		state.renderer = new THREE.WebGLRenderer();
		domNode.appendChild(state.renderer.domElement);

		// Setup scene
		const scene = new THREE.Scene();
		scene.add(state.graphScene = new THREE.Group());

		// Setup camera
		state.camera = new THREE.PerspectiveCamera();
		state.camera.far = 20000;

		// Add camera interaction
		const tbControls = new THREE.TrackballControls(state.camera, state.renderer.domElement);

		//

		// Kick-off renderer
		(function animate() { // IIFE
			if(state.onFrame) state.onFrame();

			// Update tooltip
			raycaster.setFromCamera(mousePos, state.camera);
			const intersects = raycaster.intersectObjects(state.graphScene.children)
				.filter(o => o.object.name); // Check only objects with labels
			toolTipElem.textContent = intersects.length ? intersects[0].object.name : '';

			// Frame cycle
			tbControls.update();
			state.renderer.render(scene, state.camera);
			requestAnimationFrame(animate);
		})();
	},

	update: function updateFn(state) {
		resizeCanvas();

		state.onFrame = null; // Pause simulation

		if (!state.fetchingJson && state.jsonUrl && !state.graphData.nodes.length && !state.graphData.links.length) {
			// (Re-)load data
			state.fetchingJson = true;
			qwest.get(state.jsonUrl).then((_, json) => {
				state.fetchingJson = false;
				state.graphData = json;
				updateFn(state);  // Force re-update
			});
		}

		// Auto add color to uncolored nodes
		autoColorNodes(state.graphData.nodes, state.autoColorBy, state.colorField);

		// parse links
		state.graphData.links.forEach(link => {
			link.source = link[state.linkSourceField];
			link.target = link[state.linkTargetField];
		});

		// Add WebGL objects
		while (state.graphScene.children.length) { state.graphScene.remove(state.graphScene.children[0]) } // Clear the place

		state.graphData.nodes.forEach(node => {
			const nodeMaterial = new THREE.MeshBasicMaterial({ color: node[state.colorField] || 0xffffaa, transparent: true });
			nodeMaterial.opacity = 0.75;

			const sphere = new THREE.Mesh(
				new THREE.SphereGeometry(Math.cbrt(node[state.valField] || 1) * state.nodeRelSize, 8, 8),
				nodeMaterial
			);

			sphere.name = node[state.nameField]; // Add label

			state.graphScene.add(node.__sphere = sphere);
		});

		const lineMaterial = new THREE.LineBasicMaterial({ color: 0xf0f0f0, transparent: true });
		lineMaterial.opacity = state.lineOpacity;
		state.graphData.links.forEach(link => {
			const line = new THREE.Line(new THREE.Geometry(), lineMaterial);
			line.geometry.vertices=[new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,0)];

			state.graphScene.add(link.__line = line);
		});

		state.camera.lookAt(state.graphScene.position);
		state.camera.position.z = Math.cbrt(state.graphData.nodes.length) * CAMERA_DISTANCE2NODES_FACTOR;

		// Add force-directed layout
		const graph = ngraph.graph();
		state.graphData.nodes.forEach(node => { graph.addNode(node[state.idField]); });
		state.graphData.links.forEach(link => { graph.addLink(link.source, link.target); });
		const layout = ngraph['forcelayout' + (state.numDimensions === 2 ? '' : '3d')](graph);

		for (let i=0; i<state.warmupTicks; i++) { layout.step(); } // Initial ticks before starting to render

		let cntTicks = 0;
		const startTickTime = new Date();
		state.onFrame = layoutTick;

		//

		function resizeCanvas() {
			if (state.width && state.height) {
				state.renderer.setSize(state.width, state.height);
				state.camera.aspect = state.width/state.height;
				state.camera.updateProjectionMatrix();
			}
		}

		function layoutTick() {
			if (cntTicks++ > state.cooldownTicks || (new Date()) - startTickTime > state.cooldownTime) {
				state.onFrame = null; // Stop ticking graph
			}

			layout.step(); // Tick it

			// Update nodes position
			state.graphData.nodes.forEach(node => {
				const sphere = node.__sphere,
					pos = layout.getNodePosition(node[state.idField]);
				sphere.position.x = pos.x;
				sphere.position.y = pos.y || 0;
				sphere.position.z = pos.z || 0;
			});

			// Update links position
			state.graphData.links.forEach(link => {
				const line = link.__line,
					pos = layout.getLinkPosition(graph.getLink(link.source, link.target).id);

				line.geometry.vertices = [
					new THREE.Vector3(pos.from.x, pos.from.y || 0, pos.from.z || 0),
					new THREE.Vector3(pos.to.x, pos.to.y || 0, pos.to.z || 0)
				];

				line.geometry.verticesNeedUpdate = true;
				line.geometry.computeBoundingSphere();
			});
		}

		function autoColorNodes(nodes, colorBy, colorField) {
			if (!colorBy) return;

			// Color brewer paired set
			const colors = ['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99','#b15928'];

			const uncoloredNodes = nodes.filter(node => !node[colorField]),
				nodeGroups = {};

			uncoloredNodes.forEach(node => { nodeGroups[node[colorBy]] = null });
			Object.keys(nodeGroups).forEach((group, idx) => { nodeGroups[group] = idx });

			uncoloredNodes.forEach(node => {
				node[colorField] = parseInt(colors[nodeGroups[node[colorBy]] % colors.length].slice(1), 16);
			});
		}
	}
});

<commit_msg>Add lights and enhance sphere material<commit_after>import './3d-force-graph.css';

import './threeGlobal';
import 'three/examples/js/controls/TrackBallControls';

import graph from 'ngraph.graph';
import forcelayout from 'ngraph.forcelayout';
import forcelayout3d from 'ngraph.forcelayout3d';
const ngraph = { graph, forcelayout, forcelayout3d };

import * as SWC from 'swc';

//

const CAMERA_DISTANCE2NODES_FACTOR = 150;

export default SWC.createComponent({

	props: [
		new SWC.Prop('width', window.innerWidth),
		new SWC.Prop('height', window.innerHeight),
		new SWC.Prop('jsonUrl'),
		new SWC.Prop('graphData', {
			nodes: [],
			links: []
		}),
		new SWC.Prop('numDimensions', 3),
		new SWC.Prop('nodeRelSize', 4), // volume per val unit
		new SWC.Prop('lineOpacity', 0.2),
		new SWC.Prop('autoColorBy'),
		new SWC.Prop('idField', 'id'),
		new SWC.Prop('valField', 'val'),
		new SWC.Prop('nameField', 'name'),
		new SWC.Prop('colorField', 'color'),
		new SWC.Prop('linkSourceField', 'source'),
		new SWC.Prop('linkTargetField', 'target'),
		new SWC.Prop('warmupTicks', 0), // how many times to tick the force engine at init before starting to render
		new SWC.Prop('cooldownTicks', Infinity),
		new SWC.Prop('cooldownTime', 15000) // ms
	],

	init: (domNode, state) => {
		// Wipe DOM
		domNode.innerHTML = '';

		// Add nav info section
		let navInfo;
		domNode.appendChild(navInfo = document.createElement('div'));
		navInfo.className = 'graph-nav-info';
		navInfo.textContent = "MOVE mouse & press LEFT/A: rotate, MIDDLE/S: zoom, RIGHT/D: pan";

		// Setup tooltip
		const toolTipElem = document.createElement('div');
		toolTipElem.classList.add('graph-tooltip');
		domNode.appendChild(toolTipElem);

		// Capture mouse coords on move
		const raycaster = new THREE.Raycaster();
		const mousePos = new THREE.Vector2();
		mousePos.x = -2; // Initialize off canvas
		mousePos.y = -2;
		domNode.addEventListener("mousemove", ev => {
			// update the mouse pos
			const offset = getOffset(domNode),
				relPos = {
					x: ev.pageX - offset.left,
					y: ev.pageY - offset.top
				};
			mousePos.x = (relPos.x / state.width) * 2 - 1;
			mousePos.y = -(relPos.y / state.height) * 2 + 1;

			// Move tooltip
			toolTipElem.style.top = (relPos.y - 40) + 'px';
			toolTipElem.style.left = (relPos.x - 20) + 'px';

			function getOffset(el) {
				const rect = el.getBoundingClientRect(),
					scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
					scrollTop = window.pageYOffset || document.documentElement.scrollTop;
				return { top: rect.top + scrollTop, left: rect.left + scrollLeft };
			}
		}, false);

		// Setup renderer
		state.renderer = new THREE.WebGLRenderer();
		domNode.appendChild(state.renderer.domElement);

		// Setup scene
		const scene = new THREE.Scene();
		scene.add(state.graphScene = new THREE.Group());

		// Add lights
		scene.add(new THREE.AmbientLight(0xbbbbbb));
		scene.add(new THREE.DirectionalLight(0xffffff, 0.6));

		// Setup camera
		state.camera = new THREE.PerspectiveCamera();
		state.camera.far = 20000;

		// Add camera interaction
		const tbControls = new THREE.TrackballControls(state.camera, state.renderer.domElement);

		//

		// Kick-off renderer
		(function animate() { // IIFE
			if(state.onFrame) state.onFrame();

			// Update tooltip
			raycaster.setFromCamera(mousePos, state.camera);
			const intersects = raycaster.intersectObjects(state.graphScene.children)
				.filter(o => o.object.name); // Check only objects with labels
			toolTipElem.textContent = intersects.length ? intersects[0].object.name : '';

			// Frame cycle
			tbControls.update();
			state.renderer.render(scene, state.camera);
			requestAnimationFrame(animate);
		})();
	},

	update: function updateFn(state) {
		resizeCanvas();

		state.onFrame = null; // Pause simulation

		if (!state.fetchingJson && state.jsonUrl && !state.graphData.nodes.length && !state.graphData.links.length) {
			// (Re-)load data
			state.fetchingJson = true;
			qwest.get(state.jsonUrl).then((_, json) => {
				state.fetchingJson = false;
				state.graphData = json;
				updateFn(state);  // Force re-update
			});
		}

		// Auto add color to uncolored nodes
		autoColorNodes(state.graphData.nodes, state.autoColorBy, state.colorField);

		// parse links
		state.graphData.links.forEach(link => {
			link.source = link[state.linkSourceField];
			link.target = link[state.linkTargetField];
		});

		// Add WebGL objects
		while (state.graphScene.children.length) { state.graphScene.remove(state.graphScene.children[0]) } // Clear the place

		state.graphData.nodes.forEach(node => {
			const nodeMaterial = new THREE.MeshLambertMaterial({ color: node[state.colorField] || 0xffffaa, transparent: true });
			nodeMaterial.opacity = 0.75;

			const sphere = new THREE.Mesh(
				new THREE.SphereGeometry(Math.cbrt(node[state.valField] || 1) * state.nodeRelSize, 8, 8),
				nodeMaterial
			);

			sphere.name = node[state.nameField]; // Add label

			state.graphScene.add(node.__sphere = sphere);
		});

		const lineMaterial = new THREE.LineBasicMaterial({ color: 0xf0f0f0, transparent: true });
		lineMaterial.opacity = state.lineOpacity;
		state.graphData.links.forEach(link => {
			const line = new THREE.Line(new THREE.Geometry(), lineMaterial);
			line.geometry.vertices=[new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,0)];

			state.graphScene.add(link.__line = line);
		});

		state.camera.lookAt(state.graphScene.position);
		state.camera.position.z = Math.cbrt(state.graphData.nodes.length) * CAMERA_DISTANCE2NODES_FACTOR;

		// Add force-directed layout
		const graph = ngraph.graph();
		state.graphData.nodes.forEach(node => { graph.addNode(node[state.idField]); });
		state.graphData.links.forEach(link => { graph.addLink(link.source, link.target); });
		const layout = ngraph['forcelayout' + (state.numDimensions === 2 ? '' : '3d')](graph);

		for (let i=0; i<state.warmupTicks; i++) { layout.step(); } // Initial ticks before starting to render

		let cntTicks = 0;
		const startTickTime = new Date();
		state.onFrame = layoutTick;

		//

		function resizeCanvas() {
			if (state.width && state.height) {
				state.renderer.setSize(state.width, state.height);
				state.camera.aspect = state.width/state.height;
				state.camera.updateProjectionMatrix();
			}
		}

		function layoutTick() {
			if (cntTicks++ > state.cooldownTicks || (new Date()) - startTickTime > state.cooldownTime) {
				state.onFrame = null; // Stop ticking graph
			}

			layout.step(); // Tick it

			// Update nodes position
			state.graphData.nodes.forEach(node => {
				const sphere = node.__sphere,
					pos = layout.getNodePosition(node[state.idField]);
				sphere.position.x = pos.x;
				sphere.position.y = pos.y || 0;
				sphere.position.z = pos.z || 0;
			});

			// Update links position
			state.graphData.links.forEach(link => {
				const line = link.__line,
					pos = layout.getLinkPosition(graph.getLink(link.source, link.target).id);

				line.geometry.vertices = [
					new THREE.Vector3(pos.from.x, pos.from.y || 0, pos.from.z || 0),
					new THREE.Vector3(pos.to.x, pos.to.y || 0, pos.to.z || 0)
				];

				line.geometry.verticesNeedUpdate = true;
				line.geometry.computeBoundingSphere();
			});
		}

		function autoColorNodes(nodes, colorBy, colorField) {
			if (!colorBy) return;

			// Color brewer paired set
			const colors = ['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99','#b15928'];

			const uncoloredNodes = nodes.filter(node => !node[colorField]),
				nodeGroups = {};

			uncoloredNodes.forEach(node => { nodeGroups[node[colorBy]] = null });
			Object.keys(nodeGroups).forEach((group, idx) => { nodeGroups[group] = idx });

			uncoloredNodes.forEach(node => {
				node[colorField] = parseInt(colors[nodeGroups[node[colorBy]] % colors.length].slice(1), 16);
			});
		}
	}
});

<|endoftext|>


<commit_before>import './3d-force-graph.css';

import './threeGlobal';
import 'three/examples/js/controls/TrackBallControls';

import graph from 'ngraph.graph';
import forcelayout3d from 'ngraph.forcelayout3d';
const ngraph = { graph, forcelayout3d };

export default function() {

	const CAMERA_DISTANCE2NODES_FACTOR = 150;

	class CompProp {
		constructor(name, initVal = null, redigest = true, onChange = newVal => {}) {
			this.name = name;
			this.initVal = initVal;
			this.redigest = redigest;
			this.onChange = onChange;
		}
	}

	const env = { // Holds component state
		initialised: false,
		onFrame: () => {}
	};

	const exposeProps = [
		new CompProp('width', window.innerWidth),
		new CompProp('height', window.innerHeight),
		new CompProp('graphData', {
			nodes: { 1: { name: 'mock', val: 1 } },
			links: [[1, 1]] // [from, to]
		}),
		new CompProp('nodeRelSize', 4), // volume per val unit
		new CompProp('lineOpacity', 0.2),
		new CompProp('valAccessor', node => node.val),
		new CompProp('nameAccessor', node => node.name),
		new CompProp('colorAccessor', node => node.color),
		new CompProp('warmUpTicks', 0), // how many times to tick the force engine at init before starting to render
		new CompProp('coolDownTicks', Infinity),
		new CompProp('coolDownTime', 15000) // ms
	];

	function initStatic() {
		// Wipe DOM
		env.domNode.innerHTML = '';

		// Add nav info section
		const navInfo = document.createElement('div');
		navInfo.classList.add('graph-nav-info');
		navInfo.innerHTML = "MOVE mouse & press LEFT/A: rotate, MIDDLE/S: zoom, RIGHT/D: pan";
		env.domNode.appendChild(navInfo);

		// Setup tooltip
		env.toolTipElem = document.createElement('div');
		env.toolTipElem.classList.add('graph-tooltip');
		env.domNode.appendChild(env.toolTipElem);

		// Capture mouse coords on move
		env.raycaster = new THREE.Raycaster();
		env.mouse = new THREE.Vector2();
		env.mouse.x = -2; // Initialize off canvas
		env.mouse.y = -2;
		env.domNode.addEventListener("mousemove", ev => {
			// update the mouse pos
			const offset = getOffset(env.domNode),
				relPos = {
					x: ev.pageX - offset.left,
					y: ev.pageY - offset.top
				};
			env.mouse.x = (relPos.x / env.width) * 2 - 1;
			env.mouse.y = -(relPos.y / env.height) * 2 + 1;

			// Move tooltip
			env.toolTipElem.style.top = (relPos.y - 40) + 'px';
			env.toolTipElem.style.left = (relPos.x - 20) + 'px';

			function getOffset(el) {
				const rect = el.getBoundingClientRect(),
					scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
					scrollTop = window.pageYOffset || document.documentElement.scrollTop;
				return { top: rect.top + scrollTop, left: rect.left + scrollLeft };
			}
		}, false);

		// Setup camera
		env.camera = new THREE.PerspectiveCamera();
		env.camera.far = 20000;
		env.camera.position.z = 1000;

		// Setup scene
		env.scene = new THREE.Scene();

		// Setup renderer
		env.renderer = new THREE.WebGLRenderer();
		env.domNode.appendChild(env.renderer.domElement);

		// Add camera interaction
		env.controls = new THREE.TrackballControls(env.camera, env.renderer.domElement);

		env.initialised = true;

		//

		// Kick-off renderer
		(function animate() { // IIFE
			env.onFrame();

			// Update tooltip
			env.raycaster.setFromCamera(env.mouse, env.camera);
			const intersects = env.raycaster.intersectObjects(env.scene.children);
			env.toolTipElem.innerHTML = intersects.length ? intersects[0].object.name || '' : '';

			// Frame cycle
			env.controls.update();
			env.renderer.render(env.scene, env.camera);
			requestAnimationFrame(animate);
		})()
	}

	function digest() {
		if (!env.initialised) { return }

		resizeCanvas();

		env.onFrame = ()=>{}; // Clear previous frame hook
		env.scene = new THREE.Scene(); // Clear the place

		// Build graph with data
		const graph = ngraph.graph();
		for (let nodeId in env.graphData.nodes) {
			graph.addNode(nodeId, env.graphData.nodes[nodeId]);
		}
		for (let link of env.graphData.links) {
			graph.addLink(...link, {});
		}

		// Add WebGL objects
		graph.forEachNode(node => {
			const nodeMaterial = new THREE.MeshBasicMaterial({ color: env.colorAccessor(node.data) || 0xffffaa, transparent: true });
			nodeMaterial.opacity = 0.75;

			const sphere = new THREE.Mesh(
				new THREE.SphereGeometry(Math.cbrt(env.valAccessor(node.data) || 1) * env.nodeRelSize),
				nodeMaterial
			);
			sphere.name = env.nameAccessor(node.data) || '';

			env.scene.add(node.data.sphere = sphere)
		});

		const lineMaterial = new THREE.MeshBasicMaterial({ color: 0xf0f0f0, transparent: true });
		lineMaterial.opacity = env.lineOpacity;
		graph.forEachLink(link => {
			const line = new THREE.Line(new THREE.Geometry(), lineMaterial),
				fromName = getNodeName(link.fromId),
				toName = getNodeName(link.toId);
			if (fromName && toName) { line.name = `${fromName} > ${toName}`; }

			env.scene.add(link.data.line = line)

			function getNodeName(nodeId) {
				return env.nameAccessor(graph.getNode(nodeId).data);
			}
		});

		env.camera.lookAt(env.scene.position);
		env.camera.position.z = Math.cbrt(Object.keys(env.graphData.nodes).length) * CAMERA_DISTANCE2NODES_FACTOR;

		// Add force-directed layout
		const layout = ngraph.forcelayout3d(graph);

		for (let i=0; i<env.warmUpTicks; i++) { layout.step(); } // Initial ticks before starting to render

		let cntTicks = 0;
		const startTickTime = new Date();
		env.onFrame = layoutTick;

		//

		function resizeCanvas() {
			if (env.width && env.height) {
				env.renderer.setSize(env.width, env.height);
				env.camera.aspect = env.width/env.height;
				env.camera.updateProjectionMatrix();
			}
		}

		function layoutTick() {
			if (cntTicks++ > env.coolDownTicks || (new Date()) - startTickTime > env.coolDownTime) {
				env.onFrame = ()=>{}; // Stop ticking graph
			}

			layout.step(); // Tick it

			// Update nodes position
			graph.forEachNode(node => {
				const sphere = node.data.sphere,
					pos = layout.getNodePosition(node.id);

				sphere.position.x = pos.x;
				sphere.position.y = pos.y;
				sphere.position.z = pos.z;
			});

			// Update links position
			graph.forEachLink(link => {
				const line = link.data.line,
					pos = layout.getLinkPosition(link.id);

				line.geometry.vertices = [
					new THREE.Vector3(pos.from.x, pos.from.y, pos.from.z),
					new THREE.Vector3(pos.to.x, pos.to.y, pos.to.z)
				];

				line.geometry.verticesNeedUpdate = true;
				line.geometry.computeBoundingSphere();
			});
		}
	}

	// Component constructor
	function chart(nodeElement) {
		env.domNode = nodeElement;

		initStatic();
		digest();

		return chart;
	}

	// Getter/setter methods
	exposeProps.forEach(prop => {
		chart[prop.name] = getSetEnv(prop.name, prop.redigest, prop.onChange);
		env[prop.name] = prop.initVal;
		prop.onChange(prop.initVal);

		function getSetEnv(prop, redigest = false,  onChange = newVal => {}) {
			return _ => {
				if (!arguments.length) { return env[prop] }
				env[prop] = _;
				onChange(_);
				if (redigest) { digest() }
				return chart;
			}
		}
	});

	// Reset to default state
	chart.resetState = function() {
		this.graphData({nodes: [], links: []})
			.nodeRelSize(4)
			.lineOpacity(0.2)
			.valAccessor(node => node.val)
			.nameAccessor(node => node.name)
			.colorAccessor(node => node.color)
			.warmUpTicks(0)
			.coolDownTicks(Infinity)
			.coolDownTime(15000); // ms

		return this;
	};

	chart.resetState(); // Set defaults at instantiation

	return chart;
};
<commit_msg>Improve spheres geometry<commit_after>import './3d-force-graph.css';

import './threeGlobal';
import 'three/examples/js/controls/TrackBallControls';

import graph from 'ngraph.graph';
import forcelayout3d from 'ngraph.forcelayout3d';
const ngraph = { graph, forcelayout3d };

export default function() {

	const CAMERA_DISTANCE2NODES_FACTOR = 150;

	class CompProp {
		constructor(name, initVal = null, redigest = true, onChange = newVal => {}) {
			this.name = name;
			this.initVal = initVal;
			this.redigest = redigest;
			this.onChange = onChange;
		}
	}

	const env = { // Holds component state
		initialised: false,
		onFrame: () => {}
	};

	const exposeProps = [
		new CompProp('width', window.innerWidth),
		new CompProp('height', window.innerHeight),
		new CompProp('graphData', {
			nodes: { 1: { name: 'mock', val: 1 } },
			links: [[1, 1]] // [from, to]
		}),
		new CompProp('nodeRelSize', 4), // volume per val unit
		new CompProp('lineOpacity', 0.2),
		new CompProp('valAccessor', node => node.val),
		new CompProp('nameAccessor', node => node.name),
		new CompProp('colorAccessor', node => node.color),
		new CompProp('warmUpTicks', 0), // how many times to tick the force engine at init before starting to render
		new CompProp('coolDownTicks', Infinity),
		new CompProp('coolDownTime', 15000) // ms
	];

	function initStatic() {
		// Wipe DOM
		env.domNode.innerHTML = '';

		// Add nav info section
		const navInfo = document.createElement('div');
		navInfo.classList.add('graph-nav-info');
		navInfo.innerHTML = "MOVE mouse & press LEFT/A: rotate, MIDDLE/S: zoom, RIGHT/D: pan";
		env.domNode.appendChild(navInfo);

		// Setup tooltip
		env.toolTipElem = document.createElement('div');
		env.toolTipElem.classList.add('graph-tooltip');
		env.domNode.appendChild(env.toolTipElem);

		// Capture mouse coords on move
		env.raycaster = new THREE.Raycaster();
		env.mouse = new THREE.Vector2();
		env.mouse.x = -2; // Initialize off canvas
		env.mouse.y = -2;
		env.domNode.addEventListener("mousemove", ev => {
			// update the mouse pos
			const offset = getOffset(env.domNode),
				relPos = {
					x: ev.pageX - offset.left,
					y: ev.pageY - offset.top
				};
			env.mouse.x = (relPos.x / env.width) * 2 - 1;
			env.mouse.y = -(relPos.y / env.height) * 2 + 1;

			// Move tooltip
			env.toolTipElem.style.top = (relPos.y - 40) + 'px';
			env.toolTipElem.style.left = (relPos.x - 20) + 'px';

			function getOffset(el) {
				const rect = el.getBoundingClientRect(),
					scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
					scrollTop = window.pageYOffset || document.documentElement.scrollTop;
				return { top: rect.top + scrollTop, left: rect.left + scrollLeft };
			}
		}, false);

		// Setup camera
		env.camera = new THREE.PerspectiveCamera();
		env.camera.far = 20000;
		env.camera.position.z = 1000;

		// Setup scene
		env.scene = new THREE.Scene();

		// Setup renderer
		env.renderer = new THREE.WebGLRenderer();
		env.domNode.appendChild(env.renderer.domElement);

		// Add camera interaction
		env.controls = new THREE.TrackballControls(env.camera, env.renderer.domElement);

		env.initialised = true;

		//

		// Kick-off renderer
		(function animate() { // IIFE
			env.onFrame();

			// Update tooltip
			env.raycaster.setFromCamera(env.mouse, env.camera);
			const intersects = env.raycaster.intersectObjects(env.scene.children);
			env.toolTipElem.innerHTML = intersects.length ? intersects[0].object.name || '' : '';

			// Frame cycle
			env.controls.update();
			env.renderer.render(env.scene, env.camera);
			requestAnimationFrame(animate);
		})()
	}

	function digest() {
		if (!env.initialised) { return }

		resizeCanvas();

		env.onFrame = ()=>{}; // Clear previous frame hook
		env.scene = new THREE.Scene(); // Clear the place

		// Build graph with data
		const graph = ngraph.graph();
		for (let nodeId in env.graphData.nodes) {
			graph.addNode(nodeId, env.graphData.nodes[nodeId]);
		}
		for (let link of env.graphData.links) {
			graph.addLink(...link, {});
		}

		// Add WebGL objects
		graph.forEachNode(node => {
			const nodeMaterial = new THREE.MeshBasicMaterial({ color: env.colorAccessor(node.data) || 0xffffaa, transparent: true });
			nodeMaterial.opacity = 0.75;

			const sphere = new THREE.Mesh(
				new THREE.SphereGeometry(Math.cbrt(env.valAccessor(node.data) || 1) * env.nodeRelSize, 8, 8),
				nodeMaterial
			);
			sphere.name = env.nameAccessor(node.data) || '';

			env.scene.add(node.data.sphere = sphere)
		});

		const lineMaterial = new THREE.MeshBasicMaterial({ color: 0xf0f0f0, transparent: true });
		lineMaterial.opacity = env.lineOpacity;
		graph.forEachLink(link => {
			const line = new THREE.Line(new THREE.Geometry(), lineMaterial),
				fromName = getNodeName(link.fromId),
				toName = getNodeName(link.toId);
			if (fromName && toName) { line.name = `${fromName} > ${toName}`; }

			env.scene.add(link.data.line = line)

			function getNodeName(nodeId) {
				return env.nameAccessor(graph.getNode(nodeId).data);
			}
		});

		env.camera.lookAt(env.scene.position);
		env.camera.position.z = Math.cbrt(Object.keys(env.graphData.nodes).length) * CAMERA_DISTANCE2NODES_FACTOR;

		// Add force-directed layout
		const layout = ngraph.forcelayout3d(graph);

		for (let i=0; i<env.warmUpTicks; i++) { layout.step(); } // Initial ticks before starting to render

		let cntTicks = 0;
		const startTickTime = new Date();
		env.onFrame = layoutTick;

		//

		function resizeCanvas() {
			if (env.width && env.height) {
				env.renderer.setSize(env.width, env.height);
				env.camera.aspect = env.width/env.height;
				env.camera.updateProjectionMatrix();
			}
		}

		function layoutTick() {
			if (cntTicks++ > env.coolDownTicks || (new Date()) - startTickTime > env.coolDownTime) {
				env.onFrame = ()=>{}; // Stop ticking graph
			}

			layout.step(); // Tick it

			// Update nodes position
			graph.forEachNode(node => {
				const sphere = node.data.sphere,
					pos = layout.getNodePosition(node.id);

				sphere.position.x = pos.x;
				sphere.position.y = pos.y;
				sphere.position.z = pos.z;
			});

			// Update links position
			graph.forEachLink(link => {
				const line = link.data.line,
					pos = layout.getLinkPosition(link.id);

				line.geometry.vertices = [
					new THREE.Vector3(pos.from.x, pos.from.y, pos.from.z),
					new THREE.Vector3(pos.to.x, pos.to.y, pos.to.z)
				];

				line.geometry.verticesNeedUpdate = true;
				line.geometry.computeBoundingSphere();
			});
		}
	}

	// Component constructor
	function chart(nodeElement) {
		env.domNode = nodeElement;

		initStatic();
		digest();

		return chart;
	}

	// Getter/setter methods
	exposeProps.forEach(prop => {
		chart[prop.name] = getSetEnv(prop.name, prop.redigest, prop.onChange);
		env[prop.name] = prop.initVal;
		prop.onChange(prop.initVal);

		function getSetEnv(prop, redigest = false,  onChange = newVal => {}) {
			return _ => {
				if (!arguments.length) { return env[prop] }
				env[prop] = _;
				onChange(_);
				if (redigest) { digest() }
				return chart;
			}
		}
	});

	// Reset to default state
	chart.resetState = function() {
		this.graphData({nodes: [], links: []})
			.nodeRelSize(4)
			.lineOpacity(0.2)
			.valAccessor(node => node.val)
			.nameAccessor(node => node.name)
			.colorAccessor(node => node.color)
			.warmUpTicks(0)
			.coolDownTicks(Infinity)
			.coolDownTime(15000); // ms

		return this;
	};

	chart.resetState(); // Set defaults at instantiation

	return chart;
};
<|endoftext|>