<commit_before>import external from './../../externalModules.js';
import BaseAnnotationTool from '../base/BaseAnnotationTool.js';

// State
import { getToolState } from './../../stateManagement/toolState.js';
import toolStyle from './../../stateManagement/toolStyle.js';
import toolColors from './../../stateManagement/toolColors.js';

// Drawing
import {
  getNewContext,
  draw,
  drawHandles,
  drawRect,
  drawLinkedTextBox,
  setShadow,
} from './../../drawing/index.js';

// Util
import calculateSUV from './../../util/calculateSUV.js';
import numbersWithCommas from './../../util/numbersWithCommas.js';
import throttle from './../../util/throttle.js';

/**
 * @public
 * @class RectangleRoiTool
 * @memberof Tools.Annotation
 * @classdesc Tool for drawing rectangular regions of interest, and measuring
 * the statistics of the enclosed pixels.
 * @extends Tools.Base.BaseAnnotationTool
 */
export default class RectangleRoiTool extends BaseAnnotationTool {
  constructor(configuration = {}) {
    const defaultConfig = {
      name: 'RectangleRoi',
      supportedInteractionTypes: ['Mouse', 'Touch'],
      configuration: {
        // showMinMax: false,
        // showHounsfieldUnits: true
      },
    };
    const initialConfiguration = Object.assign(defaultConfig, configuration);

    super(initialConfiguration);
    this.initialConfiguration = initialConfiguration;
  }

  createNewMeasurement(eventData) {
    const goodEventData =
      eventData && eventData.currentPoints && eventData.currentPoints.image;

    if (!goodEventData) {
      console.error(
        `required eventData not supplied to tool ${
          this.name
        }'s createNewMeasurement`
      );

      return;
    }

    return {
      visible: true,
      active: true,
      color: undefined,
      invalidated: true,
      handles: {
        start: {
          x: eventData.currentPoints.image.x,
          y: eventData.currentPoints.image.y,
          highlight: true,
          active: false,
        },
        end: {
          x: eventData.currentPoints.image.x,
          y: eventData.currentPoints.image.y,
          highlight: true,
          active: true,
        },
        textBox: {
          active: false,
          hasMoved: false,
          movesIndependently: false,
          drawnIndependently: true,
          allowedOutsideImage: true,
          hasBoundingBox: true,
        },
      },
    };
  }

  pointNearTool(element, data, coords, interactionType) {
    const hasStartAndEndHandles =
      data && data.handles && data.handles.start && data.handles.end;
    const validParameters = hasStartAndEndHandles;

    if (!validParameters) {
      console.warn(
        `invalid parameters supplieed to tool ${this.name}'s pointNearTool`
      );
    }

    if (!validParameters || data.visible === false) {
      return false;
    }

    const distance = interactionType === 'mouse' ? 15 : 25;
    const startCanvas = external.cornerstone.pixelToCanvas(
      element,
      data.handles.start
    );
    const endCanvas = external.cornerstone.pixelToCanvas(
      element,
      data.handles.end
    );

    const rect = {
      left: Math.min(startCanvas.x, endCanvas.x),
      top: Math.min(startCanvas.y, endCanvas.y),
      width: Math.abs(startCanvas.x - endCanvas.x),
      height: Math.abs(startCanvas.y - endCanvas.y),
    };

    const distanceToPoint = external.cornerstoneMath.rect.distanceToPoint(
      rect,
      coords
    );

    return distanceToPoint < distance;
  }

  renderToolData(evt) {
    const toolData = getToolState(evt.currentTarget, this.name);

    if (!toolData) {
      return;
    }

    const eventData = evt.detail;
    const { image, element } = eventData;
    const lineWidth = toolStyle.getToolWidth();
    const { handleRadius, drawHandlesOnHover } = this.configuration;
    const context = getNewContext(eventData.canvasContext.canvas);

    // Meta
    const seriesModule =
      external.cornerstone.metaData.get('generalSeriesModule', image.imageId) ||
      {};
    let imagePlane = external.cornerstone.metaData.get(
      'imagePlaneModule',
      image.imageId
    );

    // Pixel Spacing
    const modality = seriesModule.modality;
    const hasPixelSpacing =
      imagePlane && imagePlane.rowPixelSpacing && imagePlane.columnPixelSpacing;

    imagePlane = imagePlane || {};
    const pixelSpacing = {
      rowPixelSpacing: imagePlane.rowPixelSpacing || 1,
      columnPixelSpacing: imagePlane.columnPixelSpacing || 1,
    };

    draw(context, context => {
      // If we have tool data for this element - iterate over each set and draw it
      for (let i = 0; i < toolData.data.length; i++) {
        const data = toolData.data[i];

        if (data.visible === false) {
          continue;
        }

        // Configure
        const color = toolColors.getColorIfActive(data);
        const handleOptions = {
          color,
          handleRadius,
          drawHandlesIfActive: drawHandlesOnHover,
        };

        setShadow(context, this.configuration);

        // Draw
        drawRect(context, element, data.handles.start, data.handles.end, {
          color,
        });
        drawHandles(context, eventData, data.handles, handleOptions);

        // Update textbox stats
        if (data.invalidated === true) {
          if (data.cachedStats) {
            _throttledUpdateCachedStats(
              image,
              element,
              data,
              modality,
              pixelSpacing
            );
          } else {
            _updateCachedStats(image, element, data, modality, pixelSpacing);
          }
        }

        // Default to textbox on right side of ROI
        if (!data.handles.textBox.hasMoved) {
          data.handles.textBox.x = Math.max(
            data.handles.start.x,
            data.handles.end.x
          );
          data.handles.textBox.y =
            (data.handles.start.y + data.handles.end.y) / 2;
        }

        const textBoxAnchorPoints = handles => {
          return _findTextBoxAnchorPoints(handles.start, handles.end);
        };
        const textBoxContent = _createTextBoxContent(
          context,
          image.color,
          data.cachedStats,
          modality,
          hasPixelSpacing,
          this.configuration
        );

        drawLinkedTextBox(
          context,
          element,
          data.handles.textBox,
          textBoxContent,
          data.handles,
          textBoxAnchorPoints,
          color,
          lineWidth,
          0,
          true
        );
      }
    });
  }
}

/**
 *
 */
const _throttledUpdateCachedStats = throttle(_updateCachedStats, 110);

/**
 *
 *
 * @param {*} image
 * @param {*} element
 * @param {*} data
 * @param {string} modality
 * @param {*} pixelSpacing
 */
function _updateCachedStats(image, element, data, modality, pixelSpacing) {
  const stats = _calculateStats(
    image,
    element,
    data.handles,
    modality,
    pixelSpacing
  );
  data.cachedStats = stats;
  data.invalidated = false;
}

/**
 * TODO: This is the same method (+ GetPixels) for the other ROIs
 * TODO: The pixel filtering is the unique bit
 *
 * @param {*} startHandle
 * @param {*} endHandle
 * @returns
 */
function _getRectangleImageCoordinates(startHandle, endHandle) {
  return {
    left: Math.min(startHandle.x, endHandle.x),
    top: Math.min(startHandle.y, endHandle.y),
    width: Math.abs(startHandle.x - endHandle.x),
    height: Math.abs(startHandle.y - endHandle.y),
  };
}

/**
 *
 *
 * @param {*} image
 * @param {*} element
 * @param {*} handles
 * @param {*} modality
 * @param {*} pixelSpacing
 * @returns
 */
function _calculateStats(image, element, handles, modality, pixelSpacing) {
  // Retrieve the bounds of the ellipse in image coordinates
  const roiCoordinates = _getRectangleImageCoordinates(
    handles.start,
    handles.end
  );

  // Retrieve the array of pixels that the ellipse bounds cover
  const pixels = external.cornerstone.getPixels(
    element,
    roiCoordinates.left,
    roiCoordinates.top,
    roiCoordinates.width,
    roiCoordinates.height
  );

  // Calculate the mean & standard deviation from the pixels and the ellipse details
  const roiMeanStdDev = _calculateRectangleStats(pixels, roiCoordinates);

  let meanStdDevSUV;
  if (modality === 'PT') {
    meanStdDevSUV = {
      mean: calculateSUV(image, roiMeanStdDev.mean, true) || 0,
      stdDev: calculateSUV(image, roiMeanStdDev.stdDev, true) || 0,
    };
  }

  // Calculate the image area from the ellipse dimensions and pixel spacing
  const area =
    roiCoordinates.width *
    (pixelSpacing.columnPixelSpacing || 1) *
    (roiCoordinates.height * (pixelSpacing.rowPixelSpacing || 1));

  return {
    area: area || 0,
    count: roiMeanStdDev.count || 0,
    mean: roiMeanStdDev.mean || 0,
    variance: roiMeanStdDev.variance || 0,
    stdDev: roiMeanStdDev.stdDev || 0,
    min: roiMeanStdDev.min || 0,
    max: roiMeanStdDev.max || 0,
    meanStdDevSUV,
  };
}

/**
 *
 *
 * @param {*} sp
 * @param {*} rectangle
 * @returns
 */
function _calculateRectangleStats(sp, rectangle) {
  let sum = 0;
  let sumSquared = 0;
  let count = 0;
  let index = 0;
  let min = sp ? sp[0] : null;
  let max = sp ? sp[0] : null;

  for (let y = rectangle.top; y < rectangle.top + rectangle.height; y++) {
    for (let x = rectangle.left; x < rectangle.left + rectangle.width; x++) {
      sum += sp[index];
      sumSquared += sp[index] * sp[index];
      min = Math.min(min, sp[index]);
      max = Math.max(max, sp[index]);
      count++; // TODO: Wouldn't this just be sp.length?
      index++;
    }
  }

  if (count === 0) {
    return {
      count,
      mean: 0.0,
      variance: 0.0,
      stdDev: 0.0,
      min: 0.0,
      max: 0.0,
    };
  }

  const mean = sum / count;
  const variance = sumSquared / count - mean * mean;

  return {
    count,
    mean,
    variance,
    stdDev: Math.sqrt(variance),
    min,
    max,
  };
}

/**
 *
 *
 * @param {*} startHandle
 * @param {*} endHandle
 * @returns
 */
function _findTextBoxAnchorPoints(startHandle, endHandle) {
  const { left, top, width, height } = _getRectangleImageCoordinates(
    startHandle,
    endHandle
  );

  return [
    {
      // Top middle point of rectangle
      x: left + width / 2,
      y: top,
    },
    {
      // Left middle point of rectangle
      x: left,
      y: top + height / 2,
    },
    {
      // Bottom middle point of rectangle
      x: left + width / 2,
      y: top + height,
    },
    {
      // Right middle point of rectangle
      x: left + width,
      y: top + height / 2,
    },
  ];
}

/**
 *
 *
 * @param {*} area
 * @param {*} hasPixelSpacing
 * @returns
 */
function _formatArea(area, hasPixelSpacing) {
  // This uses Char code 178 for a superscript 2
  const suffix = hasPixelSpacing
    ? ` mm${String.fromCharCode(178)}`
    : ` px${String.fromCharCode(178)}`;

  return `Area: ${numbersWithCommas(area.toFixed(2))}${suffix}`;
}

/**
 * TODO: This is identical to EllipticalROI's same fn
 * TODO: We may want to make this a utility for ROIs with these values?
 *
 * @param {*} context
 * @param {*} isColorImage
 * @param {*} { area, mean, stdDev, min, max, meanStdDevSUV }
 * @param {*} modality
 * @param {*} hasPixelSpacing
 * @param {*} [options={}]
 * @returns
 */
function _createTextBoxContent(
  context,
  isColorImage,
  { area, mean, stdDev, min, max, meanStdDevSUV },
  modality,
  hasPixelSpacing,
  options = {}
) {
  const showMinMax = options.showMinMax || false;
  const showHounsfieldUnits = options.showHounsfieldUnits || true;
  const textLines = [];

  let otherLines = [];
  if (!isColorImage) {
    const hasStandardUptakeValues = meanStdDevSUV && meanStdDevSUV.mean !== 0;
    const suffix = modality === 'CT' && showHounsfieldUnits ? ' HU' : '';

    let meanString = `Mean: ${numbersWithCommas(mean.toFixed(2))}${suffix}`;
    const stdDevString = `Std Dev: ${numbersWithCommas(
      stdDev.toFixed(2)
    )}${suffix}`;

    // If this image has SUV values to display, concatenate them to the text line
    if (hasStandardUptakeValues) {
      const SUVtext = ' SUV: ';

      const meanSuvString = `${SUVtext}${numbersWithCommas(
        meanStdDevSUV.mean.toFixed(2)
      )}`;
      const stdDevSuvString = `${SUVtext}${numbersWithCommas(
        meanStdDevSUV.stdDev.toFixed(2)
      )}`;

      const targetStringLength = Math.floor(
        context.measureText(`${stdDevString}     `).width
      );
      while (context.measureText(meanString).width < targetStringLength) {
        meanString += ' ';
      }

      otherLines.push(`${meanString}${meanSuvString}`);
      otherLines.push(`${stdDevString}     ${stdDevSuvString}`);
    } else {
      otherLines.push(`${meanString}     ${stdDevString}`);
    }

    if (showMinMax) {
      let minString = `Min: ${min}${suffix}`;
      const maxString = `Max: ${max}${suffix}`;
      const targetStringLength = hasStandardUptakeValues
        ? Math.floor(context.measureText(`${stdDevString}     `).width)
        : Math.floor(context.measureText(`${meanString}     `).width);

      while (context.measureText(minString).width < targetStringLength) {
        minString += ' ';
      }

      otherLines.push(`${minString}${maxString}`);
    }
  }

  textLines.push(_formatArea(area, hasPixelSpacing));
  otherLines.forEach(x => textLines.push(x));

  return textLines;
}
<commit_msg>Update Rectangle comments<commit_after>import external from './../../externalModules.js';
import BaseAnnotationTool from '../base/BaseAnnotationTool.js';

// State
import { getToolState } from './../../stateManagement/toolState.js';
import toolStyle from './../../stateManagement/toolStyle.js';
import toolColors from './../../stateManagement/toolColors.js';

// Drawing
import {
  getNewContext,
  draw,
  drawHandles,
  drawRect,
  drawLinkedTextBox,
  setShadow,
} from './../../drawing/index.js';

// Util
import calculateSUV from './../../util/calculateSUV.js';
import numbersWithCommas from './../../util/numbersWithCommas.js';
import throttle from './../../util/throttle.js';

/**
 * @public
 * @class RectangleRoiTool
 * @memberof Tools.Annotation
 * @classdesc Tool for drawing rectangular regions of interest, and measuring
 * the statistics of the enclosed pixels.
 * @extends Tools.Base.BaseAnnotationTool
 */
export default class RectangleRoiTool extends BaseAnnotationTool {
  constructor(configuration = {}) {
    const defaultConfig = {
      name: 'RectangleRoi',
      supportedInteractionTypes: ['Mouse', 'Touch'],
      configuration: {
        // showMinMax: false,
        // showHounsfieldUnits: true
      },
    };
    const initialConfiguration = Object.assign(defaultConfig, configuration);

    super(initialConfiguration);
    this.initialConfiguration = initialConfiguration;
  }

  createNewMeasurement(eventData) {
    const goodEventData =
      eventData && eventData.currentPoints && eventData.currentPoints.image;

    if (!goodEventData) {
      console.error(
        `required eventData not supplied to tool ${
          this.name
        }'s createNewMeasurement`
      );

      return;
    }

    return {
      visible: true,
      active: true,
      color: undefined,
      invalidated: true,
      handles: {
        start: {
          x: eventData.currentPoints.image.x,
          y: eventData.currentPoints.image.y,
          highlight: true,
          active: false,
        },
        end: {
          x: eventData.currentPoints.image.x,
          y: eventData.currentPoints.image.y,
          highlight: true,
          active: true,
        },
        textBox: {
          active: false,
          hasMoved: false,
          movesIndependently: false,
          drawnIndependently: true,
          allowedOutsideImage: true,
          hasBoundingBox: true,
        },
      },
    };
  }

  pointNearTool(element, data, coords, interactionType) {
    const hasStartAndEndHandles =
      data && data.handles && data.handles.start && data.handles.end;
    const validParameters = hasStartAndEndHandles;

    if (!validParameters) {
      console.warn(
        `invalid parameters supplieed to tool ${this.name}'s pointNearTool`
      );
    }

    if (!validParameters || data.visible === false) {
      return false;
    }

    const distance = interactionType === 'mouse' ? 15 : 25;
    const startCanvas = external.cornerstone.pixelToCanvas(
      element,
      data.handles.start
    );
    const endCanvas = external.cornerstone.pixelToCanvas(
      element,
      data.handles.end
    );

    const rect = {
      left: Math.min(startCanvas.x, endCanvas.x),
      top: Math.min(startCanvas.y, endCanvas.y),
      width: Math.abs(startCanvas.x - endCanvas.x),
      height: Math.abs(startCanvas.y - endCanvas.y),
    };

    const distanceToPoint = external.cornerstoneMath.rect.distanceToPoint(
      rect,
      coords
    );

    return distanceToPoint < distance;
  }

  renderToolData(evt) {
    const toolData = getToolState(evt.currentTarget, this.name);

    if (!toolData) {
      return;
    }

    const eventData = evt.detail;
    const { image, element } = eventData;
    const lineWidth = toolStyle.getToolWidth();
    const { handleRadius, drawHandlesOnHover } = this.configuration;
    const context = getNewContext(eventData.canvasContext.canvas);

    // Meta
    const seriesModule =
      external.cornerstone.metaData.get('generalSeriesModule', image.imageId) ||
      {};
    let imagePlane = external.cornerstone.metaData.get(
      'imagePlaneModule',
      image.imageId
    );

    // Pixel Spacing
    const modality = seriesModule.modality;
    const hasPixelSpacing =
      imagePlane && imagePlane.rowPixelSpacing && imagePlane.columnPixelSpacing;

    imagePlane = imagePlane || {};
    const pixelSpacing = {
      rowPixelSpacing: imagePlane.rowPixelSpacing || 1,
      columnPixelSpacing: imagePlane.columnPixelSpacing || 1,
    };

    draw(context, context => {
      // If we have tool data for this element - iterate over each set and draw it
      for (let i = 0; i < toolData.data.length; i++) {
        const data = toolData.data[i];

        if (data.visible === false) {
          continue;
        }

        // Configure
        const color = toolColors.getColorIfActive(data);
        const handleOptions = {
          color,
          handleRadius,
          drawHandlesIfActive: drawHandlesOnHover,
        };

        setShadow(context, this.configuration);

        // Draw
        drawRect(context, element, data.handles.start, data.handles.end, {
          color,
        });
        drawHandles(context, eventData, data.handles, handleOptions);

        // Update textbox stats
        if (data.invalidated === true) {
          if (data.cachedStats) {
            _throttledUpdateCachedStats(
              image,
              element,
              data,
              modality,
              pixelSpacing
            );
          } else {
            _updateCachedStats(image, element, data, modality, pixelSpacing);
          }
        }

        // Default to textbox on right side of ROI
        if (!data.handles.textBox.hasMoved) {
          data.handles.textBox.x = Math.max(
            data.handles.start.x,
            data.handles.end.x
          );
          data.handles.textBox.y =
            (data.handles.start.y + data.handles.end.y) / 2;
        }

        const textBoxAnchorPoints = handles => {
          return _findTextBoxAnchorPoints(handles.start, handles.end);
        };
        const textBoxContent = _createTextBoxContent(
          context,
          image.color,
          data.cachedStats,
          modality,
          hasPixelSpacing,
          this.configuration
        );

        drawLinkedTextBox(
          context,
          element,
          data.handles.textBox,
          textBoxContent,
          data.handles,
          textBoxAnchorPoints,
          color,
          lineWidth,
          0,
          true
        );
      }
    });
  }
}

/**
 *
 */
const _throttledUpdateCachedStats = throttle(_updateCachedStats, 110);

/**
 *
 *
 * @param {*} image
 * @param {*} element
 * @param {*} data
 * @param {string} modality
 * @param {*} pixelSpacing
 */
function _updateCachedStats(image, element, data, modality, pixelSpacing) {
  const stats = _calculateStats(
    image,
    element,
    data.handles,
    modality,
    pixelSpacing
  );
  data.cachedStats = stats;
  data.invalidated = false;
}

/**
 * TODO: This is the same method (+ GetPixels) for the other ROIs
 * TODO: The pixel filtering is the unique bit
 *
 * @param {*} startHandle
 * @param {*} endHandle
 * @returns
 */
function _getRectangleImageCoordinates(startHandle, endHandle) {
  return {
    left: Math.min(startHandle.x, endHandle.x),
    top: Math.min(startHandle.y, endHandle.y),
    width: Math.abs(startHandle.x - endHandle.x),
    height: Math.abs(startHandle.y - endHandle.y),
  };
}

/**
 *
 *
 * @param {*} image
 * @param {*} element
 * @param {*} handles
 * @param {*} modality
 * @param {*} pixelSpacing
 * @returns
 */
function _calculateStats(image, element, handles, modality, pixelSpacing) {
  // Retrieve the bounds of the rectangle in image coordinates
  const roiCoordinates = _getRectangleImageCoordinates(
    handles.start,
    handles.end
  );

  // Retrieve the array of pixels that the rectangle bounds cover
  const pixels = external.cornerstone.getPixels(
    element,
    roiCoordinates.left,
    roiCoordinates.top,
    roiCoordinates.width,
    roiCoordinates.height
  );

  // Calculate the mean & standard deviation from the pixels and the rectangle details
  const roiMeanStdDev = _calculateRectangleStats(pixels, roiCoordinates);

  let meanStdDevSUV;
  if (modality === 'PT') {
    meanStdDevSUV = {
      mean: calculateSUV(image, roiMeanStdDev.mean, true) || 0,
      stdDev: calculateSUV(image, roiMeanStdDev.stdDev, true) || 0,
    };
  }

  // Calculate the image area from the rectangle dimensions and pixel spacing
  const area =
    roiCoordinates.width *
    (pixelSpacing.columnPixelSpacing || 1) *
    (roiCoordinates.height * (pixelSpacing.rowPixelSpacing || 1));

  return {
    area: area || 0,
    count: roiMeanStdDev.count || 0,
    mean: roiMeanStdDev.mean || 0,
    variance: roiMeanStdDev.variance || 0,
    stdDev: roiMeanStdDev.stdDev || 0,
    min: roiMeanStdDev.min || 0,
    max: roiMeanStdDev.max || 0,
    meanStdDevSUV,
  };
}

/**
 *
 *
 * @param {*} sp
 * @param {*} rectangle
 * @returns
 */
function _calculateRectangleStats(sp, rectangle) {
  let sum = 0;
  let sumSquared = 0;
  let count = 0;
  let index = 0;
  let min = sp ? sp[0] : null;
  let max = sp ? sp[0] : null;

  for (let y = rectangle.top; y < rectangle.top + rectangle.height; y++) {
    for (let x = rectangle.left; x < rectangle.left + rectangle.width; x++) {
      sum += sp[index];
      sumSquared += sp[index] * sp[index];
      min = Math.min(min, sp[index]);
      max = Math.max(max, sp[index]);
      count++; // TODO: Wouldn't this just be sp.length?
      index++;
    }
  }

  if (count === 0) {
    return {
      count,
      mean: 0.0,
      variance: 0.0,
      stdDev: 0.0,
      min: 0.0,
      max: 0.0,
    };
  }

  const mean = sum / count;
  const variance = sumSquared / count - mean * mean;

  return {
    count,
    mean,
    variance,
    stdDev: Math.sqrt(variance),
    min,
    max,
  };
}

/**
 *
 *
 * @param {*} startHandle
 * @param {*} endHandle
 * @returns
 */
function _findTextBoxAnchorPoints(startHandle, endHandle) {
  const { left, top, width, height } = _getRectangleImageCoordinates(
    startHandle,
    endHandle
  );

  return [
    {
      // Top middle point of rectangle
      x: left + width / 2,
      y: top,
    },
    {
      // Left middle point of rectangle
      x: left,
      y: top + height / 2,
    },
    {
      // Bottom middle point of rectangle
      x: left + width / 2,
      y: top + height,
    },
    {
      // Right middle point of rectangle
      x: left + width,
      y: top + height / 2,
    },
  ];
}

/**
 *
 *
 * @param {*} area
 * @param {*} hasPixelSpacing
 * @returns
 */
function _formatArea(area, hasPixelSpacing) {
  // This uses Char code 178 for a superscript 2
  const suffix = hasPixelSpacing
    ? ` mm${String.fromCharCode(178)}`
    : ` px${String.fromCharCode(178)}`;

  return `Area: ${numbersWithCommas(area.toFixed(2))}${suffix}`;
}

/**
 * TODO: This is identical to EllipticalROI's same fn
 * TODO: We may want to make this a utility for ROIs with these values?
 *
 * @param {*} context
 * @param {*} isColorImage
 * @param {*} { area, mean, stdDev, min, max, meanStdDevSUV }
 * @param {*} modality
 * @param {*} hasPixelSpacing
 * @param {*} [options={}]
 * @returns
 */
function _createTextBoxContent(
  context,
  isColorImage,
  { area, mean, stdDev, min, max, meanStdDevSUV },
  modality,
  hasPixelSpacing,
  options = {}
) {
  const showMinMax = options.showMinMax || false;
  const showHounsfieldUnits = options.showHounsfieldUnits || true;
  const textLines = [];

  let otherLines = [];
  if (!isColorImage) {
    const hasStandardUptakeValues = meanStdDevSUV && meanStdDevSUV.mean !== 0;
    const suffix = modality === 'CT' && showHounsfieldUnits ? ' HU' : '';

    let meanString = `Mean: ${numbersWithCommas(mean.toFixed(2))}${suffix}`;
    const stdDevString = `Std Dev: ${numbersWithCommas(
      stdDev.toFixed(2)
    )}${suffix}`;

    // If this image has SUV values to display, concatenate them to the text line
    if (hasStandardUptakeValues) {
      const SUVtext = ' SUV: ';

      const meanSuvString = `${SUVtext}${numbersWithCommas(
        meanStdDevSUV.mean.toFixed(2)
      )}`;
      const stdDevSuvString = `${SUVtext}${numbersWithCommas(
        meanStdDevSUV.stdDev.toFixed(2)
      )}`;

      const targetStringLength = Math.floor(
        context.measureText(`${stdDevString}     `).width
      );
      while (context.measureText(meanString).width < targetStringLength) {
        meanString += ' ';
      }

      otherLines.push(`${meanString}${meanSuvString}`);
      otherLines.push(`${stdDevString}     ${stdDevSuvString}`);
    } else {
      otherLines.push(`${meanString}     ${stdDevString}`);
    }

    if (showMinMax) {
      let minString = `Min: ${min}${suffix}`;
      const maxString = `Max: ${max}${suffix}`;
      const targetStringLength = hasStandardUptakeValues
        ? Math.floor(context.measureText(`${stdDevString}     `).width)
        : Math.floor(context.measureText(`${meanString}     `).width);

      while (context.measureText(minString).width < targetStringLength) {
        minString += ' ';
      }

      otherLines.push(`${minString}${maxString}`);
    }
  }

  textLines.push(_formatArea(area, hasPixelSpacing));
  otherLines.forEach(x => textLines.push(x));

  return textLines;
}
<|endoftext|>


<commit_before>
class Storage {
  constructor(connection) {
    this._connection = connection;
  }

  merge() {

  }
}

module.exports = {
  Storage
};
<commit_msg>feat: Add model saving and data transformation<commit_after>const mongoose = require('mongoose');
const winston = require('winston');

// TODO: Refactor to separate module?
const transformers = {
  merge_request: function(payload) {
    return {
      merge_request: payload.object_attributes,
      last_commit: payload.last_commit,
      project: payload.target,
      author: payload.user,
      assignee: {
        claimed_on_slack: false,
        name: '',
        username: ''
      }
    };
  }
};

/**
 * Return mapped object for document
 */
function transform(obj) {
  return transformers[obj.object_kind](obj);
}

class Storage {
  constructor(props) {
    this._connection = props.connection;
    this._model = mongoose.model(props.schema.name, props.schema.schema);

    this.add = this.add.bind(this);
  }

  /**
   * Save instance to database using model property
   */
  add(obj) {
    var instance = new this._model(transform(obj));
    return instance.save()
    .then((res) => {
      winston.debug(res);
      return 'Created at ' + Date();
    });
  }
}

module.exports = {
  Storage
};
<|endoftext|>


<commit_before>// $Revision$
// JavaScript to dynamically change form action on the UserData upload page according to the option selected (or radio buttons checked) and do validation on the form

Ensembl.Panel.UserData = Ensembl.Panel.extend({
  init: function () {
    var panel = this;

    this.base();

    this.elLk.activeLink      = this.el.parents('.modal_wrapper').siblings('.modal_nav').find('ul.local_context li.active');
    this.elLk.form            = this.el.find('form').validate().off('.UserData').on('submit.UserData', function (e) { e.preventDefault(); panel.formSubmit(); });
    this.elLk.requiredInputs  = this.elLk.form.find(':input.required');
    this.elLk.errorMessage    = this.elLk.form.find('label._userdata_upload_error').addClass('invalid');
    this.elLk.actionInputs    = this.elLk.form.find(':input._action').each(function () {
      $(this).off('.UserData').on('change.UserData', function () {
        // change the form action according to the dropdown/radio buttons (if <select> is changed, give priority to radio buttons if they are visible)
        var action = this.nodeName === 'SELECT' ? panel.elLk.actionInputs.filter('input:visible:checked')[0] || $(this).find('option:selected')[0] : this;
            action = action ? (action.className.match(/(?:\s+|^)_action_([^\s]+)/) || []).pop() || '' : '';

        if (action) {
          panel.elLk.form.toggleClass('upload', action === 'upload').attr('action', panel.elLk.form.find('input[name=' + action + ']').val());
        }

        panel.elLk.form.validate();     // Apply/remove the validation to/from individual input field
        panel.elLk.errorMessage.hide(); // reset any validation error messages

        var visibleInps = panel.elLk.requiredInputs.validate(false).filter(':visible');

        if (visibleInps.length === 1) {
          visibleInps.validate(true);
        } else {
          // validate only if any value is entered in the inputs, ignore any null value
          visibleInps.off('.UserData').on({
            'keyup.UserData': function (e) {
              if (e.keyCode !== 9) { // ignore TAB
                $(this).validate(!!this.value);

                if (e.keyCode !== 13) { // ignore ENTER
                  panel.elLk.errorMessage.hide();
                }
              }
            },
            'change.UserData': function () {
              panel.elLk.errorMessage.hide();
            }
          });
        }
      });
    }).filter('select').validate(true).end(); // not to forget validating the dropdown to select the format
  },

  formSubmit: function () {
    if (!this.elLk.requiredInputs.filter(':visible:not([value=""])').length) {
      this.elLk.errorMessage.show();
      return false;
    }

    this.elLk.activeLink.removeClass('active');

    return Ensembl.EventManager.trigger('modalFormSubmit', this.elLk.form);
  }
});
<commit_msg>no longer a child of ModalContent - moved to 10_UserData<commit_after><|endoftext|>


<commit_before>'use strict';

angular.module('ngCart', ['ngCart.directives'])

    .config([function () {

    }])

    .provider('$ngCart', function () {

        var shipping = false;
        var tax = false;
        this.$get = function () {

        };

    })

    .run(['$rootScope', 'ngCart','ngCartItem', 'store', function ($rootScope, ngCart, ngCartItem, store) {

        $rootScope.$on('ngCart:change', function(){
            ngCart.$save();
        });

        if (angular.isObject(store.get('cart'))) {
            ngCart.$restore(store.get('cart'));

        } else {
            ngCart.init();
        }

    }])

    .service('ngCart', ['$rootScope', 'ngCartItem', 'store', function ($rootScope, ngCartItem, store) {

        this.init = function(){
            this.$cart = {
                shipping : null,
                tax : null,
                items : []
            };
        }

        this.addItem = function (id, name, price, quantity, data) {

            var inCart = this.getItemById(id);

            if (typeof inCart === 'object'){
                //Update quantity of an item if it's already in the cart
                inCart.setQuantity(quantity, false);
            } else {
                var newItem = new ngCartItem(id, name, price, quantity, data);
                this.$cart.items.push(newItem);
                $rootScope.$broadcast('ngCart:itemAdded', newItem);
            }

            $rootScope.$broadcast('ngCart:change', {});
        };

        this.getItemById = function (itemId) {
            var items = this.getCart().items;

            var build;
            angular.forEach(items, function (item) {
                if  (item.getId() === itemId) {
                    build = item;
                }
            });
            return build;
        }

        this.setShipping = function(shipping){
            this.$cart.shipping = shipping;
        }

        this.getShipping = function(){
            if (this.getCart().items.length == 0) return 0;
            return  this.getCart().shipping;
        }

        this.setTax = function(tax){
            this.$cart.tax = tax;
        }

        this.getTax = function(){

            return +parseFloat(((this.getSubTotal()/100) * this.getCart().tax )).toFixed(2);;
        }

        this.setCart = function (cart) {
            this.$cart = cart;
        }

        this.getCart = function(){
            return this.$cart;
        }

        this.getItems = function(){
            return this.getCart().items;
        }

        this.totalItems = function () {
            return this.getCart().items.length;
        }

        this.getSubTotal = function(){
            var total = 0;
            angular.forEach(this.getCart().items, function (item) {
                total += item.getTotal();
            });
            return total;
        }

        this.totalCost= function () {
            return +parseFloat(this.getSubTotal() + this.getShipping() + this.getTax()).toFixed(2);
        }

        this.removeItem = function (index) {
            this.$cart.items.splice(index, 1);
            $rootScope.$broadcast('ngCart:itemRemoved', {});
            $rootScope.$broadcast('ngCart:change', {});

        }

        this.empty = function () {
            this.$cart.items = [];
            localStorage.removeItem('cart');
        }

        this.toObject = function() {

            var items = [];
            angular.forEach(this.getItems(), function(item){
                items.push (item.toObject());
            });

            return {
                items:items,
                shipping: this.getShipping(),
                tax: this.getTax(),
                subTotal: this.getSubTotal(),
                totalCost: this.totalCost()
            }
        };

        this.$restore = function(storedCart){
            var _self = this;
            _self.init();
            _self.$cart.shipping = storedCart.shipping;
            _self.$cart.tax = storedCart.tax;

            angular.forEach(storedCart.items, function (item) {
                _self.$cart.items.push(new ngCartItem(item._id,  item._name, item._price, item._quantity, item._data));
            });
            this.$save();
        }

        this.$save = function () {
            return store.set('cart', JSON.stringify(this.getCart()));
        }

    }])

    .factory('ngCartItem', ['$rootScope', 'store', function ($rootScope, store) {

        var item = function (id, name, price, quantity, data) {
            this.setId(id);
            this.setName(name);
            this.setPrice(price);
            this.setQuantity(quantity);
            this.setData(data);
        };

        item.prototype.setId = function(id){
            if (id)  this._id = id;
            else {
                console.error('An ID must be provided');
            }
        }

        item.prototype.getId = function(){
            return this._id;
        }

        item.prototype.setName = function(name){
            if (name)  this._name = name;
            else {
                console.error('A name must be provided');
            }
        }
        item.prototype.getName = function(){
            return this._name;
        }

        item.prototype.setPrice = function(price){
            var price = parseFloat(price);
            if (price) {
                if (price <= 0) {
                    console.error('A price must be over 0');
                }
                this._price = (price);
            } else {
                console.error('A price must be provided');
            }
        }
        item.prototype.getPrice = function(){
            return this._price;
        }

        item.prototype.setQuantity = function(quantity, relative){

            var quantity = parseInt(quantity);
            if (quantity % 1 === 0){
                if (relative === true){
                    this._quantity  += quantity;
                } else {
                    this._quantity = quantity;
                }
                if (this._quantity < 1) this._quantity = 1;

            } else {
                this._quantity = 1;
                console.info('Quantity must be an integer and was defaulted to 1');
            }
            $rootScope.$broadcast('ngCart:change', {});

        }

        item.prototype.getQuantity = function(){
            return this._quantity;
        }

        item.prototype.setData = function(data){
            if (data) this._data = data;
        }

        item.prototype.getData = function(){
            if (this._data) return this._data;
            else console.info('This item has no data');
        }

        item.prototype.getTotal = function(){
            return +parseFloat(this.getQuantity() * this.getPrice()).toFixed(2);
        }

        item.prototype.toObject = function() {
            return {
                id: this.getId(),
                name: this.getName(),
                price: this.getPrice(),
                quantity: this.getQuantity(),
                data: this.getData(),
                total: this.getTotal()
            }
        };

        return item;

    }])

    .service('store', ['$window', function ($window) {

        return {

            get: function (key) {
                if ($window.localStorage [key]) {
                    var cart = angular.fromJson($window.localStorage [key]);
                    return JSON.parse(cart);
                }
                return false;

            },

            set: function (key, val) {

                if (val === undefined) {
                    $window.localStorage .removeItem(key);
                } else {
                    $window.localStorage [key] = angular.toJson(val);
                }
                return $window.localStorage [key];
            }
        }
    }])

    .controller('CartController',['$scope', 'ngCart', function($scope, ngCart) {
        $scope.ngCart = ngCart;
    }])

    .value('version', '0.0.1-rc.2');<commit_msg>What lint wants...<commit_after>'use strict';

angular.module('ngCart', ['ngCart.directives'])

    .config([function () {

    }])

    .provider('$ngCart', function () {
        this.$get = function () {
        };
    })

    .run(['$rootScope', 'ngCart','ngCartItem', 'store', function ($rootScope, ngCart, ngCartItem, store) {

        $rootScope.$on('ngCart:change', function(){
            ngCart.$save();
        });

        if (angular.isObject(store.get('cart'))) {
            ngCart.$restore(store.get('cart'));

        } else {
            ngCart.init();
        }

    }])

    .service('ngCart', ['$rootScope', 'ngCartItem', 'store', function ($rootScope, ngCartItem, store) {

        this.init = function(){
            this.$cart = {
                shipping : null,
                tax : null,
                items : []
            };
        };

        this.addItem = function (id, name, price, quantity, data) {

            var inCart = this.getItemById(id);

            if (typeof inCart === 'object'){
                //Update quantity of an item if it's already in the cart
                inCart.setQuantity(quantity, false);
            } else {
                var newItem = new ngCartItem(id, name, price, quantity, data);
                this.$cart.items.push(newItem);
                $rootScope.$broadcast('ngCart:itemAdded', newItem);
            }

            $rootScope.$broadcast('ngCart:change', {});
        };

        this.getItemById = function (itemId) {
            var items = this.getCart().items;
            var build = false;

            angular.forEach(items, function (item) {
                if  (item.getId() === itemId) {
                    build = item;
                }
            });
            return build;
        };

        this.setShipping = function(shipping){
            this.$cart.shipping = shipping;
        };

        this.getShipping = function(){
            if (this.getCart().items.length == 0) return 0;
            return  this.getCart().shipping;
        };

        this.setTax = function(tax){
            this.$cart.tax = tax;
        };

        this.getTax = function(){

            return +parseFloat(((this.getSubTotal()/100) * this.getCart().tax )).toFixed(2);
        };

        this.setCart = function (cart) {
            this.$cart = cart;
        };

        this.getCart = function(){
            return this.$cart;
        };

        this.getItems = function(){
            return this.getCart().items;
        };

        this.totalItems = function () {
            return this.getCart().items.length;
        };

        this.getSubTotal = function(){
            var total = 0;
            angular.forEach(this.getCart().items, function (item) {
                total += item.getTotal();
            });
            return total;
        };

        this.totalCost= function () {
            return +parseFloat(this.getSubTotal() + this.getShipping() + this.getTax()).toFixed(2);
        };

        this.removeItem = function (index) {
            this.$cart.items.splice(index, 1);
            $rootScope.$broadcast('ngCart:itemRemoved', {});
            $rootScope.$broadcast('ngCart:change', {});

        };

        this.empty = function () {
            this.$cart.items = [];
            localStorage.removeItem('cart');
        };

        this.toObject = function() {

            var items = [];
            angular.forEach(this.getItems(), function(item){
                items.push (item.toObject());
            });

            return {
                items:items,
                shipping: this.getShipping(),
                tax: this.getTax(),
                subTotal: this.getSubTotal(),
                totalCost: this.totalCost()
            }
        };

        this.$restore = function(storedCart){
            var _self = this;
            _self.init();
            _self.$cart.shipping = storedCart.shipping;
            _self.$cart.tax = storedCart.tax;

            angular.forEach(storedCart.items, function (item) {
                _self.$cart.items.push(new ngCartItem(item._id,  item._name, item._price, item._quantity, item._data));
            });
            this.$save();
        };

        this.$save = function () {
            return store.set('cart', JSON.stringify(this.getCart()));
        }

    }])

    .factory('ngCartItem', ['$rootScope', function ($rootScope) {

        var item = function (id, name, price, quantity, data) {
            this.setId(id);
            this.setName(name);
            this.setPrice(price);
            this.setQuantity(quantity);
            this.setData(data);
        };

        item.prototype.setId = function(id){
            if (id)  this._id = id;
            else {
                console.error('An ID must be provided');
            }
        };

        item.prototype.getId = function(){
            return this._id;
        };

        item.prototype.setName = function(name){
            if (name)  this._name = name;
            else {
                console.error('A name must be provided');
            }
        };
        item.prototype.getName = function(){
            return this._name;
        };

        item.prototype.setPrice = function(price){
            var priceFloat = parseFloat(price);
            if (priceFloat) {
                if (priceFloat <= 0) {
                    console.error('A price must be over 0');
                } else {
                    this._price = (priceFloat);
                }
            } else {
                console.error('A price must be provided');
            }
        };
        item.prototype.getPrice = function(){
            return this._price;
        };

        item.prototype.setQuantity = function(quantity, relative){

            var quantityInt = parseInt(quantity);
            if (quantityInt % 1 === 0){
                if (relative === true){
                    this._quantity  += quantityInt;
                } else {
                    this._quantity = quantityInt;
                }
                if (this._quantity < 1) this._quantity = 1;

            } else {
                this._quantity = 1;
                console.info('Quantity must be an integer and was defaulted to 1');
            }
            $rootScope.$broadcast('ngCart:change', {});

        };

        item.prototype.getQuantity = function(){
            return this._quantity;
        };

        item.prototype.setData = function(data){
            if (data) this._data = data;
        };

        item.prototype.getData = function(){
            if (this._data) return this._data;
            else console.info('This item has no data');
        };

        item.prototype.getTotal = function(){
            return +parseFloat(this.getQuantity() * this.getPrice()).toFixed(2);
        };

        item.prototype.toObject = function() {
            return {
                id: this.getId(),
                name: this.getName(),
                price: this.getPrice(),
                quantity: this.getQuantity(),
                data: this.getData(),
                total: this.getTotal()
            }
        };

        return item;

    }])

    .service('store', ['$window', function ($window) {

        return {

            get: function (key) {
                if ($window.localStorage [key]) {
                    var cart = angular.fromJson($window.localStorage [key]);
                    return JSON.parse(cart);
                }
                return false;

            },

            set: function (key, val) {

                if (val === undefined) {
                    $window.localStorage .removeItem(key);
                } else {
                    $window.localStorage [key] = angular.toJson(val);
                }
                return $window.localStorage [key];
            }
        }
    }])

    .controller('CartController',['$scope', 'ngCart', function($scope, ngCart) {
        $scope.ngCart = ngCart;
    }])

    .value('version', '0.0.1-rc.2');<|endoftext|>


<commit_before>// ==UserScript==
// @name pcbeta ads Remover
// @namespace https://code.google.com/p/adblock-chinalist/
// @author Gythialy
// @version 1.0.2
// @description Remove bbs.pcbeta.com ads for ChinaList
// @homepage https://code.google.com/p/adblock-chinalist/
// @updateURL https://adblock-chinalist.googlecode.com/svn/trunk/scripts/remove_ads_for_pcbeta.user.js
// @include http://bbs.pcbeta.com/*
// ==/UserScript==
(function() {
	var DEBUG = 0;
	function log(message) {
		if (DEBUG && GM_log) {
			GM_log(message);
		}
	}

	function x(xpath, parent, type, result) {
		return document.evaluate(xpath, parent || document, null, type || XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, result);
	}

	function remove(elm) {
		if (elm.snapshotItem) {
			for ( var i = 0; i < elm.snapshotLength; i++) {
				remove(elm.snapshotItem(i));
			}
		} else if (elm[0]) {
			while (elm[0]) {
				remove(elm[0]);
			}
		} else {
			elm.parentNode.removeChild(elm);
		}
	}

	var t = x('//div[@id="wp"]/div[@style]');
	for ( var i = 0; i < t.snapshotLength; i++) {
		var node = t.snapshotItem(i);
		log(node.style.height);
		if (node.style.height === '437px') {
			node.style.height = 'inherit';
			break;
		}
	}

	remove(x('//div[@style="padding:0 10px 10px;background:#d0dae4;"]/div[contains(@style,"height:90px")]'));
	remove(x('//div[contains(@style,"height:170px")]'));
})();<commit_msg>update GM/Scriptish script for pcbeta.com<commit_after>// ==UserScript==
// @name pcbeta ads Remover
// @namespace https://code.google.com/p/adblock-chinalist/
// @author Gythialy
// @version 1.0.3
// @description Remove bbs.pcbeta.com ads for ChinaList
// @homepage https://code.google.com/p/adblock-chinalist/
// @updateURL https://adblock-chinalist.googlecode.com/svn/trunk/scripts/remove_ads_for_pcbeta.user.js
// @include http://bbs.pcbeta.com/*
// ==/UserScript==
(function() {
	var DEBUG = 0;
	function log(message) {
		if (DEBUG && GM_log) {
			GM_log(message);
		}
	}

	function x(xpath, parent, type, result) {
		return document.evaluate(xpath, parent || document, null, type || XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, result);
	}

	function remove(elm) {
		if (elm.snapshotItem) {
			for ( var i = 0; i < elm.snapshotLength; i++) {
				remove(elm.snapshotItem(i));
			}
		} else if (elm[0]) {
			while (elm[0]) {
				remove(elm[0]);
			}
		} else {
			elm.parentNode.removeChild(elm);
		}
	}

	var t = x('//div[@id="wp"]/div[@style]');
	for ( var i = 0; i < t.snapshotLength; i++) {
		var node = t.snapshotItem(i);
		log(node.style.height);
		if (node.style.height === '437px') {
			node.style.height = 'inherit';
			break;
		}
	}

	remove(x('//div[@style="padding:0 10px 10px;background:#d0dae4;"]/div[contains(@style,"height:90px")]'));
	remove(x('//div[contains(@style,"height:170px")]'));
	remove(x('.//div[@id="sitefocus"][@class="focus"]'));
})();<|endoftext|>


<commit_before><commit_msg>compile<commit_after><|endoftext|>


<commit_before>import {Meteor} from 'meteor/meteor';
import {DocRoba} from '../../api/doc_roba.js';
var AdmZip = require('adm-zip'),
    fs = require('fs'),
    path = require('path');

// Full collections
import {Actions}                from '../../api/action/action.js';
import {DataStores}             from '../../api/datastore/datastore.js';
import {DataStoreFields}        from '../../api/datastore/datastore_field.js';
import {DataStoreRows}          from '../../api/datastore/datastore_row.js';
import {DriverCommands}         from '../../api/driver_command/driver_command.js';
import {Nodes}                  from '../../api/node/node.js';
import {Projects}               from '../../api/project/project.js';
import {ProjectVersions}        from '../../api/project/project_version.js';
import {ReferenceDocs}          from '../../api/reference_doc/reference_doc.js';
import {Screenshots}            from '../../api/screenshot/screenshot.js';
import {Servers}                from '../../api/test_server/server.js';
import {TestAgents}             from '../../api/test_agent/test_agent.js';
import {TestCases}              from '../../api/test_case/test_case.js';
import {TestCaseRoles}          from '../../api/test_case/test_case_role.js';
import {TestCaseSteps}          from '../../api/test_case/test_case_step.js';
import {TestGroups}             from '../../api/test_case/test_group.js';
import {TestResults}            from '../../api/test_result/test_result.js';
import {TestResultRoles}        from '../../api/test_result/test_result_role.js';
import {TestResultSteps}        from '../../api/test_result/test_result_step.js';
import {TestRuns}               from '../../api/test_run/test_run.js';
import {TestRunStages}          from '../../api/test_run/test_run_stage.js';
import {TestPlans}              from '../../api/test_plan/test_plan.js';
import {TestPlanItems}          from '../../api/test_plan/test_plan_item.js';
import {TestSystems}            from '../../api/test_system/test_system.js';
import {Users}                  from '../../api/users/users.js';

// Partial collection export
import {LogMessages}            from '../../api/log_message/log_message.js';

/**
 * On startup, check to see if the demo data should be loaded
 */
Meteor.startup(() =>{
  if (Projects.find().count() == 0 && Nodes.find().count() == 0) {
    console.info("=====================================");
    console.info("No data found, executing data fixture...");
    DemoDataHandler.importData();
    console.info("...Fixture complete");
    console.info("=====================================");
  }

  /**
   * Provide a means for testing the export
   */
  Meteor.methods({
    /**
     * exportDemoData
     */
    exportDemoData() {
      console.info("exportDemoData called");
      DemoDataHandler.exportData();
    }
  });
});

/**
 * Singleton to handle import export tasks
 */
var collectionList = {
      Actions: Actions,
      DataStores: DataStores,
      DataStoreFields: DataStoreFields,
      DataStoreRows: DataStoreRows,
      DriverCommands: DriverCommands,
      LogMessages: LogMessages,
      Nodes: Nodes,
      Projects: Projects,
      ProjectVersions: ProjectVersions,
      ReferenceDocs: ReferenceDocs, // Figure out how to best store and import the files
      Screenshots: Screenshots, // Figure out how to best store and import the files
      Servers: Servers,
      TestAgents: TestAgents,
      TestCases: TestCases,
      TestCaseRoles: TestCaseRoles,
      TestCaseSteps: TestCaseSteps,
      TestGroups: TestGroups,
      TestResults: TestResults,
      TestResultRoles: TestResultRoles,
      TestResultSteps: TestResultSteps,
      TestRuns: TestRuns,
      TestRunStages: TestRunStages,
      TestPlans: TestPlans,
      TestPlanItems: TestPlanItems,
      TestSystems: TestSystems,
      Users: Users
    },
    DemoDataHandler = {
  // The location the files will be stored
  dataPath: '../demo_data',
  fileRegex: /\.json$|\.json\.zip/,
  encoding: 'utf8',

  // These full collections will be exported

  /**
   * Export all demo data
   */
  exportData() {
    console.info("DemoDataHandler.exportData");
    var handler = this,
        dataDir = path.join(DocRoba.rootPath, handler.dataPath);

    console.debug("DemoDataHandler.exportData data folder: " + dataDir);

    // remove log messages and users from the collections list because they'll be handled differently
    delete collectionList.LogMessages;
    delete collectionList.Users;

    // clear out the directory if it exists
    if(fs.existsSync(dataDir)){
      console.info("DemoDataHandler.exportData removing existing files");
      fs.readdirSync(dataDir)
          .filter((filepath) => {
            return path.basename(filepath).match(handler.fileRegex) != null
          })
          .forEach((filepath) => {
            console.debug("Removing file: " + filepath);
            fs.unlinkSync(path.join(dataDir, filepath));
          });
    } else {
      // create the folder
      console.info("DemoDataHandler.exportData creating data directory: " + dataDir);
      fs.mkdirSync(dataDir);
    }

    // export the collections
    _.keys(collectionList).forEach((collectionName) => {
      // open a file for the collection
      var collectionFilePath = path.join(dataDir, collectionName + ".json"),
          cursor = collectionList[collectionName].find({});
      handler.exportRecords(cursor, collectionFilePath, collectionName);
    });

    // export the users
    var usersFilePath = path.join(dataDir, "Users.json");
    handler.exportRecords(Meteor.users.find({}, {fields: {
      "services.resume": 0,
      "services.singleUse": 0
    }}), usersFilePath, "Users");

    // export log messages for test results
    var logMessagePath = path.join(dataDir, "LogMessages.json");
    handler.exportRecords(LogMessages.find({testResultRoleId: {$exists: 1}}), logMessagePath, "LogMessages");

    console.info("DemoDataHandler.exportData complete");
  },

  /**
   * Export a particular collection
   * @param cursor          Collection cursor pointing to records to export
   * @param jsonFilePath    Path to the json file to export to
   * @param collectionName  The name of the collection to tag the data with
   */
  exportRecords(cursor, jsonFilePath, collectionName) {
    var handler = this,
        output = "",
        recordCount = cursor.count();

    console.info("DemoDataHandler.exportRecords exporting " + collectionName + " (" + recordCount + " records)");

    // start the JSON file
    output += "[\n";
    cursor.forEach((record, i) => {
      output += JSON.stringify(record) + (i < recordCount - 1 ? ",\n" : "\n");
    });
    output += "]\n";

    // Zip it
    var zipFile = new AdmZip();
    zipFile.addFile(path.basename(jsonFilePath), new Buffer(output, handler.encoding), collectionName);
    zipFile.writeZip(jsonFilePath + ".zip");
  },

  /**
   * Import all demo data
   */
  importData() {
    console.info("DemoDataHandler.importData");
    var handler = this,
        dataDir = path.join(DocRoba.rootPath, handler.dataPath);

    console.debug("DemoDataHandler.importData data folder: " + dataDir);

    if(fs.existsSync(dataDir)){
      console.debug("DemoDataHandler.importData scanning data files");
      fs.readdirSync(dataDir)
          .filter((filepath) => {
            return path.basename(filepath).match(handler.fileRegex) != null
          })
          .forEach((filepath) => {
            handler.importRecords(path.join(dataDir, filepath));
          });
    } else {
      // create the folder
      console.error("DemoDataHandler.importData data directory didn't exist: " + dataDir);
    }

    console.info("DemoDataHandler.importData complete");
  },

  /**
   * Export a particular collection
   * @param filePath Path to the zip file to import
   */
  importRecords(filePath) {
    var handler = this,
        data, collectionName;

    console.info("DemoDataHandler.importRecords importing " + path.basename(filePath) );

    // Unzip it if needed
    if(filePath.match(/\.zip$/)){
      var zipFile = new AdmZip(filePath),
          zipEntries = zipFile.getEntries();
      if(zipEntries){
        zipEntries.forEach((zipEntry) => {
          if(zipEntry.entryName && zipEntry.entryName.match(/\.json$/)){
            console.debug("DemoDataHandler.importRecords reading file " + zipEntry.entryName );

            var input = zipFile.readAsText(zipEntry, handler.encoding);
            try {
              data = JSON.parse(input);
              collectionName = zipEntry.entryName.replace(/\.json$/i, "");
              console.debug("DemoDataHandler.importRecords found " + data.length + " records for " + collectionName );
            } catch (e) {
              console.error("DemoDataHandler.importRecords JSON parse failed: " + e.toString());
            }
          }
        });
      } else {
        console.error("Zip file didn't contain any zip entries: " + filePath);
      }
    } else {
      // read a raw json file
      try {
        var input = fs.readFileSync(filePath, handler.encoding);
        data = JSON.parse(input);
        collectionName = path.basename(filePath).replace(/\.json$/i, "");
      } catch (e) {
        console.error("JSON file didn't contain any entries: " + filePath);
      }
    }

    if(data && collectionName && collectionList[collectionName]){
      data.forEach((record) => {
        // Use the direct method to circumvent the collection hooks
        if(collectionList[collectionName] && collectionList[collectionName].direct){
          collectionList[collectionName].direct.insert(record);
        } else {
          // CollectionFS doesn't have hooks and I have to figure out how to encode the file data
          //collectionList[collectionName].insert(record);

        }
      });
    } else {
      console.error("DemoDataHandler.importRecords failed: collection [" + collectionName + "] not found");
    }
  }
};<commit_msg>Updated data fixture to include Code Modules, replaced Demo terminology with Fixture<commit_after>import {Meteor} from 'meteor/meteor';
import {DocRoba} from '../../api/doc_roba.js';
var AdmZip = require('adm-zip'),
    fs = require('fs'),
    path = require('path');

// Full collections
import {Actions}                  from '../../api/action/action.js';
import {CodeModules}              from '../../api/code_module/code_module.js';
import {CodeModuleFunctions}      from '../../api/code_module/code_module_function.js';
import {CodeModuleFunctionParams} from '../../api/code_module/code_module_function_param.js';
import {DataStores}               from '../../api/datastore/datastore.js';
import {DataStoreFields}          from '../../api/datastore/datastore_field.js';
import {DataStoreRows}            from '../../api/datastore/datastore_row.js';
import {DriverCommands}           from '../../api/driver_command/driver_command.js';
import {Nodes}                    from '../../api/node/node.js';
import {Projects}                 from '../../api/project/project.js';
import {ProjectVersions}          from '../../api/project/project_version.js';
import {ReferenceDocs}            from '../../api/reference_doc/reference_doc.js';
import {Screenshots}              from '../../api/screenshot/screenshot.js';
import {Servers}                  from '../../api/test_server/server.js';
import {TestAgents}               from '../../api/test_agent/test_agent.js';
import {TestCases}                from '../../api/test_case/test_case.js';
import {TestCaseRoles}            from '../../api/test_case/test_case_role.js';
import {TestCaseSteps}            from '../../api/test_case/test_case_step.js';
import {TestGroups}               from '../../api/test_case/test_group.js';
import {TestResults}              from '../../api/test_result/test_result.js';
import {TestResultRoles}          from '../../api/test_result/test_result_role.js';
import {TestResultSteps}          from '../../api/test_result/test_result_step.js';
import {TestRuns}                 from '../../api/test_run/test_run.js';
import {TestRunStages}            from '../../api/test_run/test_run_stage.js';
import {TestPlans}                from '../../api/test_plan/test_plan.js';
import {TestPlanItems}            from '../../api/test_plan/test_plan_item.js';
import {TestSystems}              from '../../api/test_system/test_system.js';
import {Users}                    from '../../api/users/users.js';

// Partial collection export
import {LogMessages}            from '../../api/log_message/log_message.js';

/**
 * On startup, check to see if the fixture data should be loaded
 */
Meteor.startup(() =>{
  if (Projects.find().count() == 0 && Nodes.find().count() == 0) {
    console.info("=====================================");
    console.info("No data found, executing data fixture...");
    FixtureDataHandler.importData();
    console.info("...Fixture complete");
    console.info("=====================================");
  }

  /**
   * Provide a means for testing the export
   */
  Meteor.methods({
    /**
     * exportFixtureData
     */
    exportFixtureData() {
      console.info("exportFixtureData called");
      FixtureDataHandler.exportData();
    }
  });
});

/**
 * Singleton to handle import export tasks
 */
var collectionList = {
      Actions: Actions,
      CodeModules: CodeModules,
      CodeModuleFunctions: CodeModuleFunctions,
      CodeModuleFunctionParams: CodeModuleFunctionParams,
      DataStores: DataStores,
      DataStoreFields: DataStoreFields,
      DataStoreRows: DataStoreRows,
      DriverCommands: DriverCommands,
      LogMessages: LogMessages,
      Nodes: Nodes,
      Projects: Projects,
      ProjectVersions: ProjectVersions,
      ReferenceDocs: ReferenceDocs, // Figure out how to best store and import the files
      Screenshots: Screenshots, // Figure out how to best store and import the files
      Servers: Servers,
      TestAgents: TestAgents,
      TestCases: TestCases,
      TestCaseRoles: TestCaseRoles,
      TestCaseSteps: TestCaseSteps,
      TestGroups: TestGroups,
      TestResults: TestResults,
      TestResultRoles: TestResultRoles,
      TestResultSteps: TestResultSteps,
      TestRuns: TestRuns,
      TestRunStages: TestRunStages,
      TestPlans: TestPlans,
      TestPlanItems: TestPlanItems,
      TestSystems: TestSystems,
      Users: Users
    },
    FixtureDataHandler = {
      // The location the files will be stored
      dataPath: '../fixture-data',
      fileRegex: /\.json$|\.json\.zip/,
      encoding: 'utf8',

      // These full collections will be exported

      /**
       * Export all fixture data
       */
      exportData() {
        console.info("FixtureDataHandler.exportData");
        var handler = this,
            dataDir = path.join(DocRoba.rootPath, handler.dataPath);

        console.debug("FixtureDataHandler.exportData data folder: " + dataDir);

        // remove log messages and users from the collections list because they'll be handled differently
        delete collectionList.LogMessages;
        delete collectionList.Users;

        // clear out the directory if it exists
        if(fs.existsSync(dataDir)){
          console.info("FixtureDataHandler.exportData removing existing files");
          fs.readdirSync(dataDir)
              .filter((filepath) => {
                return path.basename(filepath).match(handler.fileRegex) != null
              })
              .forEach((filepath) => {
                console.debug("Removing file: " + filepath);
                fs.unlinkSync(path.join(dataDir, filepath));
              });
        } else {
          // create the folder
          console.info("FixtureDataHandler.exportData creating data directory: " + dataDir);
          fs.mkdirSync(dataDir);
        }

        // export the collections
        _.keys(collectionList).forEach((collectionName) => {
          // open a file for the collection
          var collectionFilePath = path.join(dataDir, collectionName + ".json"),
              cursor = collectionList[collectionName].find({});
          handler.exportRecords(cursor, collectionFilePath, collectionName);
        });

        // export the users
        var usersFilePath = path.join(dataDir, "Users.json");
        handler.exportRecords(Meteor.users.find({}, {fields: {
          "services.resume": 0,
          "services.singleUse": 0
        }}), usersFilePath, "Users");

        // export log messages for test results
        var logMessagePath = path.join(dataDir, "LogMessages.json");
        handler.exportRecords(LogMessages.find({testResultRoleId: {$exists: 1}}), logMessagePath, "LogMessages");

        console.info("FixtureDataHandler.exportData complete");
      },

      /**
       * Export a particular collection
       * @param cursor          Collection cursor pointing to records to export
       * @param jsonFilePath    Path to the json file to export to
       * @param collectionName  The name of the collection to tag the data with
       */
      exportRecords(cursor, jsonFilePath, collectionName) {
        var handler = this,
            output = "",
            recordCount = cursor.count();

        console.info("FixtureDataHandler.exportRecords exporting " + collectionName + " (" + recordCount + " records)");

        // start the JSON file
        output += "[\n";
        cursor.forEach((record, i) => {
          output += JSON.stringify(record) + (i < recordCount - 1 ? ",\n" : "\n");
        });
        output += "]\n";

        // Zip it
        var zipFile = new AdmZip();
        zipFile.addFile(path.basename(jsonFilePath), new Buffer(output, handler.encoding), collectionName);
        zipFile.writeZip(jsonFilePath + ".zip");
      },

      /**
       * Import all fixture data
       */
      importData() {
        console.info("FixtureDataHandler.importData");
        var handler = this,
            dataDir = path.join(DocRoba.rootPath, handler.dataPath);

        console.debug("FixtureDataHandler.importData data folder: " + dataDir);

        if(fs.existsSync(dataDir)){
          console.debug("FixtureDataHandler.importData scanning data files");
          fs.readdirSync(dataDir)
              .filter((filepath) => {
                return path.basename(filepath).match(handler.fileRegex) != null
              })
              .forEach((filepath) => {
                handler.importRecords(path.join(dataDir, filepath));
              });
        } else {
          // create the folder
          console.error("FixtureDataHandler.importData data directory didn't exist: " + dataDir);
        }

        console.info("FixtureDataHandler.importData complete");
      },

      /**
       * Export a particular collection
       * @param filePath Path to the zip file to import
       */
      importRecords(filePath) {
        var handler = this,
            data, collectionName;

        console.info("FixtureDataHandler.importRecords importing " + path.basename(filePath) );

        // Unzip it if needed
        if(filePath.match(/\.zip$/)){
          var zipFile = new AdmZip(filePath),
              zipEntries = zipFile.getEntries();
          if(zipEntries){
            zipEntries.forEach((zipEntry) => {
              if(zipEntry.entryName && zipEntry.entryName.match(/\.json$/)){
                console.debug("FixtureDataHandler.importRecords reading file " + zipEntry.entryName );

                var input = zipFile.readAsText(zipEntry, handler.encoding);
                try {
                  data = JSON.parse(input);
                  collectionName = zipEntry.entryName.replace(/\.json$/i, "");
                  console.debug("FixtureDataHandler.importRecords found " + data.length + " records for " + collectionName );
                } catch (e) {
                  console.error("FixtureDataHandler.importRecords JSON parse failed: " + e.toString());
                }
              }
            });
          } else {
            console.error("Zip file didn't contain any zip entries: " + filePath);
          }
        } else {
          // read a raw json file
          try {
            var input = fs.readFileSync(filePath, handler.encoding);
            data = JSON.parse(input);
            collectionName = path.basename(filePath).replace(/\.json$/i, "");
          } catch (e) {
            console.error("JSON file didn't contain any entries: " + filePath);
          }
        }

        if(data && collectionName && collectionList[collectionName]){
          data.forEach((record) => {
            // Use the direct method to circumvent the collection hooks
            if(collectionList[collectionName] && collectionList[collectionName].direct){
              collectionList[collectionName].direct.insert(record);
            } else {
              // CollectionFS doesn't have hooks and I have to figure out how to encode the file data
              //collectionList[collectionName].insert(record);

            }
          });
        } else {
          console.error("FixtureDataHandler.importRecords failed: collection [" + collectionName + "] not found");
        }
      }
    };<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import { ScrollView, Text } from 'react-native';
import { Panel, Collapse } from '../zarm';

export default class Page extends PureComponent {
  render() {
    return (
      <ScrollView>
        <Panel title="基本用法">
          <Collapse>
            <Collapse.Item itemKey="1" title="50元套餐" onItemChange={(key) => console.log(key)}>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item itemKey={2} title="100元套餐" onItemChange={(key) => console.log(key)}>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item itemKey="3" title="200元套餐" onItemChange={(key) => console.log(key)}>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
            </Collapse.Item>
          </Collapse>
        </Panel>

        <Panel title="手风琴模式">
          <Collapse animated>
            <Collapse.Item itemKey="test1" title="50元套餐">
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item itemKey="test2" title="100元套餐">
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item itemKey="test3" title="200元套餐">
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
            </Collapse.Item>
          </Collapse>
        </Panel>

        <Panel title="默认展开项">
          <Collapse animated activeKey={['test1', 'test2']}>
            <Collapse.Item itemKey="test1" title="50元套餐">
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item itemKey="test2" title="100元套餐">
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item itemKey="test3" title="200元套餐">
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
            </Collapse.Item>
          </Collapse>
        </Panel>

        <Panel title="允许展开多项">
          <Collapse animated multiple defaultActiveKey={['test1', 'test3']}>
            <Collapse.Item itemKey="test1" title="50元套餐">
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item itemKey="test2" title="100元套餐">
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item itemKey="test3" title="200元套餐">
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
            </Collapse.Item>
          </Collapse>
        </Panel>

        <Panel title="禁用子项">
          <Collapse
            multiple
            activeKey={['test2']}
            onChange={(key) => console.log(key)}
          >
            <Collapse.Item itemKey="test1" title="50元套餐">
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item itemKey="test2" title="100元套餐" disabled>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item itemKey="test3" title="200元套餐" disabled>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
            </Collapse.Item>
          </Collapse>
        </Panel>
      </ScrollView>
    );
  }
}
<commit_msg>fix: react-native collapse demo<commit_after>import React, { PureComponent } from 'react';
import { ScrollView, Text } from 'react-native';
import { Panel, Collapse } from '../zarm';

export default class Page extends PureComponent {
  render() {
    return (
      <ScrollView>
        <Panel title="基本用法">
          <Collapse>
            <Collapse.Item key="1" title="50元套餐" onItemChange={(key) => console.log(key)}>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item key={2} title="100元套餐" onItemChange={(key) => console.log(key)}>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item key="3" title="200元套餐" onItemChange={(key) => console.log(key)}>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
            </Collapse.Item>
          </Collapse>
        </Panel>

        <Panel title="手风琴模式">
          <Collapse animated>
            <Collapse.Item key="test1" title="50元套餐">
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item key="test2" title="100元套餐">
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item key="test3" title="200元套餐">
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
            </Collapse.Item>
          </Collapse>
        </Panel>

        <Panel title="默认展开项">
          <Collapse animated activeKey={['test1', 'test2']}>
            <Collapse.Item key="test1" title="50元套餐">
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item key="test2" title="100元套餐">
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item key="test3" title="200元套餐">
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
            </Collapse.Item>
          </Collapse>
        </Panel>

        <Panel title="允许展开多项">
          <Collapse animated multiple defaultActiveKey={['test1', 'test3']}>
            <Collapse.Item key="test1" title="50元套餐">
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item key="test2" title="100元套餐">
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item key="test3" title="200元套餐">
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
            </Collapse.Item>
          </Collapse>
        </Panel>

        <Panel title="禁用子项">
          <Collapse
            multiple
            activeKey={['test2']}
            onChange={(key) => console.log(key)}
          >
            <Collapse.Item key="test1" title="50元套餐">
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
              <Text>我是50元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item key="test2" title="100元套餐" disabled>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
              <Text>我是100元套餐内容</Text>
            </Collapse.Item>
            <Collapse.Item key="test3" title="200元套餐" disabled>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
              <Text>我是200元套餐内容</Text>
            </Collapse.Item>
          </Collapse>
        </Panel>
      </ScrollView>
    );
  }
}
<|endoftext|>


<commit_before>'use strict';
(function() {
  var handler = null;

  $('footer').fadeIn();

  $(document).ready(function() {
    var progress = $('#progress');

    function showProgress() {
      progress.css('visibility', 'visible');
    }

    function hideProgress() {
      progress.css('visibility', 'hidden');
    }

    handler = StripeCheckout.configure({
      key: 'STRIPE_CHECKOUT_SECRET_KEY_PLEASE_REPLACE',
      image: 'https://stripe.com/img/documentation/checkout/marketplace.png',
      locale: 'auto', // TODO change to en_CA
      token: function(token) {
        showProgress();

        postPayment(
          {
            tokenId: token.id,
            paymentUsername: $('#username-input').val(),
          },
          function() {
            hideProgress();
            // Give important information and say thank you.
            $(notification).text("Thank you for registering! Your books will be renewed " +
              "every day for the next 4 months. Please note that others may still recall " +
              "the books that you have borrowed, and you may have to return them eventually.\n" +
              "Soon you should receive an email containing a unique link from which you can " +
              "see your loan status.\nIf you have any questions, please email " +
              "mcgillrenewal@gmail.com."
            );
          },
          function() {
            hideProgress();
            clearValidationErrors();
            $(validationErrorsNode).append(
              '
 An error occurred processing your payment. Don\'t worry, nothing has been charged to your card!
'
            );
          }
        );
      },
    });

    $('#submit').click(function() {
      showProgress();
      registerUser(
        function(profile) {
          postCheckUser(
            $('#username-input').val(),
            function(exists) {
              if(exists) {
                hideProgress();
                clearValidationErrors();
                $(notification).text('You are already subscribed to the service!');
              }
              else {
                hideProgress();
                handler.open({
                  name: 'McGill Book Renewal',
                  description: '4 months of daily book renewals',
                  currency: 'cad',
                  amount: 100,
                });
              }
            },
            function(error) {
              hideProgress();
              $(validationErrorsNode).append(
                '
 Your user does not exist... somehow.
'
              );
            }
          );
        },
        function(errors) {
          hideProgress();
          clearValidationErrors();
          $(validationErrorsNode).append(
            '
 We could not verify your account. Please double-check your username and password.
'
          );
        }
      );
    });

    window.addEventListener('popstate', function() {
      handler.close();
    });

    $('#signup-button').click(function() {
        $('#signup-button').hide();
        console.log('hi');
        $('#signup-card').fadeIn(400, function() {console.log('done');});
    });

    function validateEmail(email) {
      var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
      return re.test(email);
    }

    function validateUsername() {
      var username = $('#username-input');
      return username.val() && validateEmail(username.val());
    }

    function validatePassword() {
      return $('#password-input').val();
    }

    var validationErrorsNode = document.getElementById('validation-errors');
    var notification = document.getElementById('notification');

    function clearValidationErrors() {
      while(validationErrorsNode.hasChildNodes()) {
        validationErrorsNode.removeChild(validationErrorsNode.lastChild);
        console.log('Removed error.');
      }
    }

    function validateInputs() {
      clearValidationErrors();
      var hasErrors = false;
      if(!validateUsername()) {
        $(validationErrorsNode).append(
          $("
 Please provide a valid McGill email address.
")
        );
        hasErrors = true;
        console.log('added username error');
      }
      if(!validatePassword()) {
        $(validationErrorsNode).append(
          $("
 Please provide the password.
")
        );
        hasErrors = true;
        console.log('added password error');
      }
      return !hasErrors;
    }

    function registerUser(success, fail) {
      if(!validateInputs()) return;

      $('#username-and-password-input input').prop('disabled', true);
      console.log('inputs are disabled');

      postRegister(
        {
          registrantUsername: $('#username-input').val(),
          notificationEmail: '',
          pass: $('#password-input').val(),
          phoneNumber: '',
          trigger: 'onlyFailure'
        },
        function(profile) {
          $('#username-and-password-input input').prop('disabled', false);
          success(profile);
        },
        function(errors) {
          $('#username-and-password-input input').prop('disabled', false);
          fail(errors);
        }
      );
    }

    $('#password-input').on('keypress', function(e) {
      if (e.which == 13) {
        showProgress();

        registerUser(
          function(profile) {
            hideProgress();
            console.log('it worked');
            console.log(JSON.stringify(profile));
            constructTable(profile.detailedBookList);
          },
          function(errors) {
            hideProgress();
            clearValidationErrors();
            $(validationErrorsNode).append(
              $("
 An error occurred while fetching your books.
")
            );
            console.log('errors ' + JSON.stringify(errors));
          }
        );
      }
    });

    $('#see-my-books').click(function() {
      showProgress();

      registerUser(
        function(profile) {
          hideProgress();
          console.log('it worked');
          console.log(JSON.stringify(profile));
          constructTable(profile.detailedBookList);
        },
        function(errors) {
          hideProgress();
          clearValidationErrors();
          $(validationErrorsNode).append(
            $("
 An error occurred while fetching your books.
")
          );
          console.log('errors ' + JSON.stringify(errors));
        }
      );
    });

    var table = $('#table');

    function clearTable() {
      table.find('tbody tr').remove();
    }

    function constructTable(books) {
      clearTable();
      books.forEach(function(book) {
        var s = '	';
        var tr = $('

');
        tr.append($(s).text(book.description));
        tr.append($(s).text(book.library));
        tr.append($(s).text(book.accruingFine));
        tr.append($(s).text(book.dueDate + ' ' + book.dueHour));
        tr.append($(s).text(book.year));
        tr.append($(s).text(book.numberOfRenewals));
        table.children('tbody').append(tr);
      });
      table.show();
    }

    function onUnload() {
      $('#username-and-password-input input').prop('disabled', false);
    }

    window.addEventListener('unload', onUnload, false);

    $('#username-and-password-input input').prop('disabled', false);
  });
})();

// vim: shiftwidth=2 tw=79
<commit_msg>HE WASN'T READY<commit_after>'use strict';
(function() {
  var handler = null;

  $(document).ready(function() {
    $('footer').fadeIn();

    var progress = $('#progress');

    function showProgress() {
      progress.css('visibility', 'visible');
    }

    function hideProgress() {
      progress.css('visibility', 'hidden');
    }

    handler = StripeCheckout.configure({
      key: 'STRIPE_CHECKOUT_SECRET_KEY_PLEASE_REPLACE',
      image: 'https://stripe.com/img/documentation/checkout/marketplace.png',
      locale: 'auto', // TODO change to en_CA
      token: function(token) {
        showProgress();

        postPayment(
          {
            tokenId: token.id,
            paymentUsername: $('#username-input').val(),
          },
          function() {
            hideProgress();
            // Give important information and say thank you.
            $(notification).text("Thank you for registering! Your books will be renewed " +
              "every day for the next 4 months. Please note that others may still recall " +
              "the books that you have borrowed, and you may have to return them eventually.\n" +
              "Soon you should receive an email containing a unique link from which you can " +
              "see your loan status.\nIf you have any questions, please email " +
              "mcgillrenewal@gmail.com."
            );
          },
          function() {
            hideProgress();
            clearValidationErrors();
            $(validationErrorsNode).append(
              '
 An error occurred processing your payment. Don\'t worry, nothing has been charged to your card!
'
            );
          }
        );
      },
    });

    $('#submit').click(function() {
      showProgress();
      registerUser(
        function(profile) {
          postCheckUser(
            $('#username-input').val(),
            function(exists) {
              if(exists) {
                hideProgress();
                clearValidationErrors();
                $(notification).text('You are already subscribed to the service!');
              }
              else {
                hideProgress();
                handler.open({
                  name: 'McGill Book Renewal',
                  description: '4 months of daily book renewals',
                  currency: 'cad',
                  amount: 100,
                });
              }
            },
            function(error) {
              hideProgress();
              $(validationErrorsNode).append(
                '
 Your user does not exist... somehow.
'
              );
            }
          );
        },
        function(errors) {
          hideProgress();
          clearValidationErrors();
          $(validationErrorsNode).append(
            '
 We could not verify your account. Please double-check your username and password.
'
          );
        }
      );
    });

    window.addEventListener('popstate', function() {
      handler.close();
    });

    $('#signup-button').click(function() {
        $('#signup-button').hide();
        console.log('hi');
        $('#signup-card').fadeIn(400, function() {console.log('done');});
    });

    function validateEmail(email) {
      var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
      return re.test(email);
    }

    function validateUsername() {
      var username = $('#username-input');
      return username.val() && validateEmail(username.val());
    }

    function validatePassword() {
      return $('#password-input').val();
    }

    var validationErrorsNode = document.getElementById('validation-errors');
    var notification = document.getElementById('notification');

    function clearValidationErrors() {
      while(validationErrorsNode.hasChildNodes()) {
        validationErrorsNode.removeChild(validationErrorsNode.lastChild);
        console.log('Removed error.');
      }
    }

    function validateInputs() {
      clearValidationErrors();
      var hasErrors = false;
      if(!validateUsername()) {
        $(validationErrorsNode).append(
          $("
 Please provide a valid McGill email address.
")
        );
        hasErrors = true;
        console.log('added username error');
      }
      if(!validatePassword()) {
        $(validationErrorsNode).append(
          $("
 Please provide the password.
")
        );
        hasErrors = true;
        console.log('added password error');
      }
      return !hasErrors;
    }

    function registerUser(success, fail) {
      if(!validateInputs()) return;

      $('#username-and-password-input input').prop('disabled', true);
      console.log('inputs are disabled');

      postRegister(
        {
          registrantUsername: $('#username-input').val(),
          notificationEmail: '',
          pass: $('#password-input').val(),
          phoneNumber: '',
          trigger: 'onlyFailure'
        },
        function(profile) {
          $('#username-and-password-input input').prop('disabled', false);
          success(profile);
        },
        function(errors) {
          $('#username-and-password-input input').prop('disabled', false);
          fail(errors);
        }
      );
    }

    $('#password-input').on('keypress', function(e) {
      if (e.which == 13) {
        showProgress();

        registerUser(
          function(profile) {
            hideProgress();
            console.log('it worked');
            console.log(JSON.stringify(profile));
            constructTable(profile.detailedBookList);
          },
          function(errors) {
            hideProgress();
            clearValidationErrors();
            $(validationErrorsNode).append(
              $("
 An error occurred while fetching your books.
")
            );
            console.log('errors ' + JSON.stringify(errors));
          }
        );
      }
    });

    $('#see-my-books').click(function() {
      showProgress();

      registerUser(
        function(profile) {
          hideProgress();
          console.log('it worked');
          console.log(JSON.stringify(profile));
          constructTable(profile.detailedBookList);
        },
        function(errors) {
          hideProgress();
          clearValidationErrors();
          $(validationErrorsNode).append(
            $("
 An error occurred while fetching your books.
")
          );
          console.log('errors ' + JSON.stringify(errors));
        }
      );
    });

    var table = $('#table');

    function clearTable() {
      table.find('tbody tr').remove();
    }

    function constructTable(books) {
      clearTable();
      books.forEach(function(book) {
        var s = '	';
        var tr = $('

');
        tr.append($(s).text(book.description));
        tr.append($(s).text(book.library));
        tr.append($(s).text(book.accruingFine));
        tr.append($(s).text(book.dueDate + ' ' + book.dueHour));
        tr.append($(s).text(book.year));
        tr.append($(s).text(book.numberOfRenewals));
        table.children('tbody').append(tr);
      });
      table.show();
    }

    function onUnload() {
      $('#username-and-password-input input').prop('disabled', false);
    }

    window.addEventListener('unload', onUnload, false);

    $('#username-and-password-input input').prop('disabled', false);
  });
})();

// vim: shiftwidth=2 tw=79
<|endoftext|>


<commit_before>function autocomplete_source(elem) {
  return $('#search form').attr('action') + '?type=' + $('#search_type').val();
}

function add_header_field() {
  var count = $('#main form').data('add_header_filed_count');
  if (count) {
    ++count;
  } else {
    count = 1;
  }
  $('#main form').data('add_header_filed_count', count);

  var content = '
Name:Wert:
'
    + '<input type="text" name="add_header_key_count" size="15" id="header_key_count" /> : '
    + '<input type="text" name="add_header_val_count" size="35" id="header_val_count" />
';
  content = content.replace(/count/g, count);

  $('#add-header-field').before(content);
}

$(document).ready(function() {
  if (!window.location.hash) {
    var i = $('#main input[type=text]').first()
    var v = i.val();
    if (v && v.trim().length === 0) {
      i.focus();
    }
  }

  $('#spinner')
    .ajaxStart(function() {
        $(this).show();
    })
    .ajaxStop(function() {
        $(this).hide();
    });

  $('#search_query').autocomplete({
    source: autocomplete_source(),
    focus:  function(event, ui) {
      return false;
    },
    select: function(event, ui) {
      window.location.href = $(ui.item).val();
      return false;
    }
  });

  $('#search_type').change(function() {
    $('#search_query').autocomplete(
      'option', 'source', autocomplete_source()
    );
  });
});
<commit_msg>public/js/application.js: Fixed focusing first input field.<commit_after>function autocomplete_source(elem) {
  return $('#search form').attr('action') + '?type=' + $('#search_type').val();
}

function add_header_field() {
  var count = $('#main form').data('add_header_filed_count');
  if (count) {
    ++count;
  } else {
    count = 1;
  }
  $('#main form').data('add_header_filed_count', count);

  var content = '
Name:Wert:
'
    + '<input type="text" name="add_header_key_count" size="15" id="header_key_count" /> : '
    + '<input type="text" name="add_header_val_count" size="35" id="header_val_count" />
';
  content = content.replace(/count/g, count);

  $('#add-header-field').before(content);
}

$(document).ready(function() {
  var i = $('#main input[type=text]').first();
  if (i) {
    var v = i.val();
    if (v.trim().length < 1) {
      i.focus();
    }
  }

  $('#spinner')
    .ajaxStart(function() {
        $(this).show();
    })
    .ajaxStop(function() {
        $(this).hide();
    });

  $('#search_query').autocomplete({
    source: autocomplete_source(),
    focus:  function(event, ui) {
      return false;
    },
    select: function(event, ui) {
      window.location.href = $(ui.item).val();
      return false;
    }
  });

  $('#search_type').change(function() {
    $('#search_query').autocomplete(
      'option', 'source', autocomplete_source()
    );
  });
});
<|endoftext|>


<commit_before>define([
  '../libs/download'
],function(
  download
){
  var history = [];
  history.log = function() {
    history.push( [].slice(arguments ) );
    localStorage.setItem('lottory_history', JSON.stringify(history) );
  }
  history.exports = function() {
    download( 'lottory_history', localStorage.getItem('lottory_history') );
  }
  return history;
});<commit_msg>fix log bug<commit_after>define([
  '../libs/download'
],function(
  download
){
  var history = [];
  history.log = function() {
    history.push( [].slice.call(arguments) );
    localStorage.setItem('lottory_history', JSON.stringify(history) );
  }
  history.exports = function() {
    download( 'lottory_history', localStorage.getItem('lottory_history') );
  }
  return history;
});<|endoftext|>