<commit_before>import React from "react";
import PropTypes from "prop-types";
import { Selection, Helpers, Collection, Axis, Scale, Domain, Box } from "victory-core";
import { assign, defaults, isEqual, isFunction, pick } from "lodash";

const getScale = (props) => {
  const { scale = {}, dimension = "x" } = props;
  if (scale[dimension]) {
    return scale[dimension];
  }
  const fallbackScale = Scale.getBaseScale(props, dimension);
  const range = Helpers.getRange(props, dimension);
  const domain = Domain.getDomainFromProps(props, dimension) || [0, 1];
  fallbackScale.range(range).domain(domain);
  return fallbackScale;
};

const toRange = (props, domain) => {
  const scale = getScale(props);
  return [scale(Math.min(...domain)), scale(Math.max(...domain))];
};

const toDomain = (props, range) => {
  const scale = getScale(props);
  return [scale.invert(Math.min(...range)), scale.invert(Math.max(...range))];
};

const getFullRange = (props) => {
  const scale = getScale(props);
  return scale.range();
};

const getFullDomain = (props) => {
  const scale = getScale(props);
  return scale.domain();
};

const withinBound = (value, bound) => {
  return value >= Math.min(...bound) && value <= Math.max(...bound);
};

const getActiveHandle = (props, position, range) => {
  const width = props.handleWidth / 2;
  const { dimension } = props;
  const getHandle = (type) => {
    const base = {
      min: dimension === "x" ? Math.min(...range) : Math.max(...range),
      max: dimension === "x" ? Math.max(...range) : Math.min(...range)
    };
    return [base[type] - width, base[type] + width];
  };
  const active = ["min", "max"].reduce((memo, type) => {
    memo[type] = withinBound(position, getHandle(type)) ? type : undefined;
    return memo;
  }, {});
  return active.min && active.max ? "both" : active.min || active.max;
};

const getMinimumDomain = () => {
  return [0, 1 / Number.MAX_SAFE_INTEGER];
};

const getCurrentDomain = (props) => {
  const { currentDomain, cachedBrushDomain, brushDomain } = props;
  if (brushDomain && cachedBrushDomain && !isEqual(brushDomain, cachedBrushDomain)) {
    return brushDomain;
  }
  return currentDomain || brushDomain || getFullDomain(props);
};

const panBox = (props, position) => {
  const { brushDomain, startPosition, currentDomain } = props;
  const range = currentDomain ? toRange(props, currentDomain) : toRange(props, brushDomain);
  const fullRange = getFullRange(props);
  const size = Math.abs(range[1] - range[0]);
  const globalMin = Math.min(...fullRange);
  const globalMax = Math.max(...fullRange);
  const delta = startPosition ? startPosition - position : 0;
  const min = Math.min(...range) - delta;
  const max = Math.max(...range) - delta;
  const constrainedMin = min > globalMax - size ? globalMax - size : Math.max(min, globalMin);
  const constrainedMax = max < globalMin + size ? globalMin + size : Math.min(max, globalMax);
  return [constrainedMin, constrainedMax];
};

export default class VictoryBrushLine extends React.Component {
  static propTypes = {
    allowDrag: PropTypes.bool,
    allowResize: PropTypes.bool,
    brushAreaComponent: PropTypes.element,
    brushAreaStyle: PropTypes.object,
    brushComponent: PropTypes.element,
    brushDimension: PropTypes.oneOf(["x", "y"]),
    brushDomain: PropTypes.array,
    brushStyle: PropTypes.object,
    brushWidth: PropTypes.number,
    className: PropTypes.string,
    dimension: PropTypes.oneOf(["x", "y"]),
    disable: PropTypes.bool,
    events: PropTypes.object,
    groupComponent: PropTypes.element,
    handleComponent: PropTypes.element,
    handleStyle: PropTypes.object,
    handleWidth: PropTypes.number,
    lineComponent: PropTypes.element,
    name: PropTypes.string,
    onBrushDomainChange: PropTypes.func,
    scale: PropTypes.object,
    style: PropTypes.object,
    type: PropTypes.string
  };

  static defaultProps = {
    allowDrag: true,
    allowResize: true,
    brushAreaComponent: <Box/>,
    brushAreaStyle: {
      cursor: "crosshair",
      stroke: "none",
      fill: "black",
      opacity: (d, a) => a ? 0.2 : 0.1 // eslint-disable-line no-magic-numbers
    },
    brushComponent: <Box/>,
    brushStyle: {
      strokeWidth: 2,
      stroke: (d, a) => a ? "black" : "none",
      fill: "black",
      cursor: "move",
      opacity: (d, a) => a ? 0.4 : 0.3 // eslint-disable-line no-magic-numbers
    },
    brushWidth: 10,
    groupComponent: <g/>,
    handleComponent: <Box/>,
    handleStyle: {
      stroke: "none",
      fill: "none"
    },
    handleWidth: 10,
    lineComponent: <Axis/>
  };

  static defaultEvents = function (props) {
    return props.disable ? undefined : [{
      target: props.type,
      eventHandlers: {
        onMouseEnter: (evt, targetProps) => {
          evt.preventDefault();
          const { dimension, allowResize } = targetProps;
          const position = Selection.getSVGEventCoordinates(evt)[dimension];
          const fullDomain = getFullDomain(targetProps);
          const currentDomain = getCurrentDomain(targetProps) || fullDomain;
          const range = toRange(targetProps, currentDomain);
          const activeHandle = allowResize && getActiveHandle(targetProps, position, range);
          const activeBrushes = {
            brushArea: isEqual(currentDomain, fullDomain),
            brush: withinBound(position, range) && !isEqual(fullDomain, currentDomain),
            minHandle: activeHandle === "min" || activeHandle === "both",
            maxHandle: activeHandle === "min" || activeHandle === "both"
          };
          return [{
            mutation: () => ({ activeBrushes, currentDomain: targetProps.currentDomain })
          }];
        },
        onMouseDown: (evt, targetProps) => {
          evt.preventDefault();
          const { allowResize, allowDrag, dimension, brushDomain, activeBrushes } = targetProps;

          // Don't trigger events for static brushes
          if (!allowResize && !allowDrag) {
            return [];
          }

          const fullDomain = getFullDomain(targetProps);
          const currentDomain = getCurrentDomain(targetProps);
          const position = Selection.getSVGEventCoordinates(evt)[dimension];
          const range = toRange(targetProps, currentDomain);
          const activeHandle = allowResize && getActiveHandle(targetProps, position, range);
          // If the event occurs in any of the handle regions, start a resize
          if (activeHandle) {
            return [{
              mutation: () => {
                return ({
                  isSelecting: true, activeHandle, currentDomain, cachedBrushDomain: brushDomain,
                  startPosition: position, activeBrushes
                });
              }
            }];
          } else if (withinBound(position, range) && !isEqual(fullDomain, currentDomain)) {
            // if the event occurs within a selected region start a panning event, unless the whole
            // domain is selected
            return [{
              mutation: () => ({
                isPanning: allowDrag, startPosition: position,
                currentDomain, cachedBrushDomain: brushDomain, activeBrushes
              })
            }];
          } else {
            // if the event occurs outside the region, or if the whole domain is selected,
            // start a new selection
            return allowResize ? [{
              mutation: () => ({
                isSelecting: allowResize,
                currentDomain: getMinimumDomain(),
                cachedBrushDomain: brushDomain,
                startPosition: position,
                activeBrushes
              })
            }] : [];
          }
        },
        onMouseMove: (evt, targetProps) => { // eslint-disable-line max-statements, complexity
          const {
            isPanning, isSelecting, allowResize, allowDrag, dimension, onBrushDomainChange,
            cachedBrushDomain
          } = targetProps;
          if (isPanning || isSelecting) {
            evt.preventDefault();
            evt.stopPropagation();
          }
          const position = Selection.getSVGEventCoordinates(evt)[dimension];
          const fullDomain = getFullDomain(targetProps);
          const domain = getCurrentDomain(targetProps) || fullDomain;
          const initialRange = toRange(targetProps, domain);
          const activeHandle = getActiveHandle(targetProps, position, initialRange);
          const activeBrushes = {
            brushArea: isEqual(domain, fullDomain),
            brush: withinBound(position, initialRange) && !isEqual(fullDomain, domain),
            minHandle: activeHandle === "min" || activeHandle === "both",
            maxHandle: activeHandle === "max" || activeHandle === "both"
          };
          if (!targetProps.isPanning && !targetProps.isSelecting) {
            return [{
              mutation: () => ({
                activeBrushes, currentDomain: targetProps.currentDomain, cachedBrushDomain
              })
            }];
          }
          if (allowDrag && isPanning) {
            const fullRange = getFullRange(targetProps);
            const range = panBox(targetProps, position);
            const currentDomain = toDomain(targetProps, range);
            const startPosition = Math.max(...range) >= Math.max(...fullRange) ||
              Math.min(...range) <= Math.min(...fullRange) ?
              targetProps.startPosition : position;
            const mutatedProps = {
              startPosition, isPanning: true, currentDomain, cachedBrushDomain,
              activeBrushes: { brush: true }
            };

            if (isFunction(onBrushDomainChange)) {
              onBrushDomainChange(currentDomain, defaults({}, mutatedProps, targetProps));
            }
            return [{
              mutation: () => mutatedProps
            }];
          } else if (allowResize && isSelecting) {
            const range = toRange(targetProps, targetProps.currentDomain);
            const oppositeHandle = targetProps.activeHandle === "min" ? "max" : "min";
            const handle = targetProps.activeHandle &&
              getActiveHandle(targetProps, position, range) === "both" ?
                oppositeHandle : targetProps.activeHandle;
            let currentDomain;
            if (!handle) {
              currentDomain = toDomain(targetProps, [targetProps.startPosition, position]);
            } else {
              const rangeMax = dimension === "x" ? Math.max(...range) : Math.min(...range);
              const rangeMin = dimension === "x" ? Math.min(...range) : Math.max(...range);
              const min = handle === "max" ? rangeMin : position;
              const max = handle === "min" ? rangeMax : position;
              currentDomain = toDomain(targetProps, [min, max]);
            }
            const mutatedProps = {
              currentDomain, startPosition: targetProps.startPosition,
              isSelecting, activeHandle: handle, cachedBrushDomain,
              activeBrushes: {
                brush: true, minHandle: activeHandle === "min", maxHandle: activeHandle === "max"
              }
            };
            if (isFunction(onBrushDomainChange)) {
              onBrushDomainChange(currentDomain, defaults({}, mutatedProps, targetProps));
            }
            return [{
              mutation: () => (mutatedProps)
            }];
          }
          return [];
        },
        onMouseUp(evt, targetProps) {
          const {
            onBrushDomainChange, currentDomain, allowResize, activeBrushes, cachedBrushDomain
          } = targetProps;
          // if the mouse hasn't moved since a mouseDown event, select the whole domain region
          const mutatedProps = {
            isPanning: false, isSelecting: false, activeHandle: null, startPosition: null,
            currentDomain, cachedBrushDomain, activeBrushes
          };
          if (allowResize && isFunction(onBrushDomainChange)) {
            onBrushDomainChange(currentDomain, defaults({}, mutatedProps, targetProps));
          }
          return [{
            mutation: () => mutatedProps
          }];
        },
        onMouseLeave(evt, targetProps) {
          const { currentDomain, cachedBrushDomain } = targetProps;
          return [{
            mutation: () => ({
              isPanning: false, isSelecting: false, activeHandle: null, startPosition: null,
              currentDomain, cachedBrushDomain, activeBrushes: {}
            })
          }];
        }
      }
    }];
  };

  getRectDimensions(props, domain) {
    const { dimension, brushWidth } = props;
    domain = domain || getCurrentDomain(props);
    const range = toRange(props, domain);
    const coordinates = dimension === "x" ?
      { y1: props.y1, y2: props.y2, x1: Math.min(...range), x2: Math.max(...range) } :
      { x1: props.x1, x2: props.x2, y1: Math.min(...range), y2: Math.max(...range) };
    const { x1, x2, y1, y2 } = coordinates;
    const offset = {
      x: dimension === "x" ? 0 : brushWidth / 2,
      y: dimension === "y" ? 0 : brushWidth / 2
    };

    const x = Math.min(x1, x2) - offset.x;
    const y = Math.min(y1, y2) - offset.y;
    const width = Math.max(x1, x2) + offset.x - x;
    const height = Math.max(y1, y2) + offset.y - y;
    return { x, y, width, height };
  }

  getHandleDimensions(props) {
    const { dimension, brushWidth, handleWidth, x1, x2, y1, y2 } = props;
    const domain = getCurrentDomain(props);
    const range = toRange(props, domain);
    const defaultX = Math.min(x1, x2) - (brushWidth / 2);
    const defaultY = Math.min(y1, y2) - (brushWidth / 2);
    const x = {
      min: dimension === "x" ? Math.min(...range) - handleWidth / 2 : defaultX,
      max: dimension === "x" ? Math.max(...range) - handleWidth / 2 : defaultX
    };
    const y = {
      min: dimension === "y" ? Math.max(...range) - handleWidth / 2 : defaultY,
      max: dimension === "y" ? Math.min(...range) - handleWidth / 2 : defaultY
    };
    const width = dimension === "x" ? handleWidth : brushWidth;
    const height = dimension === "x" ? brushWidth : handleWidth;

    return {
      min: { x: x.min, y: y.min, width, height },
      max: { x: x.max, y: y.max, width, height }
    };
  }

  renderHandles(props) {
    const {
      handleComponent, handleStyle, dimension, datum = {}, activeBrushes = {},
      brushDomain, currentDomain
    } = props;
    if (!brushDomain && !currentDomain) {
      return null;
    }
    const domain = getCurrentDomain(props);
    const handleDimensions = this.getHandleDimensions(props);
    const cursor = dimension === "x" ? "ew-resize" : "ns-resize";
    const style = assign({ cursor }, handleStyle);
    const minDatum = assign({ handleValue: Collection.getMinValue(domain) }, datum);
    const maxDatum = assign({ handleValue: Collection.getMaxValue(domain) }, datum);
    const minHandleProps = assign({
      key: "min",
      style: Helpers.evaluateStyle(style, minDatum, activeBrushes.minHandle)
    }, handleDimensions.min);
    const maxHandleProps = assign(
      {
        key: "max",
        style: Helpers.evaluateStyle(style, maxDatum, activeBrushes.maxHandle)
      }, handleDimensions.max);
    return [
      React.cloneElement(handleComponent, minHandleProps),
      React.cloneElement(handleComponent, maxHandleProps)
    ];
  }

  renderBrush(props) {
    const {
      brushComponent, brushStyle, activeBrushes = {}, datum = {}, brushDomain, currentDomain
    } = props;
    if (!brushDomain && !currentDomain) {
      return null;
    }
    const rectDimensions = this.getRectDimensions(props);
    const style = Helpers.evaluateStyle(brushStyle, datum, activeBrushes.brush);
    const brushProps = assign({ style }, rectDimensions);
    return React.cloneElement(brushComponent, brushProps);
  }

  renderBrushArea(props) {
    const { brushAreaComponent, brushAreaStyle, activeBrushes = {}, datum = {} } = props;
    const rectDimensions = this.getRectDimensions(props, getFullDomain(props));
    const style = Helpers.evaluateStyle(brushAreaStyle, datum, activeBrushes.brushArea);
    const brushAreaProps = assign({ style }, rectDimensions);
    return React.cloneElement(brushAreaComponent, brushAreaProps);
  }

  renderLine(props) {
    const filteredProps = pick(props, [
      "x1", "x2", "y1", "y2", "datum", "scale", "active", "style"
    ]);
    return React.cloneElement(props.lineComponent, filteredProps);
  }

  render() {
    return (
      <g {...this.props.events}>
        {this.renderLine(this.props)}
        {this.renderBrushArea(this.props)}
        {this.renderBrush(this.props)}
        {this.renderHandles(this.props)}
      </g>
    );
  }
}
<commit_msg>fix brushArea active<commit_after>import React from "react";
import PropTypes from "prop-types";
import { Selection, Helpers, Collection, Axis, Scale, Domain, Box } from "victory-core";
import { assign, defaults, isEqual, isFunction, pick } from "lodash";

const getScale = (props) => {
  const { scale = {}, dimension = "x" } = props;
  if (scale[dimension]) {
    return scale[dimension];
  }
  const fallbackScale = Scale.getBaseScale(props, dimension);
  const range = Helpers.getRange(props, dimension);
  const domain = Domain.getDomainFromProps(props, dimension) || [0, 1];
  fallbackScale.range(range).domain(domain);
  return fallbackScale;
};

const toRange = (props, domain) => {
  const scale = getScale(props);
  return [scale(Math.min(...domain)), scale(Math.max(...domain))];
};

const toDomain = (props, range) => {
  const scale = getScale(props);
  return [scale.invert(Math.min(...range)), scale.invert(Math.max(...range))];
};

const getFullRange = (props) => {
  const scale = getScale(props);
  return scale.range();
};

const getFullDomain = (props) => {
  const scale = getScale(props);
  return scale.domain();
};

const withinBound = (value, bound) => {
  return value >= Math.min(...bound) && value <= Math.max(...bound);
};

const getActiveHandle = (props, position, range) => {
  const width = props.handleWidth / 2;
  const { dimension } = props;
  const getHandle = (type) => {
    const base = {
      min: dimension === "x" ? Math.min(...range) : Math.max(...range),
      max: dimension === "x" ? Math.max(...range) : Math.min(...range)
    };
    return [base[type] - width, base[type] + width];
  };
  const active = ["min", "max"].reduce((memo, type) => {
    memo[type] = withinBound(position, getHandle(type)) ? type : undefined;
    return memo;
  }, {});
  return active.min && active.max ? "both" : active.min || active.max;
};

const getMinimumDomain = () => {
  return [0, 1 / Number.MAX_SAFE_INTEGER];
};

const getCurrentDomain = (props) => {
  const { currentDomain, cachedBrushDomain, brushDomain } = props;
  if (brushDomain && cachedBrushDomain && !isEqual(brushDomain, cachedBrushDomain)) {
    return brushDomain;
  }
  return currentDomain || brushDomain || getFullDomain(props);
};

const panBox = (props, position) => {
  const { brushDomain, startPosition, currentDomain } = props;
  const range = currentDomain ? toRange(props, currentDomain) : toRange(props, brushDomain);
  const fullRange = getFullRange(props);
  const size = Math.abs(range[1] - range[0]);
  const globalMin = Math.min(...fullRange);
  const globalMax = Math.max(...fullRange);
  const delta = startPosition ? startPosition - position : 0;
  const min = Math.min(...range) - delta;
  const max = Math.max(...range) - delta;
  const constrainedMin = min > globalMax - size ? globalMax - size : Math.max(min, globalMin);
  const constrainedMax = max < globalMin + size ? globalMin + size : Math.min(max, globalMax);
  return [constrainedMin, constrainedMax];
};

export default class VictoryBrushLine extends React.Component {
  static propTypes = {
    allowDrag: PropTypes.bool,
    allowResize: PropTypes.bool,
    brushAreaComponent: PropTypes.element,
    brushAreaStyle: PropTypes.object,
    brushComponent: PropTypes.element,
    brushDimension: PropTypes.oneOf(["x", "y"]),
    brushDomain: PropTypes.array,
    brushStyle: PropTypes.object,
    brushWidth: PropTypes.number,
    className: PropTypes.string,
    dimension: PropTypes.oneOf(["x", "y"]),
    disable: PropTypes.bool,
    events: PropTypes.object,
    groupComponent: PropTypes.element,
    handleComponent: PropTypes.element,
    handleStyle: PropTypes.object,
    handleWidth: PropTypes.number,
    lineComponent: PropTypes.element,
    name: PropTypes.string,
    onBrushDomainChange: PropTypes.func,
    scale: PropTypes.object,
    style: PropTypes.object,
    type: PropTypes.string
  };

  static defaultProps = {
    allowDrag: true,
    allowResize: true,
    brushAreaComponent: <Box/>,
    brushAreaStyle: {
      cursor: "crosshair",
      stroke: "none",
      fill: "black",
      opacity: (d, a) => a ? 0.2 : 0.1 // eslint-disable-line no-magic-numbers
    },
    brushComponent: <Box/>,
    brushStyle: {
      strokeWidth: 2,
      stroke: (d, a) => a ? "black" : "none",
      fill: "black",
      cursor: "move",
      opacity: (d, a) => a ? 0.4 : 0.3 // eslint-disable-line no-magic-numbers
    },
    brushWidth: 10,
    groupComponent: <g/>,
    handleComponent: <Box/>,
    handleStyle: {
      stroke: "none",
      fill: "none"
    },
    handleWidth: 10,
    lineComponent: <Axis/>
  };

  static defaultEvents = function (props) {
    return props.disable ? undefined : [{
      target: props.type,
      eventHandlers: {
        onMouseEnter: (evt, targetProps) => {
          evt.preventDefault();
          const { dimension, allowResize } = targetProps;
          const position = Selection.getSVGEventCoordinates(evt)[dimension];
          const fullDomain = getFullDomain(targetProps);
          const minimumDomain = getMinimumDomain();
          const currentDomain = getCurrentDomain(targetProps) || fullDomain;
          const range = toRange(targetProps, currentDomain);
          const activeHandle = allowResize && getActiveHandle(targetProps, position, range);
          const activeBrushes = {
            brushArea: isEqual(currentDomain, fullDomain) || isEqual(currentDomain, minimumDomain),
            brush: withinBound(position, range) && !isEqual(fullDomain, currentDomain),
            minHandle: activeHandle === "min" || activeHandle === "both",
            maxHandle: activeHandle === "min" || activeHandle === "both"
          };
          return [{
            mutation: () => ({ activeBrushes, currentDomain: targetProps.currentDomain })
          }];
        },
        onMouseDown: (evt, targetProps) => {
          evt.preventDefault();
          const { allowResize, allowDrag, dimension, brushDomain, activeBrushes } = targetProps;

          // Don't trigger events for static brushes
          if (!allowResize && !allowDrag) {
            return [];
          }

          const fullDomain = getFullDomain(targetProps);
          const currentDomain = getCurrentDomain(targetProps);
          const position = Selection.getSVGEventCoordinates(evt)[dimension];
          const range = toRange(targetProps, currentDomain);
          const activeHandle = allowResize && getActiveHandle(targetProps, position, range);
          // If the event occurs in any of the handle regions, start a resize
          if (activeHandle) {
            return [{
              mutation: () => {
                return ({
                  isSelecting: true, activeHandle, currentDomain, cachedBrushDomain: brushDomain,
                  startPosition: position, activeBrushes
                });
              }
            }];
          } else if (withinBound(position, range) && !isEqual(fullDomain, currentDomain)) {
            // if the event occurs within a selected region start a panning event, unless the whole
            // domain is selected
            return [{
              mutation: () => ({
                isPanning: allowDrag, startPosition: position,
                currentDomain, cachedBrushDomain: brushDomain, activeBrushes
              })
            }];
          } else {
            // if the event occurs outside the region, or if the whole domain is selected,
            // start a new selection
            return allowResize ? [{
              mutation: () => ({
                isSelecting: allowResize,
                currentDomain: getMinimumDomain(),
                cachedBrushDomain: brushDomain,
                startPosition: position,
                activeBrushes
              })
            }] : [];
          }
        },
        onMouseMove: (evt, targetProps) => { // eslint-disable-line max-statements, complexity
          const {
            isPanning, isSelecting, allowResize, allowDrag, dimension, onBrushDomainChange,
            cachedBrushDomain
          } = targetProps;
          if (isPanning || isSelecting) {
            evt.preventDefault();
            evt.stopPropagation();
          }
          const position = Selection.getSVGEventCoordinates(evt)[dimension];
          const fullDomain = getFullDomain(targetProps);
          const domain = getCurrentDomain(targetProps) || fullDomain;
          const initialRange = toRange(targetProps, domain);
          const activeHandle = getActiveHandle(targetProps, position, initialRange);
          const activeBrushes = {
            brushArea: isEqual(domain, fullDomain) || isEqual(domain, getMinimumDomain()),
            brush: withinBound(position, initialRange) && !isEqual(fullDomain, domain),
            minHandle: activeHandle === "min" || activeHandle === "both",
            maxHandle: activeHandle === "max" || activeHandle === "both"
          };
          if (!targetProps.isPanning && !targetProps.isSelecting) {
            return [{
              mutation: () => ({
                activeBrushes, currentDomain: targetProps.currentDomain, cachedBrushDomain
              })
            }];
          }
          if (allowDrag && isPanning) {
            const fullRange = getFullRange(targetProps);
            const range = panBox(targetProps, position);
            const currentDomain = toDomain(targetProps, range);
            const startPosition = Math.max(...range) >= Math.max(...fullRange) ||
              Math.min(...range) <= Math.min(...fullRange) ?
              targetProps.startPosition : position;
            const mutatedProps = {
              startPosition, isPanning: true, currentDomain, cachedBrushDomain,
              activeBrushes: { brush: true }
            };

            if (isFunction(onBrushDomainChange)) {
              onBrushDomainChange(currentDomain, defaults({}, mutatedProps, targetProps));
            }
            return [{
              mutation: () => mutatedProps
            }];
          } else if (allowResize && isSelecting) {
            const range = toRange(targetProps, targetProps.currentDomain);
            const oppositeHandle = targetProps.activeHandle === "min" ? "max" : "min";
            const handle = targetProps.activeHandle &&
              getActiveHandle(targetProps, position, range) === "both" ?
                oppositeHandle : targetProps.activeHandle;
            let currentDomain;
            if (!handle) {
              currentDomain = toDomain(targetProps, [targetProps.startPosition, position]);
            } else {
              const rangeMax = dimension === "x" ? Math.max(...range) : Math.min(...range);
              const rangeMin = dimension === "x" ? Math.min(...range) : Math.max(...range);
              const min = handle === "max" ? rangeMin : position;
              const max = handle === "min" ? rangeMax : position;
              currentDomain = toDomain(targetProps, [min, max]);
            }
            const mutatedProps = {
              currentDomain, startPosition: targetProps.startPosition,
              isSelecting, activeHandle: handle, cachedBrushDomain,
              activeBrushes: {
                brush: true, minHandle: activeHandle === "min", maxHandle: activeHandle === "max"
              }
            };
            if (isFunction(onBrushDomainChange)) {
              onBrushDomainChange(currentDomain, defaults({}, mutatedProps, targetProps));
            }
            return [{
              mutation: () => (mutatedProps)
            }];
          }
          return [];
        },
        onMouseUp(evt, targetProps) {
          const {
            onBrushDomainChange, currentDomain, allowResize, activeBrushes, cachedBrushDomain
          } = targetProps;
          // if the mouse hasn't moved since a mouseDown event, select the whole domain region
          const mutatedProps = {
            isPanning: false, isSelecting: false, activeHandle: null, startPosition: null,
            currentDomain, cachedBrushDomain, activeBrushes
          };
          if (allowResize && isFunction(onBrushDomainChange)) {
            onBrushDomainChange(currentDomain, defaults({}, mutatedProps, targetProps));
          }
          return [{
            mutation: () => mutatedProps
          }];
        },
        onMouseLeave(evt, targetProps) {
          const { currentDomain, cachedBrushDomain } = targetProps;
          return [{
            mutation: () => ({
              isPanning: false, isSelecting: false, activeHandle: null, startPosition: null,
              currentDomain, cachedBrushDomain, activeBrushes: {}
            })
          }];
        }
      }
    }];
  };

  getRectDimensions(props, domain) {
    const { dimension, brushWidth } = props;
    domain = domain || getCurrentDomain(props);
    const range = toRange(props, domain);
    const coordinates = dimension === "x" ?
      { y1: props.y1, y2: props.y2, x1: Math.min(...range), x2: Math.max(...range) } :
      { x1: props.x1, x2: props.x2, y1: Math.min(...range), y2: Math.max(...range) };
    const { x1, x2, y1, y2 } = coordinates;
    const offset = {
      x: dimension === "x" ? 0 : brushWidth / 2,
      y: dimension === "y" ? 0 : brushWidth / 2
    };

    const x = Math.min(x1, x2) - offset.x;
    const y = Math.min(y1, y2) - offset.y;
    const width = Math.max(x1, x2) + offset.x - x;
    const height = Math.max(y1, y2) + offset.y - y;
    return { x, y, width, height };
  }

  getHandleDimensions(props) {
    const { dimension, brushWidth, handleWidth, x1, x2, y1, y2 } = props;
    const domain = getCurrentDomain(props);
    const range = toRange(props, domain);
    const defaultX = Math.min(x1, x2) - (brushWidth / 2);
    const defaultY = Math.min(y1, y2) - (brushWidth / 2);
    const x = {
      min: dimension === "x" ? Math.min(...range) - handleWidth / 2 : defaultX,
      max: dimension === "x" ? Math.max(...range) - handleWidth / 2 : defaultX
    };
    const y = {
      min: dimension === "y" ? Math.max(...range) - handleWidth / 2 : defaultY,
      max: dimension === "y" ? Math.min(...range) - handleWidth / 2 : defaultY
    };
    const width = dimension === "x" ? handleWidth : brushWidth;
    const height = dimension === "x" ? brushWidth : handleWidth;

    return {
      min: { x: x.min, y: y.min, width, height },
      max: { x: x.max, y: y.max, width, height }
    };
  }

  renderHandles(props) {
    const {
      handleComponent, handleStyle, dimension, datum = {}, activeBrushes = {},
      brushDomain, currentDomain
    } = props;
    if (!brushDomain && !currentDomain) {
      return null;
    }
    const domain = getCurrentDomain(props);
    const handleDimensions = this.getHandleDimensions(props);
    const cursor = dimension === "x" ? "ew-resize" : "ns-resize";
    const style = assign({ cursor }, handleStyle);
    const minDatum = assign({ handleValue: Collection.getMinValue(domain) }, datum);
    const maxDatum = assign({ handleValue: Collection.getMaxValue(domain) }, datum);
    const minHandleProps = assign({
      key: "min",
      style: Helpers.evaluateStyle(style, minDatum, activeBrushes.minHandle)
    }, handleDimensions.min);
    const maxHandleProps = assign(
      {
        key: "max",
        style: Helpers.evaluateStyle(style, maxDatum, activeBrushes.maxHandle)
      }, handleDimensions.max);
    return [
      React.cloneElement(handleComponent, minHandleProps),
      React.cloneElement(handleComponent, maxHandleProps)
    ];
  }

  renderBrush(props) {
    const {
      brushComponent, brushStyle, activeBrushes = {}, datum = {}, brushDomain, currentDomain
    } = props;
    if (!brushDomain && !currentDomain) {
      return null;
    }
    const rectDimensions = this.getRectDimensions(props);
    const style = Helpers.evaluateStyle(brushStyle, datum, activeBrushes.brush);
    const brushProps = assign({ style }, rectDimensions);
    return React.cloneElement(brushComponent, brushProps);
  }

  renderBrushArea(props) {
    const { brushAreaComponent, brushAreaStyle, activeBrushes = {}, datum = {} } = props;
    const rectDimensions = this.getRectDimensions(props, getFullDomain(props));
    const style = Helpers.evaluateStyle(brushAreaStyle, datum, activeBrushes.brushArea);
    const brushAreaProps = assign({ style }, rectDimensions);
    return React.cloneElement(brushAreaComponent, brushAreaProps);
  }

  renderLine(props) {
    const filteredProps = pick(props, [
      "x1", "x2", "y1", "y2", "datum", "scale", "active", "style"
    ]);
    return React.cloneElement(props.lineComponent, filteredProps);
  }

  render() {
    return (
      <g {...this.props.events}>
        {this.renderLine(this.props)}
        {this.renderBrushArea(this.props)}
        {this.renderBrush(this.props)}
        {this.renderHandles(this.props)}
      </g>
    );
  }
}
<|endoftext|>


<commit_before>import Datastore from '@google-cloud/datastore';

const datastore = {
  db: Datastore({
    projectId: 'phoenix-network-181216',
  }),

  getItem: async function(kind, id) {
    const key = this.db.key([kind, id]);
    const result = await this.db.get(key);
    return result;
  },

  getAll: async function(kind, property, id) {
    const query = this.db.createQuery(kind).filter(property, '=', id);
    const results = this.db.runQuery(query);
    console.log(results);

    return results;
  },

  putItem: async function(kind, id, data) {
    const key = this.db.key([kind, id]);
    const entity = {
      key: key,
      data: data
    };
    const results = await this.db.insert(entity);
    return results;
  },

  updateItem: function(kind, id, data) {
    const key = this.db.key([kind, id]);
    const entity = {
      key: key,
      data: data
    };
    const results = this.db.upsert(entity);
    return results;
  },

  deleteItem: async function(kind, id) {
    const key = this.db.key([kind, id]);
    const dbResults = await this.db.delete(key);
    return dbResults;
  }
};

export default datastore;
<commit_msg>Updated code<commit_after>import Datastore from '@google-cloud/datastore';

const datastore = {
  db: Datastore({
    projectId: 'phoenix-network-181216',
  }),

  getItem: async function(kind, id) {
    const key = this.db.key([kind, id]);
    const result = await this.db.get(key);
    return result;
  },

  getAll: async function(kind, property, id) {
    const query = this.db.createQuery(kind).filter(property, '=', id);
    const results = this.db.runQuery(query);
    return results;
  },

  putItem: async function(kind, id, data) {
    const key = this.db.key([kind, id]);
    const entity = {
      key: key,
      data: data
    };
    const results = await this.db.insert(entity);
    return results;
  },

  updateItem: function(kind, id, data) {
    const key = this.db.key([kind, id]);
    const entity = {
      key: key,
      data: data
    };
    const results = this.db.upsert(entity);
    return results;
  },

  deleteItem: async function(kind, id) {
    const key = this.db.key([kind, id]);
    const dbResults = await this.db.delete(key);
    return dbResults;
  }
};

export default datastore;
<|endoftext|>


<commit_before>import defaults from 'lodash/defaults'
import isString from 'lodash/isString'
import { getLocationObject, newId, parseUrl } from './utils'

export const HISTORY_CREATE = 'history/CREATE'
export const HISTORY_RESTORE = 'history/RESTORE'
export const HISTORY_HASH_CHANGE = 'history/HISTORY_HASH_CHANGE'
export const HISTORY_LEARN = 'history/HISTORY_LEARN'

/**
 * This action should be dispatched when you want to exchange state with a previous history.
 */
export function restore(key, pushState = true) {
  return {
    type: HISTORY_RESTORE,
    payload: key,
    meta: {
      pushState,
    },
  }
}

const defaultLoc = {
  pathname: '',
  hash: '',
  search: '',
}

/**
 * This action should be dispatched when you want a new history entry.
 */
export function create(_location, title, id = null, pushState = true) {
  const loc = isString(_location) ? parseUrl(_location) : getLocationObject(_location)
  return {
    type: HISTORY_CREATE,
    payload: {
      title: title || '',
      location: defaults(loc, defaultLoc),
      id: id || newId(),
    },
    meta: {
      pushState,
    },
  }
}
export function createFromBrowser(historyState) {
  return {
    type: HISTORY_CREATE,
    payload: historyState,
  }
}

/**
 * Currently this is private. Called when user changes hash in address bar.
 */
export function hashChange(hash) {
  return {
    type: HISTORY_HASH_CHANGE,
    payload: {
      hash,
      id: newId(),
    },
  }
}
<commit_msg>move constants inline with action creator<commit_after>import defaults from 'lodash/defaults'
import isString from 'lodash/isString'
import { getLocationObject, newId, parseUrl } from './utils'

/**
 * This action should be dispatched when you want to exchange state with a previous history.
 */
export const HISTORY_RESTORE = 'history/RESTORE'
export function restore(key, pushState = true) {
  return {
    type: HISTORY_RESTORE,
    payload: key,
    meta: {
      pushState,
    },
  }
}

const defaultLoc = {
  pathname: '',
  hash: '',
  search: '',
}

/**
 * This action should be dispatched when you want a new history entry.
 */
export const HISTORY_CREATE = 'history/CREATE'
export function create(_location, title, id = null, pushState = true) {
  const loc = isString(_location) ? parseUrl(_location) : getLocationObject(_location)
  return {
    type: HISTORY_CREATE,
    payload: {
      title: title || '',
      location: defaults(loc, defaultLoc),
      id: id || newId(),
    },
    meta: {
      pushState,
    },
  }
}

export const HISTORY_LEARN = 'history/HISTORY_LEARN'
export function createFromBrowser(historyState) {
  return {
    type: HISTORY_LEARN,
    payload: historyState,
  }
}

/**
 * Currently this is private. Called when user changes hash in address bar.
 */
export const HISTORY_HASH_CHANGE = 'history/HISTORY_HASH_CHANGE'
export function hashChange(hash) {
  return {
    type: HISTORY_HASH_CHANGE,
    payload: {
      hash,
      id: newId(),
    },
  }
}
<|endoftext|>


<commit_before>var args = arguments[0] || {};

/**
 * function to start the loading animation
 */
$.start = function() {
	//$.overlay.opacity = 0;
	$.rocketSmoke.opacity = 0.1;
	$.rocketFlight.opacity = 0;
	$.rocketFlight.top = null;

	$.rocketFlight.stop();
	$.rocketSmoke.start();

	$.overlay.animate({
		opacity: 0.7,
		duration: 250
	});

	$.rocketSmoke.animate({
		opacity: 1,
		duration: 500
	});
};

/*
 * exposed function to finish the loading animation. Animates the rocket off the screen.
 */
$.finish = function(_callback) {
	$.rocketFlight.opacity = 0.1;

	$.rocketFlight.start();

	$.rocketFlight.animate({
		opacity: 1,
		duration: 500
	});

	$.rocketSmoke.animate({
		opacity: 0,
		duration: 500,
		delay: 500
	}, function() {
		$.rocketSmoke.stop();

		$.rocketFlight.animate({
			top: -130,
			duration: 750,
			curve: Ti.UI.ANIMATION_CURVE_EASE_IN
		});

		$.overlay.animate({
			opacity: 0,
			duration: 750
		}, function() {
			$.rocketFlight.stop();

			_callback && _callback();
		});
	});
};
<commit_msg>Documentacion de codigo<commit_after>/**
 * Obtenemos los argumentos que hayan podido ser pasador al controlador
 */
var args = arguments[0] || {};

/**
 * Se encarga de inicializar la animacion
 */
$.start = function() {

	$.rocketSmoke.opacity = 0.1;
	$.rocketFlight.opacity = 0;
	$.rocketFlight.top = null;

	$.rocketFlight.stop();
	$.rocketSmoke.start();

	$.overlay.animate({
		opacity: 0.7,
		duration: 250
	});

	$.rocketSmoke.animate({
		opacity: 1,
		duration: 500
	});
};

/*
 * Funcion que se encarga de finalizar la animacion, sacando el cohete fuera de pantalla
 */
$.finish = function(_callback) {
	$.rocketFlight.opacity = 0.1;

	$.rocketFlight.start();

	$.rocketFlight.animate({
		opacity: 1,
		duration: 500
	});

	$.rocketSmoke.animate({
		opacity: 0,
		duration: 500,
		delay: 500
	}, function() {
		$.rocketSmoke.stop();

		$.rocketFlight.animate({
			top: -130,
			duration: 750,
			curve: Ti.UI.ANIMATION_CURVE_EASE_IN
		});

		$.overlay.animate({
			opacity: 0,
			duration: 750
		}, function() {
			$.rocketFlight.stop();

			_callback && _callback(); //Ejecutamos los callbacks que se hayan podido pasar al finalizar la animacion, normalmente el callback sera el inicio de la aplicacion indiando que se invoque al controlador de forms
		});
	});
};
<|endoftext|>


<commit_before>export default function getChildProps() {
  const listeners = { ...this.$listeners };

  Object
    .entries(this.$options.events || {})
    .forEach(([name, { custom }]) => {
      if (custom) {
        delete listeners[name];
      }
    });

  return listeners;
}
<commit_msg>Wrong function name on getChildListeners (#221)<commit_after>export default function getChildListeners() {
  const listeners = { ...this.$listeners };

  Object
    .entries(this.$options.events || {})
    .forEach(([name, { custom }]) => {
      if (custom) {
        delete listeners[name];
      }
    });

  return listeners;
}
<|endoftext|>


<commit_before>var Bytecode = (function () {
  function Bytecode(code) {
    var op = code.readU8();
    this.op = op;

    var i, n;

    switch (op) {
    case OP_lookupswitch:
      /* offsets[0] is the default offset. */
      this.offsets = [code.readS24()];
      var n = code.readU30() + 1;
      for (i = 0; i < n; i++) {
        this.offsets.push(code.readS24());
      }
      break;
    default:
      var opdesc = opcodeTable[op];
      if (!opdesc) {
        unexpected("Unknown Op " + op);
      }

      for (i = 0, n = opdesc.operands.length; i < n; i++) {
        var operand = opdesc.operands[i];

        switch (operand.size) {
        case "u08":
          this[operand.name] = code.readU8();
          break;
        case "s16":
          this[operand.name] = code.readU30Unsafe();
          break;
        case "s24":
          this[operand.name] = code.readS24();
          break;
        case "u30":
          this[operand.name] = code.readU30();
          break;
        case "u32":
          this[operand.name] = code.readU32();
          break;
        default:
          unexpected();
        }
      }
    }
  }

  var Bp = Bytecode.prototype;

  Bp.makeBlockHead = function makeBlockHead() {
    if (this.succs) {
      return;
    }

    this.succs = [];
    this.preds = [];
  };

  Bp.makeLoopHead = function makeLoopHead(loop) {
    if (this.loop) {
      return;
    }

    assert(this.succs);

    this.loop = loop;
    for (var i = 0, j = loop.length; i < j; i++) {
      loop[i].inLoop = this;
    }
  };

  Bp.doubleLink = function doubleLink(target) {
    assert(this.succs);
    this.succs.push(target);
    target.preds.push(this);
  };

  /*
   * Find the dominator set from immediate dominators.
   */
  function dom() {
    if (this.hasOwnProperty("dom")) {
      return this.dom;
    }

    assert(this.succs);
    assert(this.dominator);

    var b = this;
    var d = [b];
    do {
      d.push(b.dominator);
      b = b.dominator;
    } while (b !== b.dominator);

    this.dom = d;
    return d;
  }
  Object.defineProperty(Bp, "dom", { get: dom,
                                     configurable: true,
                                     enumerable: true });

  Bp.toString = function toString() {
    var opdesc = opcodeTable[this.op];
    var str = opdesc.name.padRight(' ', 20);
    var i, j;

    if (this.op === OP_lookupswitch) {
      str += "defaultTarget:" + this.targets[0].position;
      for (i = 1, j = this.offsets.length; i < j; i++) {
        str += ", target:" + this.targets[i].position;
      }
    } else {
      for (i = 0, j = opdesc.operands.length; i < j; i++) {
        var operand = opdesc.operands[i];

        if (operand.name === "offset") {
          str += "target:" + this.target.position;
        } else {
          str += operand.name + ":" + this[operand.name];
        }

        if (i < j - 1) {
          str += ", ";
        }
      }
    }

    return str;
  };

  return Bytecode;
})();

var Analysis = (function () {

  function intersect(doms, b1, b2) {
    var finger1 = b1;
    var finger2 = b2;
    while (finger1 !== finger2) {
      while (finger1 < finger2) {
        finger1 = doms[finger1];
      }
      while (finger2 < finger1) {
        finger2 = doms[finger2];
      }
    }
    return finger1;
  }

  function dfs(root, pre, post, succ) {
    var visited = {};

    function visit(node) {
      visited[node.position] = true;

      if (pre) {
        pre(node);
      }

      var succs = node.succs;
      for (var i = 0, j = succs.length; i < j; i++) {
        var s = succs[i];
        if (!visited[s.position]) {
          visit(s);
        } else if (succ) {
          succ(s);
        }
      }

      if (post) {
        post(node);
      }
    }

    visit(root);
  }

  function detectBasicBlocks(bytecodes) {
    var code;
    var pc, end;

    assert(bytecodes);

    bytecodes[0].makeBlockHead();
    for (pc = 0, end = bytecodes.length; pc < end; pc++) {
      code = bytecodes[pc];
      switch (code.op) {
      case OP_lookupswitch:
        code.targets.forEach(function (target) {
          target.makeBlockHead();
        });
        break;

      case OP_jump:
      case OP_iflt:
      case OP_ifnlt:
      case OP_ifle:
      case OP_ifnlt:
      case OP_ifnle:
      case OP_ifgt:
      case OP_ifge:
      case OP_ifngt:
      case OP_ifeq:
      case OP_ifne:
      case OP_ifstricteq:
      case OP_ifstrictne:
      case OP_iftrue:
      case OP_iffalse:
        code.target.makeBlockHead();
        bytecodes[pc + 1].makeBlockHead();
        break;

      default:;
      }
    }

    var currentBlock = bytecodes[0];
    for (pc = 1, end = bytecodes.length; pc < end; pc++) {
      if (!bytecodes[pc].succs) {
        continue;
      }

      assert(currentBlock.succs);

      code = bytecodes[pc - 1];
      currentBlock.end = code;

      var nextBlock = bytecodes[pc];

      switch (code.op) {
      case OP_lookupswitch:
        code.targets.forEach(currentBlock.doubleLink.bind(currentBlock));
        break;

      case OP_jump:
        currentBlock.doubleLink(code.target);
        break;

      case OP_iflt:
      case OP_ifnlt:
      case OP_ifle:
      case OP_ifnlt:
      case OP_ifnle:
      case OP_ifgt:
      case OP_ifge:
      case OP_ifngt:
      case OP_ifeq:
      case OP_ifne:
      case OP_ifstricteq:
      case OP_ifstrictne:
      case OP_iftrue:
      case OP_iffalse:
        currentBlock.doubleLink(code.target);
        currentBlock.doubleLink(nextBlock);
        break;

      default:
        currentBlock.doubleLink(nextBlock);
      }

      currentBlock = nextBlock;
    }
  }

  /*
   * Calculate the dominance relation.
   *
   * Algorithm is from [1].
   *
   * [1] Cooper et al. "A Simple, Fast Dominance Algorithm"
   */
  function computeDominance(root) {
    /*
     * For this algorithm we id blocks by their index in postorder. We will
     * change the id to the more familiar reverse postorder after we run the
     * algorithm.
     */
    var blocks = [];
    dfs(root, null, blocks.push.bind(blocks), null);
    var n = blocks.length;
    for (var i = 0; i < n; i++) {
      blocks[i].blockId = i;
    }

    var doms = new Array(n);
    doms[n - 1] =  n - 1;
    var changed = true;

    while (changed) {
      changed = false;

      /* Iterate all blocks but the starting block in reverse postorder. */
      for (var b = n - 2; b >= 0; b--) {
        var preds = blocks[b].preds;
        var newIdom = preds[0].blockId;

        for (var i = 1, j = preds.length; i < j; i++) {
          var p = preds[i].blockId;

          if (doms[p]) {
            newIdom = intersect(doms, p, newIdom);
          }
        }

        if (doms[b] !== newIdom) {
          doms[b] = newIdom;
          changed = true;
        }
      }
    }

    for (var i = 0; i < n; i++) {
      var block = blocks[i];
      block.blockId = n - 1 - block.blockId;
      block.dominator = blocks[doms[i]];
    }
  }

  /*
   * Find the node that dominates all other nodes in the loop.
   */
  function findLoopHead(loop) {
    var loopHead;

    for (var i = 0, j = loop.length; i < j; i++) {
      /* Candidate loop head. */
      loopHead = loop[i];

      for (var k = 0; k < j; k++) {
        if (loop[k].dom.indexOf(loopHead) < 0) {
          break;
        }
      }

      if (k === j && loop.indexOf(loopHead) >= 0) {
        return loopHead;
      }
    }

    unexpected();
  }

  /*
   * Find strongly connected components and mark them as loops.
   */
  function findLoops(root) {
    var preorderId = 0;

    /*
     * Don't fatten bytecodes with a preorder field, we don't need it outside
     * of loop detection.
     */
    var preorder = {};
    var done = [];
    var unconnectedNodes = [];
    var pendingNodes = [];

    /* Find SCCs by Gabow's algorithm in linear time. */
    dfs(root,
        function pre(v) {
          preorder[v.blockId] = preorderId++;
          unconnectedNodes.push(v);
          pendingNodes.push(v);
        },
        function post(v) {
          if (pendingNodes.peek() !== v) {
            return;
          }

          pendingNodes.pop();

          var l = [];
          do {
            var w = unconnectedNodes.pop();
            l.push(w);
            done.push(w);
          } while (w !== v);

          if (l.length > 1) {
            findLoopHead(l).makeLoopHead(l);
          }
        },
        function succ(w) {
          if (done.indexOf(w) >= 0) {
            return;
          }

          while (preorder[pendingNodes.peek().blockId] > preorder[w.blockId]) {
            pendingNodes.pop();
          }
        });
  }

  function Analysis(codeStream) {
    /*
     * Normalize the code stream. The other analyses are run by the user
     * on demand.
     */
    this.normalizeBytecode(new AbcStream(codeStream));
  }

  var Ap = Analysis.prototype;

  Ap.normalizeBytecode = function normalizeBytecode(codeStream) {
    /* This array is sparse, indexed by offset. */
    var bytecodesOffset = [];
    /* This array is dense. */
    var bytecodes = [];
    var code;

    while (codeStream.remaining() > 0) {
      var pos = codeStream.position;
      code = new Bytecode(codeStream);

      /* Get absolute offsets for normalization to new indices below. */
      switch (code.op) {
      case OP_nop:
      case OP_label:
        bytecodesOffset[pos] = bytecodes.length;
        continue;

      case OP_lookupswitch:
        code.targets = [];
        var offsets = code.offsets;
        for (var i = 0, j = offsets.length; i < j; i++) {
          offsets[i] += codeStream.position;
        }
        break;

      case OP_jump:
      case OP_iflt:
      case OP_ifnlt:
      case OP_ifle:
      case OP_ifnlt:
      case OP_ifnle:
      case OP_ifgt:
      case OP_ifge:
      case OP_ifngt:
      case OP_ifeq:
      case OP_ifne:
      case OP_ifstricteq:
      case OP_ifstrictne:
      case OP_iftrue:
      case OP_iffalse:
        code.offset += codeStream.position;
        break;

      default:;
      }

      /* Cache the position in the bytecode array. */
      code.position = bytecodes.length;
      bytecodesOffset[pos] = bytecodes.length;
      bytecodes.push(code);
    }

    for (var pc = 0, end = bytecodes.length; pc < end; pc++) {
      code = bytecodes[pc];
      switch (code.op) {
      case OP_lookupswitch:
        var offsets = code.offsets;
        for (var i = 0, j = offsets.length; i < j; i++) {
          code.targets.push(bytecodes[bytecodesOffset[offsets[i]]]);
        }
        code.offsets = undefined;
        break;

      case OP_jump:
      case OP_iflt:
      case OP_ifnlt:
      case OP_ifle:
      case OP_ifnlt:
      case OP_ifnle:
      case OP_ifgt:
      case OP_ifge:
      case OP_ifngt:
      case OP_ifeq:
      case OP_ifne:
      case OP_ifstricteq:
      case OP_ifstrictne:
      case OP_iftrue:
      case OP_iffalse:
        code.target = bytecodes[bytecodesOffset[code.offset]];
        code.offset = undefined;
        break;

      default:;
      }
    }

    this.bytecodes = bytecodes;
  };

  Ap.analyzeControlFlow = function analyzeControlFlow() {
    assert(this.bytecodes);

    var bytecodes = this.bytecodes;
    detectBasicBlocks(bytecodes);
    computeDominance(bytecodes[0]);
    findLoops(bytecodes[0]);
  };

  /*
   * Prints a normalized bytecode along with metainfo.
   *
   * Basic blocks are identified by the position of the first bytecode in the
   * block. The format for each blocks, b, is:
   *   idom(b) >> b -> succ(b) 1, ...
   *
   * Loops are identified by:
   *   loop [loop body block 0, loop body block 1, ...]
   */

  Ap.trace = function(writer) {
    function blockId(node) {
      return node.blockId;
    }

    writer.enter("analysis {");

    var ranControlFlow = !!this.bytecodes[0].succs;

    for (var pc = 0, end = this.bytecodes.length; pc < end; pc++) {
      var code = this.bytecodes[pc];

      if (ranControlFlow && code.succs) {
        if (pc > 0) {
          writer.leave("}");
        }

        if (!code.dominator) {
          writer.enter("block unreachable {");
        } else {
          writer.enter("block " + code.dominator.blockId + " >> " + code.blockId +
                       (code.succs.length > 0 ? " -> " +
                        code.succs.map(blockId).join(",") : "") + " {");
        }

        if (code.loop) {
          writer.writeLn("loop [" + code.loop.map(blockId).join(",") + "]");
          writer.writeLn("");
        }
      }

      writer.writeLn(("" + pc).padRight(' ', 5) + code);

      if (ranControlFlow && pc === end - 1) {
        writer.leave("}");
      }
    }

    writer.leave("}");
  };

  Ap.traceGraphViz = function traceGraphViz(writer, name, prefix) {
    prefix = prefix || "";
    if (!this.bytecodes) {
      return;
    }
    writeGraphViz(writer, name.toString(), this.bytecodes[0],
      function (n) {
        return prefix + n.blockId;
      },
      function (n) {
        return n.succs ? n.succs : [];
      }, function (n) {
        return n.preds ? n.preds : [];
      }, function (n) {
        return "Block: " + n.blockId;
      }
    );
  };

  return Analysis;

})();<commit_msg>Fix an infinite loop with self-edges in finding dominators<commit_after>var Bytecode = (function () {
  function Bytecode(code) {
    var op = code.readU8();
    this.op = op;

    var i, n;

    switch (op) {
    case OP_lookupswitch:
      /* offsets[0] is the default offset. */
      this.offsets = [code.readS24()];
      var n = code.readU30() + 1;
      for (i = 0; i < n; i++) {
        this.offsets.push(code.readS24());
      }
      break;
    default:
      var opdesc = opcodeTable[op];
      if (!opdesc) {
        unexpected("Unknown Op " + op);
      }

      for (i = 0, n = opdesc.operands.length; i < n; i++) {
        var operand = opdesc.operands[i];

        switch (operand.size) {
        case "u08":
          this[operand.name] = code.readU8();
          break;
        case "s16":
          this[operand.name] = code.readU30Unsafe();
          break;
        case "s24":
          this[operand.name] = code.readS24();
          break;
        case "u30":
          this[operand.name] = code.readU30();
          break;
        case "u32":
          this[operand.name] = code.readU32();
          break;
        default:
          unexpected();
        }
      }
    }
  }

  var Bp = Bytecode.prototype;

  Bp.makeBlockHead = function makeBlockHead() {
    if (this.succs) {
      return;
    }

    this.succs = [];
    this.preds = [];
  };

  Bp.makeLoopHead = function makeLoopHead(loop) {
    if (this.loop) {
      return;
    }

    assert(this.succs);

    this.loop = loop;
    for (var i = 0, j = loop.length; i < j; i++) {
      loop[i].inLoop = this;
    }
  };

  Bp.doubleLink = function doubleLink(target) {
    assert(this.succs);
    this.succs.push(target);
    target.preds.push(this);
  };

  /*
   * Find the dominator set from immediate dominators.
   */
  function dom() {
    if (this.hasOwnProperty("dom")) {
      return this.dom;
    }

    assert(this.succs);
    assert(this.dominator);

    var b = this;
    var d = [b];
    do {
      d.push(b.dominator);
      b = b.dominator;
    } while (b !== b.dominator);

    this.dom = d;
    return d;
  }
  Object.defineProperty(Bp, "dom", { get: dom,
                                     configurable: true,
                                     enumerable: true });

  Bp.toString = function toString() {
    var opdesc = opcodeTable[this.op];
    var str = opdesc.name.padRight(' ', 20);
    var i, j;

    if (this.op === OP_lookupswitch) {
      str += "defaultTarget:" + this.targets[0].position;
      for (i = 1, j = this.offsets.length; i < j; i++) {
        str += ", target:" + this.targets[i].position;
      }
    } else {
      for (i = 0, j = opdesc.operands.length; i < j; i++) {
        var operand = opdesc.operands[i];

        if (operand.name === "offset") {
          str += "target:" + this.target.position;
        } else {
          str += operand.name + ":" + this[operand.name];
        }

        if (i < j - 1) {
          str += ", ";
        }
      }
    }

    return str;
  };

  return Bytecode;
})();

var Analysis = (function () {

  function dfs(root, pre, post, succ) {
    var visited = {};

    function visit(node) {
      visited[node.position] = true;

      if (pre) {
        pre(node);
      }

      var succs = node.succs;
      for (var i = 0, j = succs.length; i < j; i++) {
        var s = succs[i];
        if (!visited[s.position]) {
          visit(s);
        } else if (succ) {
          succ(s);
        }
      }

      if (post) {
        post(node);
      }
    }

    visit(root);
  }

  function detectBasicBlocks(bytecodes) {
    var code;
    var pc, end;

    assert(bytecodes);

    bytecodes[0].makeBlockHead();
    for (pc = 0, end = bytecodes.length; pc < end; pc++) {
      code = bytecodes[pc];
      switch (code.op) {
      case OP_lookupswitch:
        code.targets.forEach(function (target) {
          target.makeBlockHead();
        });
        break;

      case OP_jump:
      case OP_iflt:
      case OP_ifnlt:
      case OP_ifle:
      case OP_ifnlt:
      case OP_ifnle:
      case OP_ifgt:
      case OP_ifge:
      case OP_ifngt:
      case OP_ifeq:
      case OP_ifne:
      case OP_ifstricteq:
      case OP_ifstrictne:
      case OP_iftrue:
      case OP_iffalse:
        code.target.makeBlockHead();
        bytecodes[pc + 1].makeBlockHead();
        break;

      default:;
      }
    }

    var currentBlock = bytecodes[0];
    for (pc = 1, end = bytecodes.length; pc < end; pc++) {
      if (!bytecodes[pc].succs) {
        continue;
      }

      assert(currentBlock.succs);

      code = bytecodes[pc - 1];
      currentBlock.end = code;

      var nextBlock = bytecodes[pc];

      switch (code.op) {
      case OP_lookupswitch:
        code.targets.forEach(currentBlock.doubleLink.bind(currentBlock));
        break;

      case OP_jump:
        currentBlock.doubleLink(code.target);
        break;

      case OP_iflt:
      case OP_ifnlt:
      case OP_ifle:
      case OP_ifnlt:
      case OP_ifnle:
      case OP_ifgt:
      case OP_ifge:
      case OP_ifngt:
      case OP_ifeq:
      case OP_ifne:
      case OP_ifstricteq:
      case OP_ifstrictne:
      case OP_iftrue:
      case OP_iffalse:
        currentBlock.doubleLink(code.target);
        currentBlock.doubleLink(nextBlock);
        break;

      default:
        currentBlock.doubleLink(nextBlock);
      }

      currentBlock = nextBlock;
    }
  }

  /*
   * Calculate the dominance relation.
   *
   * Algorithm is from [1].
   *
   * [1] Cooper et al. "A Simple, Fast Dominance Algorithm"
   */
  function computeDominance(root) {
    var doms;

    function intersect(doms, b1, b2) {
      var finger1 = b1;
      var finger2 = b2;
      while (finger1 !== finger2) {
        while (finger1 < finger2) {
          finger1 = doms[finger1];
        }
        while (finger2 < finger1) {
          finger2 = doms[finger2];
        }
      }
      return finger1;
    }

    /* The root must not have incoming edges! */
    assert(root.preds.length === 0);

    /*
     * For this algorithm we id blocks by their index in postorder. We will
     * change the id to the more familiar reverse postorder after we run the
     * algorithm.
     */
    var blocks = [];
    dfs(root, null, blocks.push.bind(blocks), null);
    var n = blocks.length;
    for (var i = 0; i < n; i++) {
      blocks[i].blockId = i;
    }

    var doms = new Array(n);
    doms[n - 1] =  n - 1;
    var changed = true;

    while (changed) {
      changed = false;

      /* Iterate all blocks but the starting block in reverse postorder. */
      for (var b = n - 2; b >= 0; b--) {
        var preds = blocks[b].preds;
        var j = preds.length;

        var newIdom = preds[0].blockId;
        if (!doms[newIdom]) {
          for (var i = 1; i < j; i++) {
            newIdom = preds[i].blockId;
            if (doms[newIdom]) {
              break;
            }
          }
        }
        assert(doms[newIdom]);

        for (var i = 1, j = preds.length; i < j; i++) {
          var p = preds[i].blockId;

          if (doms[p]) {
            newIdom = intersect(doms, p, newIdom);
          }
        }

        if (doms[b] !== newIdom) {
          doms[b] = newIdom;
          changed = true;
        }
      }
    }

    for (var i = 0; i < n; i++) {
      var block = blocks[i];
      block.blockId = n - 1 - block.blockId;
      block.dominator = blocks[doms[i]];
    }
  }

  /*
   * Find the node that dominates all other nodes in the loop.
   */
  function findLoopHead(loop) {
    var loopHead;

    for (var i = 0, j = loop.length; i < j; i++) {
      /* Candidate loop head. */
      loopHead = loop[i];

      for (var k = 0; k < j; k++) {
        if (loop[k].dom.indexOf(loopHead) < 0) {
          break;
        }
      }

      if (k === j && loop.indexOf(loopHead) >= 0) {
        return loopHead;
      }
    }

    unexpected();
  }

  /*
   * Find strongly connected components and mark them as loops.
   */
  function findLoops(root) {
    var preorderId = 0;

    /*
     * Don't fatten bytecodes with a preorder field, we don't need it outside
     * of loop detection.
     */
    var preorder = {};
    var done = [];
    var unconnectedNodes = [];
    var pendingNodes = [];

    /* Find SCCs by Gabow's algorithm in linear time. */
    dfs(root,
        function pre(v) {
          preorder[v.blockId] = preorderId++;
          unconnectedNodes.push(v);
          pendingNodes.push(v);
        },
        function post(v) {
          if (pendingNodes.peek() !== v) {
            return;
          }

          pendingNodes.pop();

          var l = [];
          do {
            var w = unconnectedNodes.pop();
            l.push(w);
            done.push(w);
          } while (w !== v);

          if (l.length > 1) {
            findLoopHead(l).makeLoopHead(l);
          }
        },
        function succ(w) {
          if (done.indexOf(w) >= 0) {
            return;
          }

          while (preorder[pendingNodes.peek().blockId] > preorder[w.blockId]) {
            pendingNodes.pop();
          }
        });
  }

  function Analysis(codeStream) {
    /*
     * Normalize the code stream. The other analyses are run by the user
     * on demand.
     */
    this.normalizeBytecode(new AbcStream(codeStream));
  }

  var Ap = Analysis.prototype;

  Ap.normalizeBytecode = function normalizeBytecode(codeStream) {
    /* This array is sparse, indexed by offset. */
    var bytecodesOffset = [];
    /* This array is dense. */
    var bytecodes = [];
    var code;

    while (codeStream.remaining() > 0) {
      var pos = codeStream.position;
      code = new Bytecode(codeStream);

      /* Get absolute offsets for normalization to new indices below. */
      switch (code.op) {
      case OP_nop:
      case OP_label:
        bytecodesOffset[pos] = bytecodes.length;
        continue;

      case OP_lookupswitch:
        code.targets = [];
        var offsets = code.offsets;
        for (var i = 0, j = offsets.length; i < j; i++) {
          offsets[i] += codeStream.position;
        }
        break;

      case OP_jump:
      case OP_iflt:
      case OP_ifnlt:
      case OP_ifle:
      case OP_ifnlt:
      case OP_ifnle:
      case OP_ifgt:
      case OP_ifge:
      case OP_ifngt:
      case OP_ifeq:
      case OP_ifne:
      case OP_ifstricteq:
      case OP_ifstrictne:
      case OP_iftrue:
      case OP_iffalse:
        code.offset += codeStream.position;
        break;

      default:;
      }

      /* Cache the position in the bytecode array. */
      code.position = bytecodes.length;
      bytecodesOffset[pos] = bytecodes.length;
      bytecodes.push(code);
    }

    for (var pc = 0, end = bytecodes.length; pc < end; pc++) {
      code = bytecodes[pc];
      switch (code.op) {
      case OP_lookupswitch:
        var offsets = code.offsets;
        for (var i = 0, j = offsets.length; i < j; i++) {
          code.targets.push(bytecodes[bytecodesOffset[offsets[i]]]);
        }
        code.offsets = undefined;
        break;

      case OP_jump:
      case OP_iflt:
      case OP_ifnlt:
      case OP_ifle:
      case OP_ifnlt:
      case OP_ifnle:
      case OP_ifgt:
      case OP_ifge:
      case OP_ifngt:
      case OP_ifeq:
      case OP_ifne:
      case OP_ifstricteq:
      case OP_ifstrictne:
      case OP_iftrue:
      case OP_iffalse:
        code.target = bytecodes[bytecodesOffset[code.offset]];
        code.offset = undefined;
        break;

      default:;
      }
    }

    this.bytecodes = bytecodes;
  };

  Ap.analyzeControlFlow = function analyzeControlFlow() {
    assert(this.bytecodes);

    var bytecodes = this.bytecodes;
    detectBasicBlocks(bytecodes);
    computeDominance(bytecodes[0]);
    findLoops(bytecodes[0]);
  };

  /*
   * Prints a normalized bytecode along with metainfo.
   *
   * Basic blocks are identified by the position of the first bytecode in the
   * block. The format for each blocks, b, is:
   *   idom(b) >> b -> succ(b) 1, ...
   *
   * Loops are identified by:
   *   loop [loop body block 0, loop body block 1, ...]
   */

  Ap.trace = function(writer) {
    function blockId(node) {
      return node.blockId;
    }

    writer.enter("analysis {");

    var ranControlFlow = !!this.bytecodes[0].succs;

    for (var pc = 0, end = this.bytecodes.length; pc < end; pc++) {
      var code = this.bytecodes[pc];

      if (ranControlFlow && code.succs) {
        if (pc > 0) {
          writer.leave("}");
        }

        if (!code.dominator) {
          writer.enter("block unreachable {");
        } else {
          writer.enter("block " + code.dominator.blockId + " >> " + code.blockId +
                       (code.succs.length > 0 ? " -> " +
                        code.succs.map(blockId).join(",") : "") + " {");
        }

        if (code.loop) {
          writer.writeLn("loop [" + code.loop.map(blockId).join(",") + "]");
          writer.writeLn("");
        }
      }

      writer.writeLn(("" + pc).padRight(' ', 5) + code);

      if (ranControlFlow && pc === end - 1) {
        writer.leave("}");
      }
    }

    writer.leave("}");
  };

  Ap.traceGraphViz = function traceGraphViz(writer, name, prefix) {
    prefix = prefix || "";
    if (!this.bytecodes) {
      return;
    }
    writeGraphViz(writer, name.toString(), this.bytecodes[0],
      function (n) {
        return prefix + n.blockId;
      },
      function (n) {
        return n.succs ? n.succs : [];
      }, function (n) {
        return n.preds ? n.preds : [];
      }, function (n) {
        return "Block: " + n.blockId;
      }
    );
  };

  return Analysis;

})();<|endoftext|>


<commit_before>/*
 * django-scribbler
 * Source: https://github.com/mlavin/django-scribbler
 * Docs: http://django-scribbler.readthedocs.org/
 *
 * Copyright 2012, Mark Lavin
 * BSD License
 *
*/

require.config({
    paths: {
        jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min',
        codemirror: '../libs/codemirror-compressed'
    },
    shim: {
        codemirror: {
            exports: 'CodeMirror'
        }
    }
});

require(['jquery', 'codemirror'], function($, CodeMirror) {
    var ScribbleEditor = {
        visible: false,
        rendering: false,
        errorLine: null,
        element: null,
        controls: {},
        current: {},
        editor: null,
        scribbles: null,
        init: function() {
            this.scribbles = $('.scribble-wrapper.with-controls');
            if (this.scribbles.length > 0) {
                this.element = $('

');
                this.buildControls();
                $('body').append(this.element);
                // Initialize CodeMirror editor
                var options = {
                    mode: "text/html",
                    tabMode: "indent",
                    lineNumbers: true,
                    onChange: function(editor) {ScribbleEditor.submitPreview();}
                };
                this.editor = CodeMirror(
                    document.getElementById("scribbleEditorContainer"),
                    options
                );
                // Bind editor to the scribbles
                this.scribbles.each(function(i, elem) {
                    // Bind event handlers for each scribble
                    $(elem).click(function(e) {
                        e.preventDefault();
                        ScribbleEditor.open($(this));
                    });
                });
            }
        },
        buildControls: function() {
            // Build control bar
            var footerControls = $('

').addClass('controls clearfix');
            // Close button
            this.controls.close = $('Close')
            .attr({title: 'Close', href: '#'})
            .addClass('close')
            .click(function(e) {
                e.preventDefault();
                ScribbleEditor.close();
            });
            // Save button
            this.controls.save = $('Save')
            .attr({title: 'Save', href: "#"})
            .addClass('btn save').click(function(e) {
                e.preventDefault();
                ScribbleEditor.submitSave();
            });
            // Error message
            this.controls.errors = $('')
            .addClass('error-msg');
            footerControls.append(
                this.controls.errors,
                this.controls.close,
                this.controls.save
            );
            this.element.append(footerControls);
        },
        open: function(scribble) {
            this.current.content = $('.scribble-content.original', scribble);
            this.current.preview = $('.scribble-content.preview', scribble);
            this.current.form = $('.scribble-form', scribble);
            this.current.can_save = this.current.form.data('save');
            this.current.can_delete = this.current.form.data('delete');
            this.element.show();
            this.element.animate({height: '300px'}, 500);
            if (this.current.can_save) {
                this.controls.save.show();
                this.editor.setOption('readOnly', false);
                this.editor.setValue($('[name$=content]', this.current.form).val());
            } else {
                this.controls.save.hide();
                this.editor.setOption('readOnly', true);
                this.editor.setValue('You do not have permission to edit this content.');
            }
            this.editor.focus();
            this.visible = true;
        },
        close: function() {
            this.current.preview.hide();
            this.current.content.show();
            $('[name$=content]', this.current.form).val(this.editor.getValue());
            this.current = {};
            this.editor.setValue('');
            this.element.animate({height: 0}, 500);
            this.visible = false;
        },
        submitPreview: function() {
            if (this.current.form && !this.rendering && !this.editor.getOption('readOnly')) {
                this.rendering = true;
                // Submit the form and display the preview
                $.post(
                    this.current.form.attr('action'),
                    this.getFormData(),
                    function(response) {
                        ScribbleEditor.renderPreview(response);
                    },
                    'json'
                );
            }
        },
        renderPreview: function(response) {
            if (this.errorLine !== null) {
                this.editor.setLineClass(this.errorLine, null, null);
            }
            this.controls.errors.html('');
            if (response.valid) {
                this.current.preview.html(response.html);
                this.current.preview.show();
                this.current.content.hide();
                this.controls.save.show();
            } else {
                this.errorLine = response.error.line - 1;
                this.editor.setLineClass(this.errorLine, null, "activeline");
                this.controls.errors.html("Error: " + response.error.message);
                this.controls.save.hide();
            }
            this.rendering = false;
        },
        getFormData: function() {
            var result = {};
            var prefix = '';
            if (this.current.form) {
                prefix = this.current.form.data('prefix');
                $(':input', this.current.form).each(function(i, input) {
                    var inputName = $(input).attr('name').replace(prefix + '-', '');
                    var inputValue = $(input).val();
                    if (inputName === 'content') {
                        result[inputName] = ScribbleEditor.editor.getValue();
                    } else {
                        result[inputName] = inputValue;
                    }
                });
            }
            return result;
        },
        submitSave: function() {
            if (this.current.form && !this.errorLine) {
                // Submit the form and change current content
                $.post(
                    this.current.form.data('save'),
                    this.getFormData(),
                    function(response) {
                        ScribbleEditor.renderSave(response);
                    },
                    'json'
                );
            }
        },
        renderSave: function(response) {
            if (response.valid) {
                this.current.form.data('save', response.url);
                this.current.content.html(this.current.preview.html());
                this.close();
            } else {
                this.controls.errors.html("Error: Content is not valid");
            }
        }
    };

    $(document).ready(function(){ScribbleEditor.init();});
});
<commit_msg>There is no reason to prevent the default click event for the scribble-wrapper div. Prevents following links when signed in as a content editor. Fixes case #21.<commit_after>/*
 * django-scribbler
 * Source: https://github.com/mlavin/django-scribbler
 * Docs: http://django-scribbler.readthedocs.org/
 *
 * Copyright 2012, Mark Lavin
 * BSD License
 *
*/

require.config({
    paths: {
        jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min',
        codemirror: '../libs/codemirror-compressed'
    },
    shim: {
        codemirror: {
            exports: 'CodeMirror'
        }
    }
});

require(['jquery', 'codemirror'], function($, CodeMirror) {
    var ScribbleEditor = {
        visible: false,
        rendering: false,
        errorLine: null,
        element: null,
        controls: {},
        current: {},
        editor: null,
        scribbles: null,
        init: function() {
            this.scribbles = $('.scribble-wrapper.with-controls');
            if (this.scribbles.length > 0) {
                this.element = $('

');
                this.buildControls();
                $('body').append(this.element);
                // Initialize CodeMirror editor
                var options = {
                    mode: "text/html",
                    tabMode: "indent",
                    lineNumbers: true,
                    onChange: function(editor) {ScribbleEditor.submitPreview();}
                };
                this.editor = CodeMirror(
                    document.getElementById("scribbleEditorContainer"),
                    options
                );
                // Bind editor to the scribbles
                this.scribbles.each(function(i, elem) {
                    // Bind event handlers for each scribble
                    $(elem).click(function(e) {
                        ScribbleEditor.open($(this));
                    });
                });
            }
        },
        buildControls: function() {
            // Build control bar
            var footerControls = $('

').addClass('controls clearfix');
            // Close button
            this.controls.close = $('Close')
            .attr({title: 'Close', href: '#'})
            .addClass('close')
            .click(function(e) {
                e.preventDefault();
                ScribbleEditor.close();
            });
            // Save button
            this.controls.save = $('Save')
            .attr({title: 'Save', href: "#"})
            .addClass('btn save').click(function(e) {
                e.preventDefault();
                ScribbleEditor.submitSave();
            });
            // Error message
            this.controls.errors = $('')
            .addClass('error-msg');
            footerControls.append(
                this.controls.errors,
                this.controls.close,
                this.controls.save
            );
            this.element.append(footerControls);
        },
        open: function(scribble) {
            this.current.content = $('.scribble-content.original', scribble);
            this.current.preview = $('.scribble-content.preview', scribble);
            this.current.form = $('.scribble-form', scribble);
            this.current.can_save = this.current.form.data('save');
            this.current.can_delete = this.current.form.data('delete');
            this.element.show();
            this.element.animate({height: '300px'}, 500);
            if (this.current.can_save) {
                this.controls.save.show();
                this.editor.setOption('readOnly', false);
                this.editor.setValue($('[name$=content]', this.current.form).val());
            } else {
                this.controls.save.hide();
                this.editor.setOption('readOnly', true);
                this.editor.setValue('You do not have permission to edit this content.');
            }
            this.editor.focus();
            this.visible = true;
        },
        close: function() {
            this.current.preview.hide();
            this.current.content.show();
            $('[name$=content]', this.current.form).val(this.editor.getValue());
            this.current = {};
            this.editor.setValue('');
            this.element.animate({height: 0}, 500);
            this.visible = false;
        },
        submitPreview: function() {
            if (this.current.form && !this.rendering && !this.editor.getOption('readOnly')) {
                this.rendering = true;
                // Submit the form and display the preview
                $.post(
                    this.current.form.attr('action'),
                    this.getFormData(),
                    function(response) {
                        ScribbleEditor.renderPreview(response);
                    },
                    'json'
                );
            }
        },
        renderPreview: function(response) {
            if (this.errorLine !== null) {
                this.editor.setLineClass(this.errorLine, null, null);
            }
            this.controls.errors.html('');
            if (response.valid) {
                this.current.preview.html(response.html);
                this.current.preview.show();
                this.current.content.hide();
                this.controls.save.show();
            } else {
                this.errorLine = response.error.line - 1;
                this.editor.setLineClass(this.errorLine, null, "activeline");
                this.controls.errors.html("Error: " + response.error.message);
                this.controls.save.hide();
            }
            this.rendering = false;
        },
        getFormData: function() {
            var result = {};
            var prefix = '';
            if (this.current.form) {
                prefix = this.current.form.data('prefix');
                $(':input', this.current.form).each(function(i, input) {
                    var inputName = $(input).attr('name').replace(prefix + '-', '');
                    var inputValue = $(input).val();
                    if (inputName === 'content') {
                        result[inputName] = ScribbleEditor.editor.getValue();
                    } else {
                        result[inputName] = inputValue;
                    }
                });
            }
            return result;
        },
        submitSave: function() {
            if (this.current.form && !this.errorLine) {
                // Submit the form and change current content
                $.post(
                    this.current.form.data('save'),
                    this.getFormData(),
                    function(response) {
                        ScribbleEditor.renderSave(response);
                    },
                    'json'
                );
            }
        },
        renderSave: function(response) {
            if (response.valid) {
                this.current.form.data('save', response.url);
                this.current.content.html(this.current.preview.html());
                this.close();
            } else {
                this.controls.errors.html("Error: Content is not valid");
            }
        }
    };

    $(document).ready(function(){ScribbleEditor.init();});
});
<|endoftext|>