<commit_before>import { extent, groups, mean, deviation } from "d3-array";
import { axisBottom, axisLeft } from "d3-axis";
import { scaleLinear } from "d3-scale";
import { select, event as d3event } from "d3-selection";
import { symbol, symbolDiamond } from "d3-shape";
import { orderBy } from "lodash";
import { measurementIdSymbol, measurementJitterSymbol } from "../../util/globals";

/* C O N S T A N T S */
const layout = {
  leftPadding: 120,
  rightPadding: 30,
  topPadding: 20,
  bottomPadding: 50,
  subplotHeight: 100,
  subplotPadding: 10,
  circleRadius: 3,
  circleHoverRadius: 5,
  thresholdStrokeWidth: 2,
  thresholdStroke: "#DDD",
  subplotFill: "#adb1b3",
  subplotFillOpacity: "0.15",
  diamondSize: 25,
  standardDeviationStroke: 2,
  overallMeanColor: "#000"
};

const classes = {
  xAxis: "measurementXAxis",
  yAxis: "measurementYAxis",
  threshold: "measurementThreshold",
  subplot: "measurementSubplot",
  subplotBackground: "measurementSubplotBackground",
  rawMeasurements: "rawMeasurements",
  rawMeasurementsGroup: "rawMeasurementsGroup",
  overallMean: "measurementsOverallMean",
  colorMean: "measurementsColorMean",
  mean: "mean",
  standardDeviation: "standardDeviation"
};

export const svgContainerDOMId = "measurementsSVGContainer";
const getMeasurementDOMId = (measurement) => `meaurement_${measurement[measurementIdSymbol]}`;
const domIdRegex = new RegExp("[^\\w\\-_]+", "gi");
const getSubplotDOMId = (groupingValue) => `measurement_subplot_${groupingValue.replace(domIdRegex, "_")}`;

/**
 * Creates the D3 linear scale for the x-axis with the provided measurements'
 * values as the domain and the panelWidth with hard-coded padding values as
 * the range. Expected to be shared across all subplots.
 * @param {number} panelWidth
 * @param {Array<Object>} measurements
 * @returns {function}
 */
export const createXScale = (panelWidth, measurements) => {
  return (
    scaleLinear()
      .domain(extent(measurements, (m) => m.value))
      .range([layout.leftPadding, panelWidth - layout.rightPadding])
      .nice()
  );
};

/**
 * Creates the D3 linear scale for the y-axis of each individual subplot with
 * the provided measurements' jitters as the domain and the hard-coded
 * subplot height as the range.
 * @param {Array<Object>} measurements
 * @returns {function}
 */
export const createYScale = (measurements) => {
  return (
    scaleLinear()
      .domain(extent(measurements, (m) => m[measurementJitterSymbol]))
      .range([layout.subplotHeight, 0])
      .nice()
  );
};

/**
 * Uses D3.groups() to aggregate measurements into a nested array of groups
 * The groups are sorted with the most number of measurements first.
 *
 * If groupByFilters Map is provided, sort the groups by the order of the keys.
 * @param {Array<Object>} measurements
 * @param {string} groupBy
 * @param {Map<string,Object>} groupByFilters
 * @returns {Array<Array<string, Array>>}
 */
export const groupMeasurements = (measurements, groupBy, groupByFilters) => {
  const groupedMeasurements = groups(measurements, (d) => d[groupBy]);
  if (groupByFilters) {
    const sortGroupOrder = [...groupByFilters.keys()];
    return orderBy(
      groupedMeasurements,
      ([groupingValue]) => sortGroupOrder.indexOf(groupingValue),
      "asc"
    );
  }
  return orderBy(
    groupedMeasurements,
    ([, groupingMeasurements]) => groupingMeasurements.length,
    "desc");
};

export const clearMeasurementsSVG = (ref) => {
  select(ref)
    .attr("height", null)
    .selectAll("*").remove();
};

const drawMeanAndStandardDeviation = (values, d3ParentNode, containerClass, color, xScale, yValue, handleHover) => {
  const meanAndStandardDeviation = {
    mean: mean(values),
    standardDeviation: deviation(values)
  };
  // Container for both mean and standard deviation
  const container = d3ParentNode.append("g")
    .attr("class", containerClass)
    .attr("display", "none")
    .selectAll("meanAndStandardDeviation")
    .data([meanAndStandardDeviation])
    .enter();

  container.append("path")
    .attr("class", classes.mean)
    .attr("transform", (d) => `translate(${xScale(d.mean)}, ${yValue})`)
    .attr("d", symbol().type(symbolDiamond).size(layout.diamondSize))
    .attr("fill", color)
    .on("mouseover", (d) => {
      // Get mouse position for HoverPanel
      const { clientX, clientY } = d3event;
      handleHover(d, "mean", clientX, clientY);
    })
    .on("mouseout", () => handleHover(null));

  if (meanAndStandardDeviation.standardDeviation !== undefined) {
    container.append("line")
      .attr("class", classes.standardDeviation)
      .attr("x1", (d) => xScale(d.mean - d.standardDeviation))
      .attr("x2", (d) => xScale(d.mean + d.standardDeviation))
      .attr("y1", yValue)
      .attr("y2", yValue)
      .attr("stroke-width", layout.standardDeviationStroke)
      .attr("stroke", color)
      .on("mouseover", (d) => {
        // Get mouse position for HoverPanel
        const { clientX, clientY } = d3event;
        handleHover(d, "mean", clientX, clientY);
      })
      .on("mouseout", () => handleHover(null));
  }
};

export const drawMeasurementsSVG = (ref, svgData, handleHover) => {
  const {xScale, yScale, x_axis_label, threshold, groupedMeasurements} = svgData;

  // Do not draw SVG if there are no measurements
  if (groupedMeasurements && groupedMeasurements.length === 0) return;

  const svg = select(ref);
  const svgWidth = svg.node().getBoundingClientRect().width;

  // The number of groups is the number of subplots, which determines the final SVG height
  const totalSubplotHeight = (layout.subplotHeight * groupedMeasurements.length);
  const svgHeight = totalSubplotHeight + layout.topPadding + layout.bottomPadding;
  svg.attr("height", svgHeight);

  // Add threshold if provided
  if (threshold !== null) {
    const thresholdXValue = xScale(threshold);
    svg.append("line")
      .attr("class", classes.threshold)
      .attr("x1", thresholdXValue)
      .attr("x2", thresholdXValue)
      .attr("y1", layout.topPadding)
      .attr("y2", svgHeight - layout.bottomPadding)
      .attr("stroke-width", layout.thresholdStrokeWidth)
      .attr("stroke", layout.thresholdStroke)
      // Hide threshold by default since another function will toggle display
      .attr("display", "none");
  }

  // Add x-axis to the bottom of the SVG
  // (above the bottomPadding to leave room for the x-axis label)
  svg.append("g")
    .attr("class", classes.xAxis)
    .attr("transform", `translate(0, ${svgHeight - layout.bottomPadding})`)
    .call(axisBottom(xScale))
    .call((g) => g.attr("font-family", null))
    .append("text")
      .attr("x", layout.leftPadding + ((svgWidth - layout.leftPadding - layout.rightPadding)) / 2)
      .attr("y", layout.bottomPadding * 2 / 3)
      .attr("text-anchor", "middle")
      .attr("fill", "currentColor")
      .text(x_axis_label);

  // Create a subplot for each grouping
  let prevSubplotBottom = layout.topPadding;
  groupedMeasurements.forEach(([groupingValue, measurements], index) => {
    // Make each subplot it's own SVG to re-use the same subplot yScale
    const subplot = svg.append("svg")
      .attr("class", classes.subplot)
      .attr("id", getSubplotDOMId(groupingValue))
      .attr("width", "100%")
      .attr("height", layout.subplotHeight)
      .attr("y", prevSubplotBottom);

    // Add subplot height to keep track of y position for next subplot
    prevSubplotBottom += layout.subplotHeight;

    // Add a rect to fill the entire width with a light grey background for every other group
    subplot.append("rect")
      .attr("class", classes.subplotBackground)
      .attr("x", 0)
      .attr("y", 0)
      .attr("height", "100%")
      .attr("width", "100%")
      .attr("fill", index % 2 ? layout.subplotFill : "none")
      .attr("fill-opacity", layout.subplotFillOpacity);

    // Add y axis with a single tick that displays the grouping value
    subplot.append("g")
      .attr("class", classes.yAxis)
      .attr("transform", `translate(${layout.leftPadding}, 0)`)
      .call(
        axisLeft(yScale)
          .ticks(1)
          .tickFormat(() => groupingValue))
      .call((g) => g.attr("font-family", null));

    // Add circles for each measurement
    subplot.append("g")
      .attr("class", classes.rawMeasurementsGroup)
      .attr("display", "none")
      .selectAll("dot")
      .data(measurements)
      .enter()
      .append("circle")
        .attr("class", classes.rawMeasurements)
        .attr("id", (d) => getMeasurementDOMId(d))
        .attr("cx", (d) => xScale(d.value))
        .attr("cy", (d) => yScale(d[measurementJitterSymbol]))
        .attr("r", layout.circleRadius)
        .on("mouseover", (d, i, elements) => {
          select(elements[i]).transition()
            .duration("100")
            .attr("r", layout.circleHoverRadius);

          // Get mouse position for HoverPanel
          const { clientX, clientY } = d3event;
          // sets hover data state to trigger the hover panel display
          handleHover(d, "measurement", clientX, clientY);
        })
        .on("mouseout", (_, i, elements) => {
          select(elements[i]).transition()
            .duration("200")
            .attr("r", layout.circleRadius);
          // sets hover data state to null to hide the hover panel display
          handleHover(null);
        });

    // Draw overall mean and standard deviation for measurement values
    drawMeanAndStandardDeviation(
      measurements.map((d) => d.value),
      subplot,
      classes.overallMean,
      layout.overallMeanColor,
      xScale,
      layout.subplotHeight / 2,
      handleHover
    );
  });
};

export const colorMeasurementsSVG = (ref, treeStrainColors) => {
  const svg = select(ref);
  svg.selectAll(`.${classes.rawMeasurements}`)
    .style("fill", (d) => treeStrainColors[d.strain].color);
};

export const drawMeansForColorBy = (ref, svgData, treeStrainColors, handleHover) => {
  const { xScale, groupedMeasurements } = svgData;
  const svg = select(ref);
  // Re move all current color by means
  svg.selectAll(`.${classes.colorMean}`).remove();
  // Calc and draw color by means for each group
  groupedMeasurements.forEach(([groupingValue, measurements]) => {
    // For each color-by attribute, create an array of measurement values and keep track of color
    const colorByGroups = {};
    measurements.forEach((measurement) => {
      const { attribute, color } = treeStrainColors[measurement.strain];
      colorByGroups[attribute] = colorByGroups[attribute] || {color: null, values: []};
      colorByGroups[attribute].values.push(measurement.value);
      if (!colorByGroups[attribute].color) {
        colorByGroups[attribute].color = color;
      }
    });
    // Plot mean/SD for each color-by attribute within subplot
    const subplot = svg.select(`#${getSubplotDOMId(groupingValue)}`);
    const numberOfColorByAttributes = Object.keys(colorByGroups).length;
    // Calc space between means to evenly spread them within subplot
    const ySpacing = (layout.subplotHeight - 2 * layout.subplotPadding) / (numberOfColorByAttributes - 1);
    let yValue = layout.subplotPadding;
    Object.values(colorByGroups).forEach(({color, values}) => {
      drawMeanAndStandardDeviation(
        values,
        subplot,
        classes.colorMean,
        color,
        xScale,
        yValue,
        handleHover
      );
      // Increate yValue for next attribute mean
      yValue += ySpacing;
    });
  });
};

export const changeMeasurementsDisplay = (ref, display) => {
  const svg = select(ref);
  const dataDisplayClasses = {
    raw: classes.rawMeasurementsGroup,
    mean: classes.colorMean
  };
  Object.entries(dataDisplayClasses).forEach(([displayOption, displayClass]) => {
    svg.selectAll(`.${displayClass}`)
      .attr("display", display === displayOption ? null : "none");
  });
};

export const toggleDisplay = (ref, elementClass, displayOn) => {
  const displayAttr = displayOn ? null : "none";
  select(ref)
    .selectAll(`.${classes[elementClass]}`)
      .attr("display", displayAttr);
};
<commit_msg>Measurements: add padding around overall mean<commit_after>import { extent, groups, mean, deviation } from "d3-array";
import { axisBottom, axisLeft } from "d3-axis";
import { scaleLinear } from "d3-scale";
import { select, event as d3event } from "d3-selection";
import { symbol, symbolDiamond } from "d3-shape";
import { orderBy } from "lodash";
import { measurementIdSymbol, measurementJitterSymbol } from "../../util/globals";

/* C O N S T A N T S */
const layout = {
  leftPadding: 120,
  rightPadding: 30,
  topPadding: 20,
  bottomPadding: 50,
  subplotHeight: 100,
  subplotPadding: 10,
  circleRadius: 3,
  circleHoverRadius: 5,
  thresholdStrokeWidth: 2,
  thresholdStroke: "#DDD",
  subplotFill: "#adb1b3",
  subplotFillOpacity: "0.15",
  diamondSize: 25,
  standardDeviationStroke: 2,
  overallMeanColor: "#000"
};
// Display overall mean at the center of each subplot
layout['overallMeanYValue'] = layout.subplotHeight / 2;

const classes = {
  xAxis: "measurementXAxis",
  yAxis: "measurementYAxis",
  threshold: "measurementThreshold",
  subplot: "measurementSubplot",
  subplotBackground: "measurementSubplotBackground",
  rawMeasurements: "rawMeasurements",
  rawMeasurementsGroup: "rawMeasurementsGroup",
  overallMean: "measurementsOverallMean",
  colorMean: "measurementsColorMean",
  mean: "mean",
  standardDeviation: "standardDeviation"
};

export const svgContainerDOMId = "measurementsSVGContainer";
const getMeasurementDOMId = (measurement) => `meaurement_${measurement[measurementIdSymbol]}`;
const domIdRegex = new RegExp("[^\\w\\-_]+", "gi");
const getSubplotDOMId = (groupingValue) => `measurement_subplot_${groupingValue.replace(domIdRegex, "_")}`;

/**
 * Creates the D3 linear scale for the x-axis with the provided measurements'
 * values as the domain and the panelWidth with hard-coded padding values as
 * the range. Expected to be shared across all subplots.
 * @param {number} panelWidth
 * @param {Array<Object>} measurements
 * @returns {function}
 */
export const createXScale = (panelWidth, measurements) => {
  return (
    scaleLinear()
      .domain(extent(measurements, (m) => m.value))
      .range([layout.leftPadding, panelWidth - layout.rightPadding])
      .nice()
  );
};

/**
 * Creates the D3 linear scale for the y-axis of each individual subplot with
 * the provided measurements' jitters as the domain and the hard-coded
 * subplot height as the range.
 * @param {Array<Object>} measurements
 * @returns {function}
 */
export const createYScale = (measurements) => {
  return (
    scaleLinear()
      .domain(extent(measurements, (m) => m[measurementJitterSymbol]))
      .range([layout.subplotHeight, 0])
      .nice()
  );
};

/**
 * Uses D3.groups() to aggregate measurements into a nested array of groups
 * The groups are sorted with the most number of measurements first.
 *
 * If groupByFilters Map is provided, sort the groups by the order of the keys.
 * @param {Array<Object>} measurements
 * @param {string} groupBy
 * @param {Map<string,Object>} groupByFilters
 * @returns {Array<Array<string, Array>>}
 */
export const groupMeasurements = (measurements, groupBy, groupByFilters) => {
  const groupedMeasurements = groups(measurements, (d) => d[groupBy]);
  if (groupByFilters) {
    const sortGroupOrder = [...groupByFilters.keys()];
    return orderBy(
      groupedMeasurements,
      ([groupingValue]) => sortGroupOrder.indexOf(groupingValue),
      "asc"
    );
  }
  return orderBy(
    groupedMeasurements,
    ([, groupingMeasurements]) => groupingMeasurements.length,
    "desc");
};

export const clearMeasurementsSVG = (ref) => {
  select(ref)
    .attr("height", null)
    .selectAll("*").remove();
};

const drawMeanAndStandardDeviation = (values, d3ParentNode, containerClass, color, xScale, yValue, handleHover) => {
  const meanAndStandardDeviation = {
    mean: mean(values),
    standardDeviation: deviation(values)
  };
  // Container for both mean and standard deviation
  const container = d3ParentNode.append("g")
    .attr("class", containerClass)
    .attr("display", "none")
    .selectAll("meanAndStandardDeviation")
    .data([meanAndStandardDeviation])
    .enter();

  container.append("path")
    .attr("class", classes.mean)
    .attr("transform", (d) => `translate(${xScale(d.mean)}, ${yValue})`)
    .attr("d", symbol().type(symbolDiamond).size(layout.diamondSize))
    .attr("fill", color)
    .on("mouseover", (d) => {
      // Get mouse position for HoverPanel
      const { clientX, clientY } = d3event;
      handleHover(d, "mean", clientX, clientY);
    })
    .on("mouseout", () => handleHover(null));

  if (meanAndStandardDeviation.standardDeviation !== undefined) {
    container.append("line")
      .attr("class", classes.standardDeviation)
      .attr("x1", (d) => xScale(d.mean - d.standardDeviation))
      .attr("x2", (d) => xScale(d.mean + d.standardDeviation))
      .attr("y1", yValue)
      .attr("y2", yValue)
      .attr("stroke-width", layout.standardDeviationStroke)
      .attr("stroke", color)
      .on("mouseover", (d) => {
        // Get mouse position for HoverPanel
        const { clientX, clientY } = d3event;
        handleHover(d, "mean", clientX, clientY);
      })
      .on("mouseout", () => handleHover(null));
  }
};

export const drawMeasurementsSVG = (ref, svgData, handleHover) => {
  const {xScale, yScale, x_axis_label, threshold, groupedMeasurements} = svgData;

  // Do not draw SVG if there are no measurements
  if (groupedMeasurements && groupedMeasurements.length === 0) return;

  const svg = select(ref);
  const svgWidth = svg.node().getBoundingClientRect().width;

  // The number of groups is the number of subplots, which determines the final SVG height
  const totalSubplotHeight = (layout.subplotHeight * groupedMeasurements.length);
  const svgHeight = totalSubplotHeight + layout.topPadding + layout.bottomPadding;
  svg.attr("height", svgHeight);

  // Add threshold if provided
  if (threshold !== null) {
    const thresholdXValue = xScale(threshold);
    svg.append("line")
      .attr("class", classes.threshold)
      .attr("x1", thresholdXValue)
      .attr("x2", thresholdXValue)
      .attr("y1", layout.topPadding)
      .attr("y2", svgHeight - layout.bottomPadding)
      .attr("stroke-width", layout.thresholdStrokeWidth)
      .attr("stroke", layout.thresholdStroke)
      // Hide threshold by default since another function will toggle display
      .attr("display", "none");
  }

  // Add x-axis to the bottom of the SVG
  // (above the bottomPadding to leave room for the x-axis label)
  svg.append("g")
    .attr("class", classes.xAxis)
    .attr("transform", `translate(0, ${svgHeight - layout.bottomPadding})`)
    .call(axisBottom(xScale))
    .call((g) => g.attr("font-family", null))
    .append("text")
      .attr("x", layout.leftPadding + ((svgWidth - layout.leftPadding - layout.rightPadding)) / 2)
      .attr("y", layout.bottomPadding * 2 / 3)
      .attr("text-anchor", "middle")
      .attr("fill", "currentColor")
      .text(x_axis_label);

  // Create a subplot for each grouping
  let prevSubplotBottom = layout.topPadding;
  groupedMeasurements.forEach(([groupingValue, measurements], index) => {
    // Make each subplot it's own SVG to re-use the same subplot yScale
    const subplot = svg.append("svg")
      .attr("class", classes.subplot)
      .attr("id", getSubplotDOMId(groupingValue))
      .attr("width", "100%")
      .attr("height", layout.subplotHeight)
      .attr("y", prevSubplotBottom);

    // Add subplot height to keep track of y position for next subplot
    prevSubplotBottom += layout.subplotHeight;

    // Add a rect to fill the entire width with a light grey background for every other group
    subplot.append("rect")
      .attr("class", classes.subplotBackground)
      .attr("x", 0)
      .attr("y", 0)
      .attr("height", "100%")
      .attr("width", "100%")
      .attr("fill", index % 2 ? layout.subplotFill : "none")
      .attr("fill-opacity", layout.subplotFillOpacity);

    // Add y axis with a single tick that displays the grouping value
    subplot.append("g")
      .attr("class", classes.yAxis)
      .attr("transform", `translate(${layout.leftPadding}, 0)`)
      .call(
        axisLeft(yScale)
          .ticks(1)
          .tickFormat(() => groupingValue))
      .call((g) => g.attr("font-family", null));

    // Add circles for each measurement
    subplot.append("g")
      .attr("class", classes.rawMeasurementsGroup)
      .attr("display", "none")
      .selectAll("dot")
      .data(measurements)
      .enter()
      .append("circle")
        .attr("class", classes.rawMeasurements)
        .attr("id", (d) => getMeasurementDOMId(d))
        .attr("cx", (d) => xScale(d.value))
        .attr("cy", (d) => yScale(d[measurementJitterSymbol]))
        .attr("r", layout.circleRadius)
        .on("mouseover", (d, i, elements) => {
          select(elements[i]).transition()
            .duration("100")
            .attr("r", layout.circleHoverRadius);

          // Get mouse position for HoverPanel
          const { clientX, clientY } = d3event;
          // sets hover data state to trigger the hover panel display
          handleHover(d, "measurement", clientX, clientY);
        })
        .on("mouseout", (_, i, elements) => {
          select(elements[i]).transition()
            .duration("200")
            .attr("r", layout.circleRadius);
          // sets hover data state to null to hide the hover panel display
          handleHover(null);
        });

    // Draw overall mean and standard deviation for measurement values
    drawMeanAndStandardDeviation(
      measurements.map((d) => d.value),
      subplot,
      classes.overallMean,
      layout.overallMeanColor,
      xScale,
      layout.overallMeanYValue,
      handleHover
    );
  });
};

export const colorMeasurementsSVG = (ref, treeStrainColors) => {
  const svg = select(ref);
  svg.selectAll(`.${classes.rawMeasurements}`)
    .style("fill", (d) => treeStrainColors[d.strain].color);
};

export const drawMeansForColorBy = (ref, svgData, treeStrainColors, handleHover) => {
  const { xScale, groupedMeasurements } = svgData;
  const svg = select(ref);
  // Re move all current color by means
  svg.selectAll(`.${classes.colorMean}`).remove();
  // Calc and draw color by means for each group
  groupedMeasurements.forEach(([groupingValue, measurements]) => {
    // For each color-by attribute, create an array of measurement values and keep track of color
    const colorByGroups = {};
    measurements.forEach((measurement) => {
      const { attribute, color } = treeStrainColors[measurement.strain];
      colorByGroups[attribute] = colorByGroups[attribute] || {color: null, values: []};
      colorByGroups[attribute].values.push(measurement.value);
      if (!colorByGroups[attribute].color) {
        colorByGroups[attribute].color = color;
      }
    });
    // Plot mean/SD for each color-by attribute within subplot
    const subplot = svg.select(`#${getSubplotDOMId(groupingValue)}`);
    const numberOfColorByAttributes = Object.keys(colorByGroups).length;
    // Calc space between means to evenly spread them within subplot
    // 2 x subplotPadding for padding of top and bottom of each subplot
    // 2 x subplotPadding for padding around the overall mean display
    const ySpacing = (layout.subplotHeight - 4 * layout.subplotPadding) / (numberOfColorByAttributes - 1);
    let yValue = layout.subplotPadding;
    Object.values(colorByGroups).forEach(({color, values}) => {
      drawMeanAndStandardDeviation(
        values,
        subplot,
        classes.colorMean,
        color,
        xScale,
        yValue,
        handleHover
      );
      // Increate yValue for next attribute mean
      yValue += ySpacing;
      // If the next yValue is near the overall mean display,
      // shift to below the overall mean display + subplotPadding
      if (yValue > (layout.overallMeanYValue - layout.subplotPadding) &&
          yValue < (layout.overallMeanYValue + layout.subplotPadding)) {
        yValue = layout.overallMeanYValue + layout.subplotPadding;
      }
    });
  });
};

export const changeMeasurementsDisplay = (ref, display) => {
  const svg = select(ref);
  const dataDisplayClasses = {
    raw: classes.rawMeasurementsGroup,
    mean: classes.colorMean
  };
  Object.entries(dataDisplayClasses).forEach(([displayOption, displayClass]) => {
    svg.selectAll(`.${displayClass}`)
      .attr("display", display === displayOption ? null : "none");
  });
};

export const toggleDisplay = (ref, elementClass, displayOn) => {
  const displayAttr = displayOn ? null : "none";
  select(ref)
    .selectAll(`.${classes[elementClass]}`)
      .attr("display", displayAttr);
};
<|endoftext|>


<commit_before>/*
Copyright 2017, 2018 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import GroupFilterOrderStore from '../../stores/GroupFilterOrderStore';

import GroupActions from '../../actions/GroupActions';

import * as sdk from '../../index';
import dis from '../../dispatcher/dispatcher';
import { _t } from '../../languageHandler';

import { Droppable } from 'react-beautiful-dnd';
import classNames from 'classnames';
import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar";
import SettingsStore from "../../settings/SettingsStore";
import UserTagTile from "../views/elements/UserTagTile";
import {replaceableComponent} from "../../utils/replaceableComponent";

@replaceableComponent("structures.GroupFilterPanel")
class GroupFilterPanel extends React.Component {
    static contextType = MatrixClientContext;

    state = {
        orderedTags: [],
        selectedTags: [],
    };

    componentDidMount() {
        this.unmounted = false;
        this.context.on("Group.myMembership", this._onGroupMyMembership);
        this.context.on("sync", this._onClientSync);

        this._groupFilterOrderStoreToken = GroupFilterOrderStore.addListener(() => {
            if (this.unmounted) {
                return;
            }
            this.setState({
                orderedTags: GroupFilterOrderStore.getOrderedTags() || [],
                selectedTags: GroupFilterOrderStore.getSelectedTags(),
            });
        });
        // This could be done by anything with a matrix client
        dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
    }

    componentWillUnmount() {
        this.unmounted = true;
        this.context.removeListener("Group.myMembership", this._onGroupMyMembership);
        this.context.removeListener("sync", this._onClientSync);
        if (this._groupFilterOrderStoreToken) {
            this._groupFilterOrderStoreToken.remove();
        }
    }

    _onGroupMyMembership = () => {
        if (this.unmounted) return;
        dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
    };

    _onClientSync = (syncState, prevState) => {
        // Consider the client reconnected if there is no error with syncing.
        // This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
        const reconnected = syncState !== "ERROR" && prevState !== syncState;
        if (reconnected) {
            // Load joined groups
            dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
        }
    };

    onMouseDown = e => {
        // only dispatch if its not a no-op
        if (this.state.selectedTags.length > 0) {
            dis.dispatch({action: 'deselect_tags'});
        }
    };

    onClearFilterClick = ev => {
        dis.dispatch({action: 'deselect_tags'});
    };

    renderGlobalIcon() {
        if (!SettingsStore.getValue("feature_communities_v2_prototypes")) return null;

        return (

                <UserTagTile />

        );
    }

    render() {
        const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
        const ActionButton = sdk.getComponent('elements.ActionButton');

        const tags = this.state.orderedTags.map((tag, index) => {
            return <DNDTagTile
                key={tag}
                tag={tag}
                index={index}
                selected={this.state.selectedTags.includes(tag)}
            />;
        });

        const itemsSelected = this.state.selectedTags.length > 0;
        const classes = classNames('mx_GroupFilterPanel', {
            mx_GroupFilterPanel_items_selected: itemsSelected,
        });

        let betaDot;
        if (!SettingsStore.getValue("feature_spaces") && !localStorage.getItem("mx_seenSpacesBeta")) {
            betaDot =
;
        }

        let createButton = (
            <ActionButton
                tooltip
                label={_t("Communities")}
                action="toggle_my_groups"
                className="mx_TagTile mx_TagTile_plus">
                { betaDot }
            </ActionButton>
        );

        if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
            createButton = (
                <ActionButton
                    tooltip
                    label={_t("Create community")}
                    action="view_create_group"
                    className="mx_TagTile mx_TagTile_plus" />
            );
        }

        return

            <AutoHideScrollbar
                className="mx_GroupFilterPanel_scroller"
                // XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273
                // instead of onClick. Otherwise we experience https://github.com/vector-im/element-web/issues/6253
                onMouseDown={this.onMouseDown}
            >
                <Droppable
                    droppableId="tag-panel-droppable"
                    type="draggable-TagTile"
                >
                    { (provided, snapshot) => (

                                { this.renderGlobalIcon() }
                                { tags }

                                    {createButton}

                                { provided.placeholder }

                    ) }
                </Droppable>
            </AutoHideScrollbar>

;
    }
}
export default GroupFilterPanel;
<commit_msg>fix beta dot showing up when beta is disabled<commit_after>/*
Copyright 2017, 2018 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import GroupFilterOrderStore from '../../stores/GroupFilterOrderStore';

import GroupActions from '../../actions/GroupActions';

import * as sdk from '../../index';
import dis from '../../dispatcher/dispatcher';
import { _t } from '../../languageHandler';

import { Droppable } from 'react-beautiful-dnd';
import classNames from 'classnames';
import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar";
import SettingsStore from "../../settings/SettingsStore";
import UserTagTile from "../views/elements/UserTagTile";
import {replaceableComponent} from "../../utils/replaceableComponent";

@replaceableComponent("structures.GroupFilterPanel")
class GroupFilterPanel extends React.Component {
    static contextType = MatrixClientContext;

    state = {
        orderedTags: [],
        selectedTags: [],
    };

    componentDidMount() {
        this.unmounted = false;
        this.context.on("Group.myMembership", this._onGroupMyMembership);
        this.context.on("sync", this._onClientSync);

        this._groupFilterOrderStoreToken = GroupFilterOrderStore.addListener(() => {
            if (this.unmounted) {
                return;
            }
            this.setState({
                orderedTags: GroupFilterOrderStore.getOrderedTags() || [],
                selectedTags: GroupFilterOrderStore.getSelectedTags(),
            });
        });
        // This could be done by anything with a matrix client
        dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
    }

    componentWillUnmount() {
        this.unmounted = true;
        this.context.removeListener("Group.myMembership", this._onGroupMyMembership);
        this.context.removeListener("sync", this._onClientSync);
        if (this._groupFilterOrderStoreToken) {
            this._groupFilterOrderStoreToken.remove();
        }
    }

    _onGroupMyMembership = () => {
        if (this.unmounted) return;
        dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
    };

    _onClientSync = (syncState, prevState) => {
        // Consider the client reconnected if there is no error with syncing.
        // This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
        const reconnected = syncState !== "ERROR" && prevState !== syncState;
        if (reconnected) {
            // Load joined groups
            dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
        }
    };

    onMouseDown = e => {
        // only dispatch if its not a no-op
        if (this.state.selectedTags.length > 0) {
            dis.dispatch({action: 'deselect_tags'});
        }
    };

    onClearFilterClick = ev => {
        dis.dispatch({action: 'deselect_tags'});
    };

    renderGlobalIcon() {
        if (!SettingsStore.getValue("feature_communities_v2_prototypes")) return null;

        return (

                <UserTagTile />

        );
    }

    render() {
        const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
        const ActionButton = sdk.getComponent('elements.ActionButton');

        const tags = this.state.orderedTags.map((tag, index) => {
            return <DNDTagTile
                key={tag}
                tag={tag}
                index={index}
                selected={this.state.selectedTags.includes(tag)}
            />;
        });

        const itemsSelected = this.state.selectedTags.length > 0;
        const classes = classNames('mx_GroupFilterPanel', {
            mx_GroupFilterPanel_items_selected: itemsSelected,
        });

        let betaDot;
        if (SettingsStore.getBetaInfo("feature_spaces") && !localStorage.getItem("mx_seenSpacesBeta")) {
            betaDot =
;
        }

        let createButton = (
            <ActionButton
                tooltip
                label={_t("Communities")}
                action="toggle_my_groups"
                className="mx_TagTile mx_TagTile_plus">
                { betaDot }
            </ActionButton>
        );

        if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
            createButton = (
                <ActionButton
                    tooltip
                    label={_t("Create community")}
                    action="view_create_group"
                    className="mx_TagTile mx_TagTile_plus" />
            );
        }

        return

            <AutoHideScrollbar
                className="mx_GroupFilterPanel_scroller"
                // XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273
                // instead of onClick. Otherwise we experience https://github.com/vector-im/element-web/issues/6253
                onMouseDown={this.onMouseDown}
            >
                <Droppable
                    droppableId="tag-panel-droppable"
                    type="draggable-TagTile"
                >
                    { (provided, snapshot) => (

                                { this.renderGlobalIcon() }
                                { tags }

                                    {createButton}

                                { provided.placeholder }

                    ) }
                </Droppable>
            </AutoHideScrollbar>

;
    }
}
export default GroupFilterPanel;
<|endoftext|>


<commit_before>import React from 'react';
import { injectIntl } from 'react-intl';
import LinkWithFilters from '../LinkWithFilters';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import SnapshotSelectorContainer from './SnapshotSelectorContainer';
import TimespanSelectorContainer from './TimespanSelectorContainer';

class ControlBar extends React.Component {
  getStyles() {
    const styles = {
      root: {
        backgroundColor: '#f2f2f2',
        marginBottom: 15,
      },
      leftArea: {
        paddingTop: 18,
      },
      controlBar: {
      },
    };
    return styles;
  }
  render() {
    const { title, topicId, location } = this.props;
    const styles = this.getStyles();
    return (

        <Grid>
          <Row style={styles.controlBar}>
            <Col lg={3} md={3} sm={3} style={styles.leftArea}>
              <LinkWithFilters to={`/topics/${topicId}/summary`} style={styles.name}>{title}</LinkWithFilters>
            </Col>
            <Col lg={6} md={6} sm={6}>
              <TimespanSelectorContainer topicId={topicId} location={location} />
            </Col>
            <Col lg={3} md={3} sm={3}>
              <SnapshotSelectorContainer topicId={topicId} location={location} />
            </Col>
          </Row>
        </Grid>

    );
  }
}

ControlBar.propTypes = {
  // from context
  intl: React.PropTypes.object.isRequired,
  // from parent
  title: React.PropTypes.string,
  topicId: React.PropTypes.number,
  location: React.PropTypes.object.isRequired,
};

export default injectIntl(ControlBar);
<commit_msg>add border under control bar<commit_after>import React from 'react';
import { injectIntl } from 'react-intl';
import LinkWithFilters from '../LinkWithFilters';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import SnapshotSelectorContainer from './SnapshotSelectorContainer';
import TimespanSelectorContainer from './TimespanSelectorContainer';

class ControlBar extends React.Component {
  getStyles() {
    const styles = {
      root: {
        backgroundColor: '#f2f2f2',
        marginBottom: 15,
        borderBottom: '1px solid rgb(215,215,215)',
      },
      leftArea: {
        paddingTop: 18,
      },
      controlBar: {
      },
    };
    return styles;
  }
  render() {
    const { title, topicId, location } = this.props;
    const styles = this.getStyles();
    return (

        <Grid>
          <Row style={styles.controlBar}>
            <Col lg={3} md={3} sm={3} style={styles.leftArea}>
              <LinkWithFilters to={`/topics/${topicId}/summary`} style={styles.name}>{title}</LinkWithFilters>
            </Col>
            <Col lg={6} md={6} sm={6}>
              <TimespanSelectorContainer topicId={topicId} location={location} />
            </Col>
            <Col lg={3} md={3} sm={3}>
              <SnapshotSelectorContainer topicId={topicId} location={location} />
            </Col>
          </Row>
        </Grid>

    );
  }
}

ControlBar.propTypes = {
  // from context
  intl: React.PropTypes.object.isRequired,
  // from parent
  title: React.PropTypes.string,
  topicId: React.PropTypes.number,
  location: React.PropTypes.object.isRequired,
};

export default injectIntl(ControlBar);
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc function
 * @name ftepApp.controller:SearchbarCtrl
 * @description
 * # SearchbarCtrl
 * Controller of the ftepApp
 */
define(['../../../ftepmodules'], function(ftepmodules) {

    ftepmodules.controller('SearchbarCtrl', ['$scope', '$rootScope', '$http', 'CommonService', 'BasketService', 'MapService', 'SearchService', '$location', function($scope, $rootScope, $http, CommonService, BasketService, MapService, SearchService, $location) {

        // Get page path to save the search params in the SearchService when changing tabs
        var page = $location.path().replace(/\W/g,'') ? $location.path().replace('/','') : 'explorer';

        // Used for listening for drawn polygons on the map
        $scope.mapAoi = MapService.mapstore.aoi;

        $scope.searchParams = {
            selectedCatalog: {},
            activeSearch: {},
            allowedValues: {},
            catalogues: {},
            visibleList: {},
        };

        // Use a saved search if it exists
        if (SearchService.params[page].savedSearch) {
            $scope.searchParams = SearchService.params[page].savedSearch;
        }

        // Get all search input fields
        SearchService.getSearchParameters().then(function(data) {
            for (var field in data) {
                data[field].id = field;
            }
            $scope.searchParams.catalogues = data;
        });

        $scope.selectCatalog = function(field, catalog) {
            $scope.searchParams.activeSearch = {};
            $scope.searchParams.activeSearch[field.type] = catalog.value;
            $scope.searchParams.selectedCatalog = catalog;
            $scope.updateForm($scope.searchParams.catalogues, $scope.searchParams.activeSearch);
        };

        $scope.closeCatalog = function(field) {
            $scope.searchParams.activeSearch = {};
            $scope.searchParams.selectedCatalog = null;
        };

        $scope.$watch('mapAoi.wkt', function(wkt) {
            if (wkt) {
                $scope.searchParams.activeSearch.aoi = wkt;
            }
        });

        $scope.updateForm = function(catalogues, activeSearch) {
            // Get the list of fields from the catalog to display
            var visibleFields = SearchService.getVisibleFields(catalogues, activeSearch);
            $scope.searchParams.visibleList = visibleFields.map(function(a) {return a.id;});
            // Cleanup search params that are no longer visible
            $scope.searchParams.activeSearch = SearchService.searchCleanup(activeSearch, $scope.searchParams.visibleList);
            // Set default values in the active search
            $scope.searchParams.activeSearch = SearchService.getDefaultValues(catalogues, activeSearch, visibleFields, $scope.searchParams.visibleList);
            // Get the list of allowed values for each select option (based on current selections)
            $scope.searchParams.allowedValues = SearchService.getSelectValues(activeSearch, visibleFields);
        };

        $scope.search = function() {
            // Format the query for submission (format date fields correctly)
            var searchQuery = SearchService.formatSearchRequest($scope.searchParams.activeSearch);

            // Save the search so the results controller can access the search name
            SearchService.params[page].savedSearch = $scope.searchParams;

            SearchService.submit(searchQuery).then(function(searchResults) {
                $rootScope.$broadcast('update.geoResults', searchResults);
            }).catch(function() {
                $rootScope.$broadcast('update.geoResults');
            });
        };

        // Save the search when changing tabs so it can be reaccessed
        $scope.$on('$destroy', function() {
            SearchService.params[page].savedSearch = $scope.searchParams;
        });

    }]);
});
<commit_msg>Undefined AOI parameter fix<commit_after>'use strict';

/**
 * @ngdoc function
 * @name ftepApp.controller:SearchbarCtrl
 * @description
 * # SearchbarCtrl
 * Controller of the ftepApp
 */
define(['../../../ftepmodules'], function(ftepmodules) {

    ftepmodules.controller('SearchbarCtrl', ['$scope', '$rootScope', '$http', 'CommonService', 'BasketService', 'MapService', 'SearchService', '$location', function($scope, $rootScope, $http, CommonService, BasketService, MapService, SearchService, $location) {

        // Get page path to save the search params in the SearchService when changing tabs
        var page = $location.path().replace(/\W/g,'') ? $location.path().replace('/','') : 'explorer';

        // Used for listening for drawn polygons on the map
        $scope.mapAoi = MapService.mapstore.aoi;

        $scope.searchParams = {
            selectedCatalog: {},
            activeSearch: {},
            allowedValues: {},
            catalogues: {},
            visibleList: {},
        };

        // Use a saved search if it exists
        if (SearchService.params[page].savedSearch) {
            $scope.searchParams = SearchService.params[page].savedSearch;
        }

        // Get all search input fields
        SearchService.getSearchParameters().then(function(data) {
            for (var field in data) {
                data[field].id = field;
            }
            $scope.searchParams.catalogues = data;
        });

        $scope.selectCatalog = function(field, catalog) {
            if (!$scope.searchParams.activeSearch) {
                $scope.searchParams.activeSearch = {};
            }
            $scope.searchParams.activeSearch[field.type] = catalog.value;
            $scope.searchParams.selectedCatalog = catalog;
            $scope.updateForm($scope.searchParams.catalogues, $scope.searchParams.activeSearch);
        };

        $scope.closeCatalog = function(field) {
            $scope.searchParams.activeSearch = {};
            $scope.searchParams.selectedCatalog = null;
        };

        $scope.$watch('mapAoi.wkt', function(wkt) {
            if (wkt) {
                if (!$scope.searchParams.activeSearch) {
                    $scope.searchParams.activeSearch = {};
                }
                $scope.searchParams.activeSearch.aoi = wkt;
            }
        });

        $scope.updateForm = function(catalogues, activeSearch) {
            // Get the list of fields from the catalog to display
            var visibleFields = SearchService.getVisibleFields(catalogues, activeSearch);
            $scope.searchParams.visibleList = visibleFields.map(function(a) {return a.id;});
            // Cleanup search params that are no longer visible
            $scope.searchParams.activeSearch = SearchService.searchCleanup(activeSearch, $scope.searchParams.visibleList);
            // Set default values in the active search
            $scope.searchParams.activeSearch = SearchService.getDefaultValues(catalogues, activeSearch, visibleFields, $scope.searchParams.visibleList);
            // Get the list of allowed values for each select option (based on current selections)
            $scope.searchParams.allowedValues = SearchService.getSelectValues(activeSearch, visibleFields);
        };

        $scope.search = function() {
            // Format the query for submission (format date fields correctly)
            var searchQuery = SearchService.formatSearchRequest($scope.searchParams.activeSearch);

            // Save the search so the results controller can access the search name
            SearchService.params[page].savedSearch = $scope.searchParams;

            SearchService.submit(searchQuery).then(function(searchResults) {
                $rootScope.$broadcast('update.geoResults', searchResults);
            }).catch(function() {
                $rootScope.$broadcast('update.geoResults');
            });
        };

        // Save the search when changing tabs so it can be reaccessed
        $scope.$on('$destroy', function() {
            SearchService.params[page].savedSearch = $scope.searchParams;
        });

    }]);
});
<|endoftext|>


<commit_before>/*jslint node: true, indent: 4, regexp: true */
/* vim: set ts=4 et: */

/**
 * Crypt module. Does various encryption things
 * @module crypt
 */
'use strict';
var discourse;

/**
 * Description, for help text.
 * @type {String}
 */
exports.description = 'Encryptor';

/**
 * The config for the module
 * @type {Object}
 */
exports.configuration = {
    /**
     * Should the module be enabled?
     * @type {Boolean}
     */
    enabled: false
};

/**
 * The name of this sock module
 * @type {String}
 */
exports.name = 'Crypt';

/**
 * The priority of the sock module
 * @type {Number}
 */
exports.priority = undefined;

/**
 * The version of this module
 * @type {String}
 */
exports.version = '0.1.0';

/**
 *  Each command is an encryption mechanism and has the following properties:
 * - handler:        The encryption function.
 * - defaults:       Default values of parameters
 * - params:         Named parameters for this function
 * - randomPickable: If true, random encryption can select this function.
 *                   NOTE: random currently does not support parameters.
 * - description:    A description of this function for the help
 *
 * @type {Object}
 */
exports.commands = {
    /**
     * ROT13 "encryption"
     * @type {Object}
     */
    rot13: {
        handler: cryptCmd(function(s, payload, callback) {
            callback(null,
                s.replace( /[A-Za-z]/g, function(c) {
                    return String.fromCharCode(c.charCodeAt(0)
                        + (c.toUpperCase() <= 'M' ? 13 : -13 ));
                })
            );
        }),
        defaults: {},
        params: [],
        randomPickable: true,
        description: 'Rot13 encoding.'
    },
    /**
     * Reverse the string
     * @type {Object}
     */
    reverse: {
        handler: cryptCmd(function(s, payload, callback) {
            callback(null, s.split('').reverse().join(''));
        }),
        defaults: {},
        params: [],
        randomPickable: true,
        description: 'Reverse input.'
    },
    /**
     * XOR with block chaining
     * @type {Object}
     */
    xorbc: {
        handler: cryptCmd(xorbc(false)),
        defaults: {key: '42', iv: false},
        params: ['[key', '[iv'],
        description: 'XOR with block chaining.'
    },
    /**
     * Reverse XOR with block chaining
     * @type {Object}
     */
    rxorbc: {
        handler: cryptCmd(xorbc(true)),
        defaults: {key: '42', iv: false},
        params: ['[key', '[iv'],
        description: 'reverse XOR with block chaining.'
    },
    /**
     * Random other command
     * @type {Object}
     */
    random: {
        handler: function(payload, callback) {
            var keys = Object.keys(exports.commands).filter(function(k) {
                return exports.commands[k].randomPickable;
            });
            var id = Math.floor(keys.length * Math.random());
            payload.$command = 'random:' + keys[id];
            exports.commands[keys[id]].handler(payload, callback);
        },
        defaults: {},
        params: [],
        description: 'Random encryption.'
    }
};

/**
 * Helper to chain encryptions
 * @param  {function} handler The handler
 * @return {function} A curried version of that handler, assuming this author is using the word "curried" correctly
 */
function cryptCmd(handler) {
    return function(payload, callback) {
        discourse.log('Encrypt ' + (payload.$draft ? 'draft' : 'post')
                + ' with ' + payload.$command);
        handler(payload.$draft || payload.$post.cleaned,
                payload,
                function(err, msg, log) {
                    callback(err, {
                        replaceMsg: true,
                        msg: msg,
                        log: [log || payload.$command]
                    });
                });
    };
}

/**
 * xorbc implementatioon. Homegrown and weak as shit but who cares
 * @param  {[type]}
 * @return {[type]}
 */
function xorbc(decrypt) {
    return function(s, payload, callback) {
        /* Key is always passed as argument. */
        var key = toCharCodes(payload.key);
        /* IV is passed, or zero */
        var iv = payload.iv ? toCharCodes(payload.iv) : zeroArray(key.length);
        if (key.length !== iv.length) {
            return callback('Key and IV must be the same length');
        }
        var log = payload.$command + '(key: ' + JSON.stringify(key)
                + ', iv: ' + JSON.stringify(iv) + ')';
        callback(null,
            toCharCodes(s).map( function(c, i) {
                /* Calculate key for this byte */
                var o = i % key.length,
                    k = key[o] ^ iv[o];
                /* Next block IV is this block's plaintext */
                if (decrypt) {
                    iv[o] = c ^ k;
                    return iv[o];
                } else {
                    iv[o] = c;
                    return iv[o] ^ k;
                }
            }).map( function(c) {
                return String.fromCharCode(c);
            }).join(''),
            log
        );
    };
}

/**
 * Convert string to array of character codes
 * @param  {string} s The string
 * @return {Array} an array of character codes
 */
function toCharCodes(s) {
    return s.split('').map(function(c) {
        return c.charCodeAt(0);
    });
}

/**
 * Create array with constant value
 * @param  {Number} l The length of the array to make
 * @return {Array} an array of all 0s
 */
function zeroArray(l) {
    var a = [];
    for (var i = 0; i < l; i++) {
        a.push(0);
    }
    return a;
}

/**
 * Use a random encryption when PMed/mentioned/replied without command
 * @param {string} type - The type of event. Only responds if this is 'mentioned'
 * @param {string} notification - The notification to respond to
 * @param {string} topic - Unused.
 * @param {string} post - The post the notification was for
 * @param {function} callback - The callback to notify when processing is complete.
 */
exports.onNotify = function (type, notification, topic, post, callback) {
    if (!post || !post.cleaned ||
        ['private_message', 'mentioned', 'replied'].indexOf(type) === -1) {
        return callback();
    }
    discourse.log('Randomly encrypting post');

    exports.commands.random.handler({
        $post: post,
        $type: type,
        $command: 'random'
    }, function(_, msg) {
        var text = msg.msg + '\n\n<small>Filed under: ' + msg.log.join(' → ');
        discourse.createPost(notification.topic_id,
            notification.post_number, text, function() {
                callback(true);
            });
    });
};

/**
 * Bootstrap the module
 * @param  {string} browser - discourse.
 * @param  {object} config - The configuration to use
 */
exports.begin = function begin(browser) {
    discourse = browser;
};
<commit_msg>Fixing broken doc in crypt<commit_after>/*jslint node: true, indent: 4, regexp: true */
/* vim: set ts=4 et: */

/**
 * Crypt module. Does various encryption things
 * @module crypt
 */
'use strict';
var discourse;

/**
 * Description, for help text.
 * @type {String}
 */
exports.description = 'Encryptor';

/**
 * The config for the module
 * @type {Object}
 */
exports.configuration = {
    /**
     * Should the module be enabled?
     * @type {Boolean}
     */
    enabled: false
};

/**
 * The name of this sock module
 * @type {String}
 */
exports.name = 'Crypt';

/**
 * The priority of the sock module
 * @type {Number}
 */
exports.priority = undefined;

/**
 * The version of this module
 * @type {String}
 */
exports.version = '0.1.0';

/**
 *  Each command is an encryption mechanism and has the following properties:
 * - handler:        The encryption function.
 * - defaults:       Default values of parameters
 * - params:         Named parameters for this function
 * - randomPickable: If true, random encryption can select this function.
 *                   NOTE: random currently does not support parameters.
 * - description:    A description of this function for the help
 *
 * @type {Object}
 */
exports.commands = {
    /**
     * ROT13 "encryption"
     * @type {Object}
     */
    rot13: {
        handler: cryptCmd(function(s, payload, callback) {
            callback(null,
                s.replace( /[A-Za-z]/g, function(c) {
                    return String.fromCharCode(c.charCodeAt(0)
                        + (c.toUpperCase() <= 'M' ? 13 : -13 ));
                })
            );
        }),
        defaults: {},
        params: [],
        randomPickable: true,
        description: 'Rot13 encoding.'
    },
    /**
     * Reverse the string
     * @type {Object}
     */
    reverse: {
        handler: cryptCmd(function(s, payload, callback) {
            callback(null, s.split('').reverse().join(''));
        }),
        defaults: {},
        params: [],
        randomPickable: true,
        description: 'Reverse input.'
    },
    /**
     * XOR with block chaining
     * @type {Object}
     */
    xorbc: {
        handler: cryptCmd(xorbc(false)),
        defaults: {key: '42', iv: false},
        params: ['[key', '[iv'],
        description: 'XOR with block chaining.'
    },
    /**
     * Reverse XOR with block chaining
     * @type {Object}
     */
    rxorbc: {
        handler: cryptCmd(xorbc(true)),
        defaults: {key: '42', iv: false},
        params: ['[key', '[iv'],
        description: 'reverse XOR with block chaining.'
    },
    /**
     * Random other command
     * @type {Object}
     */
    random: {
        handler: function(payload, callback) {
            var keys = Object.keys(exports.commands).filter(function(k) {
                return exports.commands[k].randomPickable;
            });
            var id = Math.floor(keys.length * Math.random());
            payload.$command = 'random:' + keys[id];
            exports.commands[keys[id]].handler(payload, callback);
        },
        defaults: {},
        params: [],
        description: 'Random encryption.'
    }
};

/**
 * Helper to chain encryptions
 * @param  {function} handler The handler
 * @return {function} A curried version of that handler, assuming this author is using the word "curried" correctly
 */
function cryptCmd(handler) {
    return function(payload, callback) {
        discourse.log('Encrypt ' + (payload.$draft ? 'draft' : 'post')
                + ' with ' + payload.$command);
        handler(payload.$draft || payload.$post.cleaned,
                payload,
                function(err, msg, log) {
                    callback(err, {
                        replaceMsg: true,
                        msg: msg,
                        log: [log || payload.$command]
                    });
                });
    };
}

/**
 * xorbc implementatioon. Homegrown and weak as shit but who cares
 * @param  {boolean} decrypt - Whether this should be decrypted or not
 */
function xorbc(decrypt) {
    return function(s, payload, callback) {
        /* Key is always passed as argument. */
        var key = toCharCodes(payload.key);
        /* IV is passed, or zero */
        var iv = payload.iv ? toCharCodes(payload.iv) : zeroArray(key.length);
        if (key.length !== iv.length) {
            return callback('Key and IV must be the same length');
        }
        var log = payload.$command + '(key: ' + JSON.stringify(key)
                + ', iv: ' + JSON.stringify(iv) + ')';
        callback(null,
            toCharCodes(s).map( function(c, i) {
                /* Calculate key for this byte */
                var o = i % key.length,
                    k = key[o] ^ iv[o];
                /* Next block IV is this block's plaintext */
                if (decrypt) {
                    iv[o] = c ^ k;
                    return iv[o];
                } else {
                    iv[o] = c;
                    return iv[o] ^ k;
                }
            }).map( function(c) {
                return String.fromCharCode(c);
            }).join(''),
            log
        );
    };
}

/**
 * Convert string to array of character codes
 * @param  {string} s The string
 * @return {Array} an array of character codes
 */
function toCharCodes(s) {
    return s.split('').map(function(c) {
        return c.charCodeAt(0);
    });
}

/**
 * Create array with constant value
 * @param  {Number} l The length of the array to make
 * @return {Array} an array of all 0s
 */
function zeroArray(l) {
    var a = [];
    for (var i = 0; i < l; i++) {
        a.push(0);
    }
    return a;
}

/**
 * Use a random encryption when PMed/mentioned/replied without command
 * @param {string} type - The type of event. Only responds if this is 'mentioned'
 * @param {string} notification - The notification to respond to
 * @param {string} topic - Unused.
 * @param {string} post - The post the notification was for
 * @param {function} callback - The callback to notify when processing is complete.
 */
exports.onNotify = function (type, notification, topic, post, callback) {
    if (!post || !post.cleaned ||
        ['private_message', 'mentioned', 'replied'].indexOf(type) === -1) {
        return callback();
    }
    discourse.log('Randomly encrypting post');

    exports.commands.random.handler({
        $post: post,
        $type: type,
        $command: 'random'
    }, function(_, msg) {
        var text = msg.msg + '\n\n<small>Filed under: ' + msg.log.join(' → ');
        discourse.createPost(notification.topic_id,
            notification.post_number, text, function() {
                callback(true);
            });
    });
};

/**
 * Bootstrap the module
 * @param  {string} browser - discourse.
 * @param  {object} config - The configuration to use
 */
exports.begin = function begin(browser) {
    discourse = browser;
};
<|endoftext|>


<commit_before>let sortEach = {};

sortEach['sass import'] = function (that, element) {
  element.value.sort(smartSort());
};

sortEach.selector = function (that, element) {
  element.selector.sort(smartSort());
};

module.exports = sortEach;
<commit_msg>Added require for 'sortEach'<commit_after>const smartSort = require('../../vendor/smartSort');

let sortEach = {};

sortEach['sass import'] = function (that, element) {
  element.value.sort(smartSort());
};

sortEach.selector = function (that, element) {
  element.selector.sort(smartSort());
};

module.exports = sortEach;
<|endoftext|>


<commit_before>/**
 * Created by AlexanderC on 3/14/16.
 */

'use strict';

import dynamoDBThroughput from 'dynamodb-throughput';

export class AutoScaleDynamoDB {
  /**
   * @param {AWS.DynamoDB|AWS.DynamoDB.DocumentClient|*} dynamoDb
   * @param {AWS.DynamoDB|AWS.DynamoDB.DocumentClient|*} dynamoDbDocumentClient
   */
  constructor(dynamoDb, dynamoDbDocumentClient) {
    this._dynamoDb = dynamoDb;
    this._dynamoDbDocumentClient = dynamoDbDocumentClient;

    this._increasedFor = {};
  }

  /**
   * @returns {AWS.DynamoDB|AWS.DynamoDB.DocumentClient|*}
   */
  extend() {
    AutoScaleDynamoDB.PROXIED_METHODS.forEach((method) => {
      let originalMethod = `__deep_db_AS_${method}__`;

      this._dynamoDbDocumentClient[originalMethod] = this._dynamoDbDocumentClient[method];
      this._dynamoDbDocumentClient[method] = (payload, originalCb) => {
        let cb = this._decorate(method, payload, originalCb);

        this._dynamoDbDocumentClient[originalMethod](payload, cb);
      };
    });

    this._dynamoDbDocumentClient[AutoScaleDynamoDB.DEEP_DB_DECORATOR_FLAG] = true;

    return this._dynamoDbDocumentClient;
  }

  /**
   * @param {String} method
   * @param {Object} payload
   * @param {Function} originalCb
   * @returns {Function}
   * @private
   */
  _decorate(method, payload, originalCb) {
    return (error, data) => {
      if (error && error.code === AutoScaleDynamoDB.THROUGHPUT_EXCEEDED_ERROR) {
        let originalError = error;
        let table = AutoScaleDynamoDB._getTableNameFromPayload(method, payload);

        if (!table) {
          console.error(`Unable to find table name in payload while increasing throughput`);
          originalCb(originalError, data);
          return;
        } else if (this._increasedFor[table] &&
          this._increasedFor[table] >= AutoScaleDynamoDB.MAX_INCREASE_NUM_PER_TABLE) {

          console.error(`The table '${table}' capacity increase count exceeded ${this._increasedFor[table]}`);
          originalCb(originalError, data);
          return;
        }

        let throughput = dynamoDBThroughput(table, {
          endpoint: this._dynamoDb.config.endpoint,
          region: this._dynamoDb.config.region,
          credentials: this._dynamoDb.config.credentials,
        });

        throughput.tableInfo((error, info) => {
          if (error) {
            console.error(`Failed on gather information about table '${table}': ${error}`);

            originalCb(originalError, data);
            return;
          }

          let increasePayload = {};
          let increaseType = AutoScaleDynamoDB.increaseType(method);
          increasePayload[increaseType] = Math.ceil(parseInt(info.main[increaseType]) *
            AutoScaleDynamoDB.PROVISION_INCREASE_COEFFICIENT);

          throughput.setCapacity(increasePayload, (error) => {
            if (error) {
              if (this._isResourceInUseError(error)) {
                console.error(`'${table}' is already in use: ${error}`);

                setTimeout(
                  this._dynamoDbDocumentClient[method].bind(this),
                  500, // increasing IOPS runs ~ 500ms
                  payload, originalCb
                );

                return;
              }

              console.error(`Failed on increase throughput for table '${table}': ${error}`);
              originalCb(originalError, data);
              return;
            }

            this._increasedFor[table] = (this._increasedFor[table] || 0) + 1;

            console.log(`The table '${table}' throughput increased by ${increasePayload[increaseType]}`);
            this._dynamoDbDocumentClient[method](payload, originalCb);
          });
        });

        return;
      }

      originalCb(error, data);
    };
  }

  /**
   * @param {Error} error
   * @returns {String}
   * @private
   */
  _isResourceInUseError(error) {
    return error.name === 'ResourceInUseException';
  }

  /**
   * @param {String} method
   * @returns {String}
   */
  static increaseType(method) {
    let type = null;

    switch (method) {
      case 'get':
      case 'scan':
      case 'query':
        type = 'read';
        break;
      case 'put':
      case'delete':
      case 'update':
        type = 'write';
        break;
      default: throw new Error(`Unknown DynamoDB autoscale method '${method}'`);
    }

    return type;
  }

  /**
   * @param {String} method
   * @param {Object} payload
   * @returns {String}
   * @private
   */
  static _getTableNameFromPayload(method, payload) {
    let tableName = null;

    switch (method) {
      case 'get':
      case 'scan':
      case 'query':
      case 'put':
      case 'delete':
      case 'update':
        tableName = payload.TableName;
        break;
      default: throw new Error(`Unknown DynamoDB autoscale method '${method}'`);
    }

    return tableName;
  }

  /**
   * @returns {AWS.DynamoDB|AWS.DynamoDB.DocumentClient|*}
   */
  get dynamoDbDocumentClient() {
    return this._dynamoDbDocumentClient;
  }

  /**
   * @returns {AWS.DynamoDB|AWS.DynamoDB.DocumentClient|*}
   */
  get dynamoDb() {
    return this._dynamoDb;
  }

  /**
   * @returns {String[]}
   */
  static get PROXIED_METHODS() {
    return [
      'get', 'scan', 'query',
      'put', 'delete', 'update',
    ];
  }

  /**
   * @returns {Number}
   */
  static get MAX_INCREASE_NUM_PER_TABLE() {
    return 3;
  }

  /**
   * @returns {Number}
   */
  static get PROVISION_INCREASE_COEFFICIENT() {
    return 1.3;
  }

  /**
   * @returns {String}
   */
  static get DEEP_DB_DECORATOR_FLAG() {
    return '__deep_db_decorator__';
  }

  /**
   * @see https://github.com/aws/aws-sdk-js/blob/master/apis%2Fdynamodb-2012-08-10.normal.json
   * @returns {String}
   */
  static get THROUGHPUT_EXCEEDED_ERROR() {
    return 'ProvisionedThroughputExceededException';
  }
}
<commit_msg>Update AutoScaleDynamoDB.js<commit_after>/**
 * Created by AlexanderC on 3/14/16.
 */

'use strict';

import dynamoDBThroughput from 'dynamodb-throughput';

export class AutoScaleDynamoDB {
  /**
   * @param {AWS.DynamoDB|AWS.DynamoDB.DocumentClient|*} dynamoDb
   * @param {AWS.DynamoDB|AWS.DynamoDB.DocumentClient|*} dynamoDbDocumentClient
   */
  constructor(dynamoDb, dynamoDbDocumentClient) {
    this._dynamoDb = dynamoDb;
    this._dynamoDbDocumentClient = dynamoDbDocumentClient;

    this._increasedFor = {};
  }

  /**
   * @returns {AWS.DynamoDB|AWS.DynamoDB.DocumentClient|*}
   */
  extend() {
    AutoScaleDynamoDB.PROXIED_METHODS.forEach((method) => {
      let originalMethod = `__deep_db_AS_${method}__`;

      this._dynamoDbDocumentClient[originalMethod] = this._dynamoDbDocumentClient[method];
      this._dynamoDbDocumentClient[method] = (payload, originalCb) => {
        let cb = this._decorate(method, payload, originalCb);

        this._dynamoDbDocumentClient[originalMethod](payload, cb);
      };
    });

    this._dynamoDbDocumentClient[AutoScaleDynamoDB.DEEP_DB_DECORATOR_FLAG] = true;

    return this._dynamoDbDocumentClient;
  }

  /**
   * @param {String} method
   * @param {Object} payload
   * @param {Function} originalCb
   * @returns {Function}
   * @private
   */
  _decorate(method, payload, originalCb) {
    return (error, data) => {
      if (error && error.code === AutoScaleDynamoDB.THROUGHPUT_EXCEEDED_ERROR) {
        let originalError = error;
        let table = AutoScaleDynamoDB._getTableNameFromPayload(method, payload);

        if (!table) {
          console.error(`Unable to find table name in payload while increasing throughput`);
          originalCb(originalError, data);
          return;
        } else if (this._increasedFor[table] &&
          this._increasedFor[table] >= AutoScaleDynamoDB.MAX_INCREASE_NUM_PER_TABLE) {

          console.error(`The table '${table}' capacity increase count exceeded ${this._increasedFor[table]}`);
          originalCb(originalError, data);
          return;
        }

        let throughput = dynamoDBThroughput(table, {
          endpoint: this._dynamoDb.config.endpoint,
          region: this._dynamoDb.config.region,
          credentials: this._dynamoDb.config.credentials,
        });

        throughput.tableInfo((error, info) => {
          if (error) {
            console.error(`Failed on gather information about table '${table}': ${error}`);

            originalCb(originalError, data);
            return;
          }

          let increasePayload = {};
          let increaseType = AutoScaleDynamoDB.increaseType(method);
          increasePayload[increaseType] = Math.ceil(parseInt(info.main[increaseType]) *
            AutoScaleDynamoDB.PROVISION_INCREASE_COEFFICIENT);

          throughput.setCapacity(increasePayload, (error) => {
            if (error) {
              if (error.name === AutoScaleDynamoDB.RESOURCE_IN_USE_ERROR) {
                console.error(`'${table}' is already in use: ${error}`);

                setTimeout(
                  this._dynamoDbDocumentClient[method].bind(this),
                  500, // increasing IOPS runs ~ 500ms
                  payload, originalCb
                );

                return;
              }

              console.error(`Failed on increase throughput for table '${table}': ${error}`);
              originalCb(originalError, data);
              return;
            }

            this._increasedFor[table] = (this._increasedFor[table] || 0) + 1;

            console.log(`The table '${table}' throughput increased by ${increasePayload[increaseType]}`);
            this._dynamoDbDocumentClient[method](payload, originalCb);
          });
        });

        return;
      }

      originalCb(error, data);
    };
  }

  /**
   * @param {String} method
   * @returns {String}
   */
  static increaseType(method) {
    let type = null;

    switch (method) {
      case 'get':
      case 'scan':
      case 'query':
        type = 'read';
        break;
      case 'put':
      case'delete':
      case 'update':
        type = 'write';
        break;
      default: throw new Error(`Unknown DynamoDB autoscale method '${method}'`);
    }

    return type;
  }

  /**
   * @param {String} method
   * @param {Object} payload
   * @returns {String}
   * @private
   */
  static _getTableNameFromPayload(method, payload) {
    let tableName = null;

    switch (method) {
      case 'get':
      case 'scan':
      case 'query':
      case 'put':
      case 'delete':
      case 'update':
        tableName = payload.TableName;
        break;
      default: throw new Error(`Unknown DynamoDB autoscale method '${method}'`);
    }

    return tableName;
  }

  /**
   * @returns {AWS.DynamoDB|AWS.DynamoDB.DocumentClient|*}
   */
  get dynamoDbDocumentClient() {
    return this._dynamoDbDocumentClient;
  }

  /**
   * @returns {AWS.DynamoDB|AWS.DynamoDB.DocumentClient|*}
   */
  get dynamoDb() {
    return this._dynamoDb;
  }

  /**
   * @returns {String[]}
   */
  static get PROXIED_METHODS() {
    return [
      'get', 'scan', 'query',
      'put', 'delete', 'update',
    ];
  }

  /**
   * @returns {Number}
   */
  static get MAX_INCREASE_NUM_PER_TABLE() {
    return 3;
  }

  /**
   * @returns {Number}
   */
  static get PROVISION_INCREASE_COEFFICIENT() {
    return 1.3;
  }

  /**
   * @returns {String}
   */
  static get DEEP_DB_DECORATOR_FLAG() {
    return '__deep_db_decorator__';
  }

  /**
   * @see https://github.com/aws/aws-sdk-js/blob/master/apis%2Fdynamodb-2012-08-10.normal.json
   * @returns {String}
   */
  static get THROUGHPUT_EXCEEDED_ERROR() {
    return 'ProvisionedThroughputExceededException';
  }

  /**
   * @returns {String}
   */
  static get RESOURCE_IN_USE_ERROR() {
    return 'ResourceInUseException';
  }
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOBrowseControllerView',
  extends: 'foam.u2.View',

  documentation: `
    The inline DAO controller for a collection of instances of a model that can
    switch between multiple views
  `,

  imports: [
    'auth',
    'stack'
  ],

  exports: [
    'as browseCtrl'
  ],

  requires: [
    'foam.comics.v2.DAOBrowserView',
    'foam.u2.borders.CardBorder',
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.view.IconChoiceView'
  ],

  css: `
    ^container {
      padding: 32px;
    }

    ^header-container {
      padding-bottom: 32px;
      align-items: center;
    }

    ^browse-title {
      font-size: 36px;
      font-weight: 600;
      line-height: 1.33;
      color: #1e1f21;
    }

    ^browse-subtitle {
      font-size: 18px;
      line-height: 1.56;
      color: #5e6061;
      width: 50%;
    }

    ^altview-container {
      position: absolute;
      right: 0;
      padding: 12px 16px 0 0;
    }
  `,

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'config',
      factory: function() {
        return foam.comics.v2.DAOControllerConfig.create({ dao: this.data });
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'browseView',
      expression: function(config$browseViews) {
        return config$browseViews && config$browseViews.length
          ? config$browseViews[0].view
          : this.DAOBrowserView
          ;
      }
    }
  ],
  actions: [
    {
      name: 'create',
      isEnabled: function(config, data) {
        if ( config.CRUDEnabledActionsAuth && config.CRUDEnabledActionsAuth.isEnabled ) {
          try {
            let permissionString = config.CRUDEnabledActionsAuth.enabledActionsAuth.permissionFactory(foam.nanos.ruler.Operations.CREATE, data);

            return this.auth.check(null, permissionString);
          } catch(e) {
            return false;
          }
        }
        return true;
      },
      isAvailable: function(config) {
        try {
          return config.createPredicate.f();
        } catch(e) {
          return false;
        }
      },
      code: function() {
        if ( ! this.stack ) return;
        this.stack.push({
          class: 'foam.comics.v2.DAOCreateView',
          data: this.data.of.create({ mode: 'create' }, this),
          config$: this.config$,
          of: this.data.of
        }, this.__subContext__);
      }
    }
  ],
  methods: [
    function initE() {
    this.SUPER();

    var self = this;

      this.addClass(this.myClass())
      .add(this.slot(function(data, config, config$browseBorder, config$browseViews, config$browseTitle, config$browseSubtitle) {
        return self.E()
          .start(self.Rows)
            .addClass(self.myClass('container'))
              .start()
                .addClass(self.myClass('header-container'))
                .start(self.Cols)
                  .start()
                    .addClass(self.myClass('browse-title'))
                    .add(config$browseTitle)
                  .end()
                  .startContext({ data: self }).tag(self.CREATE).endContext()
                .end()
                .callIf(config$browseSubtitle.length > 0, function() {
                  this
                    .start()
                      .addClass(self.myClass('browse-subtitle'))
                      .add(config$browseSubtitle)
                    .end();
                })
              .end()
            .start(self.CardBorder)
              .style({ position: 'relative' })
              .start(config$browseBorder)
                .callIf(config$browseViews.length > 1 && config.cannedQueries.length > 0, function() {
                  this
                    .start(self.IconChoiceView, {
                      choices:config$browseViews.map(o => [o.view, o.icon]),
                      data$: self.browseView$
                    })
                      .addClass(self.myClass('altview-container'))
                    .end();
                })
                .add(self.slot(function(browseView) {
                  return self.E().tag(browseView, {data: data, config: config});
                }))
              .end()
            .end()
          .end();
      }));
    }
  ]
});
<commit_msg>Removed code from playing around<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOBrowseControllerView',
  extends: 'foam.u2.View',

  documentation: `
    The inline DAO controller for a collection of instances of a model that can
    switch between multiple views
  `,

  imports: [
    'auth',
    'stack'
  ],

  requires: [
    'foam.comics.v2.DAOBrowserView',
    'foam.u2.borders.CardBorder',
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.view.IconChoiceView'
  ],

  css: `
    ^container {
      padding: 32px;
    }

    ^header-container {
      padding-bottom: 32px;
      align-items: center;
    }

    ^browse-title {
      font-size: 36px;
      font-weight: 600;
      line-height: 1.33;
      color: #1e1f21;
    }

    ^browse-subtitle {
      font-size: 18px;
      line-height: 1.56;
      color: #5e6061;
      width: 50%;
    }

    ^altview-container {
      position: absolute;
      right: 0;
      padding: 12px 16px 0 0;
    }
  `,

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'config',
      factory: function() {
        return foam.comics.v2.DAOControllerConfig.create({ dao: this.data });
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'browseView',
      expression: function(config$browseViews) {
        return config$browseViews && config$browseViews.length
          ? config$browseViews[0].view
          : this.DAOBrowserView
          ;
      }
    }
  ],
  actions: [
    {
      name: 'create',
      isEnabled: function(config, data) {
        if ( config.CRUDEnabledActionsAuth && config.CRUDEnabledActionsAuth.isEnabled ) {
          try {
            let permissionString = config.CRUDEnabledActionsAuth.enabledActionsAuth.permissionFactory(foam.nanos.ruler.Operations.CREATE, data);

            return this.auth.check(null, permissionString);
          } catch(e) {
            return false;
          }
        }
        return true;
      },
      isAvailable: function(config) {
        try {
          return config.createPredicate.f();
        } catch(e) {
          return false;
        }
      },
      code: function() {
        if ( ! this.stack ) return;
        this.stack.push({
          class: 'foam.comics.v2.DAOCreateView',
          data: this.data.of.create({ mode: 'create' }, this),
          config$: this.config$,
          of: this.data.of
        }, this.__subContext__);
      }
    }
  ],
  methods: [
    function initE() {
    this.SUPER();

    var self = this;

      this.addClass(this.myClass())
      .add(this.slot(function(data, config, config$browseBorder, config$browseViews, config$browseTitle, config$browseSubtitle) {
        return self.E()
          .start(self.Rows)
            .addClass(self.myClass('container'))
              .start()
                .addClass(self.myClass('header-container'))
                .start(self.Cols)
                  .start()
                    .addClass(self.myClass('browse-title'))
                    .add(config$browseTitle)
                  .end()
                  .startContext({ data: self }).tag(self.CREATE).endContext()
                .end()
                .callIf(config$browseSubtitle.length > 0, function() {
                  this
                    .start()
                      .addClass(self.myClass('browse-subtitle'))
                      .add(config$browseSubtitle)
                    .end();
                })
              .end()
            .start(self.CardBorder)
              .style({ position: 'relative' })
              .start(config$browseBorder)
                .callIf(config$browseViews.length > 1 && config.cannedQueries.length > 0, function() {
                  this
                    .start(self.IconChoiceView, {
                      choices:config$browseViews.map(o => [o.view, o.icon]),
                      data$: self.browseView$
                    })
                      .addClass(self.myClass('altview-container'))
                    .end();
                })
                .add(self.slot(function(browseView) {
                  return self.E().tag(browseView, {data: data, config: config});
                }))
              .end()
            .end()
          .end();
      }));
    }
  ]
});
<|endoftext|>