<commit_before>/*
 * 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.
 */
define([
    "jquery",
    "backbone",
    "d3"
],
function(
){
    var TreeDependencyGraphView = Backbone.View.extend({
        initialize:function(){
        },
        render:function(){
            var diameter = 760;
            var tree = d3.layout.tree()
                .size([360, diameter / 2 - 120])
                .separation(function(a, b) {
                    return (a.parent == b.parent ? 1 : 2) / a.depth;
                });
            var diagonal = d3.svg.diagonal.radial()
                .projection(function(d) {
                    var x = (d.x/180.0)*Math.PI;
                    return [d.y, (d.x / 180) * Math.PI];
                });
            var svg = d3.select(this.$el.get(0)).append("svg")
                .attr("width", diameter)
                .attr("height", diameter - 150)
                .append("g")
                .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
            var nodes = tree.nodes(this.model.toJSON());
            var links = tree.links(nodes);
            var link = svg.selectAll(".link")
                .data(links)
                .enter().append("path")
                .attr("class", "link")
                .attr("d", diagonal);
            var node = svg.selectAll(".node")
                .data(nodes)
                .enter().append("g")
                .attr("class", "node")
                .attr("transform", function(d) {
                    return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
                });
            node.append("circle")
                .attr("r", 4.5);
            node.append("text")
                .attr("dy", ".31em")
                .attr("text-anchor", function(d) {
                    return d.x < 180 ? "start" : "end";
                })
                .attr("transform", function(d) {
                    return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)";
                })
                .text(function(d) {
                    return d.name;
                });
            return this;
        }
    });
    var TreeForceDirectedGraphView = Backbone.View.extend({
        initialize:function(){
            _.bindAll(this);
        },
        render:function(){
            var w = 960,
                h = 500,
                node,
                link,
                root;

            var force = d3.layout.force()
                .on("tick", this.tick)
                .size([w, h]);

            var vis = d3.select(this.$el.get(0)).append("svg")
                .attr("width", w)
                .attr("height", h);

            this.update();
            return this;
        },
        // Returns a list of all nodes under the root.
        flatten:function(root) {
            var nodes = [], i = 0;

            function recurse(node) {
                if (node.children) node.children.forEach(recurse);
                if (!node.id) node.id = ++i;
                nodes.push(node);
            }

            recurse(root);
            return nodes;
        },
        // Toggle children on click.
        click: function(d) {
            if (d.children) {
                d._children = d.children;
                d.children = null;
            } else {
                d.children = d._children;
                d._children = null;
            }
            this.update();
        },
        // Color leaf nodes orange, and packages white or blue.
        color:function(d) {
            return d._children ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
        },
        tick: function() {
            link.attr("x1", function(d) { return d.source.x; })
                .attr("y1", function(d) { return d.source.y; })
                .attr("x2", function(d) { return d.target.x; })
                .attr("y2", function(d) { return d.target.y; });

            node.attr("cx", function(d) { return d.x; })
                .attr("cy", function(d) { return d.y; });
        },
        update: function() {
            var w = 960,
                h = 500,
                node,
                link,
                root;

            var force = d3.layout.force()
                .on("tick", this.tick)
                .size([w, h]);

            var vis = d3.select(this.$el.get(0)).append("svg")
                .attr("width", w)
                .attr("height", h);
            var root = this.model.toJSON();
            var nodes = this.flatten(root),
                links = d3.layout.tree().links(nodes);

            // Restart the force layout.
            force.nodes(nodes)
                .links(links)
                .start();

            // Update the links…
            link = vis.selectAll("line.link")
                .data(links, function(d) { return d.target.id; });

            // Enter any new links.
            link.enter().insert("svg:line", ".node")
                .attr("class", "link")
                .attr("x1", function(d) { return d.source.x; })
                .attr("y1", function(d) { return d.source.y; })
                .attr("x2", function(d) { return d.target.x; })
                .attr("y2", function(d) { return d.target.y; });

            // Exit any old links.
            link.exit().remove();

            // Update the nodes…
            node = vis.selectAll("circle.node")
                .data(nodes, function(d) { return d.id; })
                .style("fill", this.color);

            node.append("text")
                .attr("dx", 12)
                .attr("dy", ".35em")
                .text(function(d) { return d.name });
            // Enter any new nodes.
            node.enter().append("svg:circle")
                .attr("class", "node")
                .attr("cx", function(d) { return d.x; })
                .attr("cy", function(d) { return d.y; })
                .attr("r", function(d) { return Math.sqrt(d.size) / 10 || 30; })
                .style("fill", this.color)
                .on("click", this.click)
                .call(force.drag);

            // Exit any old nodes.
            node.exit().remove();
        }
    });
    var ForceDirectedGraphView = Backbone.View.extend({
        initialize:function(){

        },
        render:function(){
            var width = 960,height = 500;
            var color = d3.scale.category20();
            var force = d3.layout.force()
                .charge(-120)
                .linkDistance(60)
                .size([width, height]);
            var svg = d3.select(this.$el.get(0)).append("svg")
                .attr("width", width)
                .attr("height", height);
            var graph = this.model.toJSON();
            force.nodes(graph.nodes)
                .links(graph.links)
                .start();
            var link = svg.selectAll(".link")
                .data(graph.links)
                .enter().append("line")
                .attr("class", "link")
                .style("stroke-width", function(d) { return Math.sqrt(d.value); });
            var node = svg.selectAll(".node")
                .data(graph.nodes)
                .enter().append("circle")
                .attr("class", "node")
                .attr("r", 10)
                .style("fill", function(d) { return color(d.group); })
                .call(force.drag);
            node.append("title")
                .text(function(d) { return d.name; });
            node.append("text")
                .text(function(d) { return d.name; });
            force.on("tick", function() {
                link.attr("x1", function(d) { return d.source.x; })
                    .attr("y1", function(d) { return d.source.y; })
                    .attr("x2", function(d) { return d.target.x; })
                    .attr("y2", function(d) { return d.target.y; });
                node.attr("cx", function(d) { return d.x; })
                    .attr("cy", function(d) { return d.y; });
            });
            return this;
        }
    });
    var TreeDependencyGraphView = Backbone.View.extend({
        initialize:function(){
        },
        render:function(){
            var diameter = 760;
            var tree = d3.layout.tree()
                .size([360, diameter / 2 - 120])
                .separation(function(a, b) {
                    return (a.parent == b.parent ? 1 : 2) / a.depth;
                });
            var diagonal = d3.svg.diagonal.radial()
                .projection(function(d) {
                    var x = (d.x/180.0)*Math.PI;
                    return [d.y, (d.x / 180) * Math.PI];
                });
            var svg = d3.select(this.$el.get(0)).append("svg")
                .attr("width", diameter)
                .attr("height", diameter - 150)
                .append("g")
                .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
            var nodes = tree.nodes(this.model.toJSON());
            var links = tree.links(nodes);
            var link = svg.selectAll(".link")
                .data(links)
                .enter().append("path")
                .attr("class", "link")
                .attr("d", diagonal);
            var node = svg.selectAll(".node")
                .data(nodes)
                .enter().append("g")
                .attr("class", "node")
                .attr("transform", function(d) {
                    return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
                });
            node.append("circle")
                .attr("r", 4.5);
            node.append("text")
                .attr("dy", ".31em")
                .attr("text-anchor", function(d) {
                    return d.x < 180 ? "start" : "end";
                })
                .attr("transform", function(d) {
                    return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)";
                })
                .text(function(d) {
                    return d.name;
                });
            return this;
        }
    });
    var ForceDirectedGraphModel = Backbone.Model.extend({
        initialize:function(opts){
            _.bindAll(this);
            this.jobId=opts.jobId;
        },
        url:function(){
            return "/job/dependencies/sinks?id="+this.jobId;
        },
        parse:function(data){
            var nodeMap = {}, nodeList=[];
            _.each(data.nodes,function(node,idx){
                nodeMap[node.id]=nodeList.length;
                nodeList.push({
                    name:node.id,
                    group:1
                });
            });
            var edges = [];
            _.each(data.edges,function(edge){
                edges.push({
                    source:nodeMap[edge.source],
                    target:nodeMap[edge.sink],
                    value:1
                });
            });
            return {
                nodes:nodeList,
                links:edges
            };
        }
    });
    var TreeGraphModel = Backbone.Model.extend({
        initialize:function(opts){
            _.bindAll(this,'url','parse','buildGraph');
            this.jobId=opts.jobId;
        },
        url:function(){
            return "/job/dependencies/sinks?id="+this.jobId;
        },
        parse:function(data){
            var nodes = {};
            _.each(data.nodes,function(node){
                nodes[node.id]=node;
            });
            var edges = {};
            _.each(data.edges,function(edge){
                if(_.isUndefined(edges[edge.source])){
                    edges[edge.source]=[edge.sink];
                }
                else{
                    edges[edge.source].push(edge.sink);
                }
            });
            var graph = this.buildGraph(this.jobId,nodes,edges);
            return graph;
        },
        buildGraph:function(nodeId,nodes,edges){
            var childrenIds = edges[nodeId];
            var childrenNodes = [],self=this;
            _.each(childrenIds,function(child){
                var childNode = self.buildGraph(child,nodes,edges);
                childrenNodes.push(childNode);
            });
            return {
                name:nodeId.substring(0,10),
                children: childrenNodes,
                size:1
            };
        }
    });
    return {
        TreeForceDirectedGraphView:TreeForceDirectedGraphView,
        ForceDirectedGraphView:ForceDirectedGraphView,
        TreeDependencyGraphVie:TreeDependencyGraphView,
        ForceDirectedGraphModel:ForceDirectedGraphModel,
        TreeGraphModel:TreeGraphModel,
        TreeDependencyGraphView:TreeDependencyGraphView
    };
});<commit_msg>make all dependency text render rightside-up<commit_after>/*
 * 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.
 */
define([
    "jquery",
    "backbone",
    "d3"
],
function(
){
    var TreeForceDirectedGraphView = Backbone.View.extend({
        initialize:function(){
            _.bindAll(this);
        },
        render:function(){
            var w = 960,
                h = 500,
                node,
                link,
                root;

            var force = d3.layout.force()
                .on("tick", this.tick)
                .size([w, h]);

            var vis = d3.select(this.$el.get(0)).append("svg")
                .attr("width", w)
                .attr("height", h);

            this.update();
            return this;
        },
        // Returns a list of all nodes under the root.
        flatten:function(root) {
            var nodes = [], i = 0;

            function recurse(node) {
                if (node.children) node.children.forEach(recurse);
                if (!node.id) node.id = ++i;
                nodes.push(node);
            }

            recurse(root);
            return nodes;
        },
        // Toggle children on click.
        click: function(d) {
            if (d.children) {
                d._children = d.children;
                d.children = null;
            } else {
                d.children = d._children;
                d._children = null;
            }
            this.update();
        },
        // Color leaf nodes orange, and packages white or blue.
        color:function(d) {
            return d._children ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
        },
        tick: function() {
            link.attr("x1", function(d) { return d.source.x; })
                .attr("y1", function(d) { return d.source.y; })
                .attr("x2", function(d) { return d.target.x; })
                .attr("y2", function(d) { return d.target.y; });

            node.attr("cx", function(d) { return d.x; })
                .attr("cy", function(d) { return d.y; });
        },
        update: function() {
            var w = 960,
                h = 500,
                node,
                link,
                root;

            var force = d3.layout.force()
                .on("tick", this.tick)
                .size([w, h]);

            var vis = d3.select(this.$el.get(0)).append("svg")
                .attr("width", w)
                .attr("height", h);
            var root = this.model.toJSON();
            var nodes = this.flatten(root),
                links = d3.layout.tree().links(nodes);

            // Restart the force layout.
            force.nodes(nodes)
                .links(links)
                .start();

            // Update the links…
            link = vis.selectAll("line.link")
                .data(links, function(d) { return d.target.id; });

            // Enter any new links.
            link.enter().insert("svg:line", ".node")
                .attr("class", "link")
                .attr("x1", function(d) { return d.source.x; })
                .attr("y1", function(d) { return d.source.y; })
                .attr("x2", function(d) { return d.target.x; })
                .attr("y2", function(d) { return d.target.y; });

            // Exit any old links.
            link.exit().remove();

            // Update the nodes…
            node = vis.selectAll("circle.node")
                .data(nodes, function(d) { return d.id; })
                .style("fill", this.color);

            node.append("text")
                .attr("dx", 12)
                .attr("dy", ".35em")
                .text(function(d) { return d.name });
            // Enter any new nodes.
            node.enter().append("svg:circle")
                .attr("class", "node")
                .attr("cx", function(d) { return d.x; })
                .attr("cy", function(d) { return d.y; })
                .attr("r", function(d) { return Math.sqrt(d.size) / 10 || 30; })
                .style("fill", this.color)
                .on("click", this.click)
                .call(force.drag);

            // Exit any old nodes.
            node.exit().remove();
        }
    });
    var ForceDirectedGraphView = Backbone.View.extend({
        initialize:function(){

        },
        render:function(){
            var width = 960,height = 500;
            var color = d3.scale.category20();
            var force = d3.layout.force()
                .charge(-120)
                .linkDistance(60)
                .size([width, height]);
            var svg = d3.select(this.$el.get(0)).append("svg")
                .attr("width", width)
                .attr("height", height);
            var graph = this.model.toJSON();
            force.nodes(graph.nodes)
                .links(graph.links)
                .start();
            var link = svg.selectAll(".link")
                .data(graph.links)
                .enter().append("line")
                .attr("class", "link")
                .style("stroke-width", function(d) { return Math.sqrt(d.value); });
            var node = svg.selectAll(".node")
                .data(graph.nodes)
                .enter().append("circle")
                .attr("class", "node")
                .attr("r", 10)
                .style("fill", function(d) { return color(d.group); })
                .call(force.drag);
            node.append("title")
                .text(function(d) { return d.name; });
            node.append("text")
                .text(function(d) { return d.name; });
            force.on("tick", function() {
                link.attr("x1", function(d) { return d.source.x; })
                    .attr("y1", function(d) { return d.source.y; })
                    .attr("x2", function(d) { return d.target.x; })
                    .attr("y2", function(d) { return d.target.y; });
                node.attr("cx", function(d) { return d.x; })
                    .attr("cy", function(d) { return d.y; });
            });
            return this;
        }
    });
    var TreeDependencyGraphView = Backbone.View.extend({
        initialize:function(){
        },
        render:function(){
            var diameter = 760;
            var tree = d3.layout.tree()
                .size([360, diameter / 2 - 120])
                .separation(function(a, b) {
                    return (a.parent == b.parent ? 1 : 2) / a.depth;
                });
            var diagonal = d3.svg.diagonal.radial()
                .projection(function(d) {
                    var x = (d.x/180.0)*Math.PI;
                    return [d.y, (d.x / 180) * Math.PI];
                });
            var svg = d3.select(this.$el.get(0)).append("svg")
                .attr("width", diameter)
                .attr("height", diameter - 150)
                .append("g")
                .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
            var nodes = tree.nodes(this.model.toJSON());
            var links = tree.links(nodes);
            var link = svg.selectAll(".link")
                .data(links)
                .enter().append("path")
                .attr("class", "link")
                .attr("d", diagonal);
            var node = svg.selectAll(".node")
                .data(nodes)
                .enter().append("g")
                .attr("class", "node")
                .attr("transform", function(d) {
                    return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
                });
            node.append("circle")
                .attr("r", 4.5);
            node.append("text")
                .attr("dy", ".31em")
                .attr("text-anchor", function(d) {
                    return "start";
                })
                .attr("transform", function(d) {
                    return "rotate(" + (90 - d.x) + ")translate(8)"
                })
                .text(function(d) {
                    return d.name;
                });
            return this;
        }
    });
    var ForceDirectedGraphModel = Backbone.Model.extend({
        initialize:function(opts){
            _.bindAll(this);
            this.jobId=opts.jobId;
        },
        url:function(){
            return "/job/dependencies/sinks?id="+this.jobId;
        },
        parse:function(data){
            var nodeMap = {}, nodeList=[];
            _.each(data.nodes,function(node,idx){
                nodeMap[node.id]=nodeList.length;
                nodeList.push({
                    name:node.id,
                    group:1
                });
            });
            var edges = [];
            _.each(data.edges,function(edge){
                edges.push({
                    source:nodeMap[edge.source],
                    target:nodeMap[edge.sink],
                    value:1
                });
            });
            return {
                nodes:nodeList,
                links:edges
            };
        }
    });
    var TreeGraphModel = Backbone.Model.extend({
        initialize:function(opts){
            _.bindAll(this,'url','parse','buildGraph');
            this.jobId=opts.jobId;
        },
        url:function(){
            return "/job/dependencies/sinks?id="+this.jobId;
        },
        parse:function(data){
            var nodes = {};
            _.each(data.nodes,function(node){
                nodes[node.id]=node;
            });
            var edges = {};
            _.each(data.edges,function(edge){
                if(_.isUndefined(edges[edge.source])){
                    edges[edge.source]=[edge.sink];
                }
                else{
                    edges[edge.source].push(edge.sink);
                }
            });
            var graph = this.buildGraph(this.jobId,nodes,edges);
            return graph;
        },
        buildGraph:function(nodeId,nodes,edges){
            var childrenIds = edges[nodeId];
            var childrenNodes = [],self=this;
            _.each(childrenIds,function(child){
                var childNode = self.buildGraph(child,nodes,edges);
                childrenNodes.push(childNode);
            });
            return {
                name:nodeId.substring(0,10),
                children: childrenNodes,
                size:1
            };
        }
    });
    return {
        TreeForceDirectedGraphView:TreeForceDirectedGraphView,
        ForceDirectedGraphView:ForceDirectedGraphView,
        ForceDirectedGraphModel:ForceDirectedGraphModel,
        TreeGraphModel:TreeGraphModel,
        TreeDependencyGraphView:TreeDependencyGraphView
    };
});<|endoftext|>


<commit_before>import { Component, PropTypes } from "react";
import ReactDOM from "react-dom";
import Debouncer from "./../../util/debounce";

const { span } = ReactDOM;

const Status = {
  PENDING: "pending",
  LOADING: "loading",
  LOADED: "loaded",
  FAILED: "failed",
};

export default class ImageLoader extends Component {
  static propTypes = {
    wrapper: PropTypes.func,
    className: PropTypes.string,
    style: PropTypes.object, // eslint-disable-line
    preloader: PropTypes.func,
    src: PropTypes.string,
    onLoad: PropTypes.func,
    onError: PropTypes.func,
    imgProps: PropTypes.object, // eslint-disable-line
    force: PropTypes.bool,
    renderElement: PropTypes.func,
    children: PropTypes.any, // eslint-disable-line
    imgProps: PropTypes.array, // eslint-disable-line
  };

  static defaultProps = {
    wrapper: span,
  };

  constructor(props) {
    super(props);
    this.state = { status: props.src ? Status.LOADING : Status.PENDING };
  }

  componentDidMount() {
    if (this.state.status === Status.LOADING) {
      this.createLoader();
    }
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.src !== nextProps.src) {
      this.setState({
        status: nextProps.src ? Status.LOADING : Status.PENDING,
      });
    }
  }

  componentDidUpdate() {
    if (this.state.status === Status.LOADING && !this.img) {
      this.createLoader();
    }
  }

  componentWillUnmount() {
    this.destroyLoader();
  }

  getClassName() {
    let className = `imageloader ${this.state.status}`;
    if (this.props.className) className = `${className} ${this.props.className}`;
    return className;
  }

  createLoader() {
    this.destroyLoader();  // We can only have one loader at a time.

    const makeImage = () => {
      this.img = new Image();
      this.img.onload = ::this.handleLoad;
      this.img.onerror = ::this.handleError;
      this.img.src = this.props.src;
    };

    if (Meteor.isServer) {
      makeImage();
      return;
    }

    // lazy load only if in view on client
    let el = this.node;
    el = el.children[0];

    const isElementInView = (e) => {
      const coords = e.getBoundingClientRect();
      return (
        // if item is left of the screen's left side
        Math.abs(coords.left) >= 0 &&
        // if item is within two screens
        Math.abs(coords.top) <= (window.innerHeight || document.documentElement.clientHeight) * 2
      );
    };

    const seeIfInView = () => {
      if (isElementInView(el)) {
        // callback to make sure user really intends to view content
        // prevents accidental firing on scrolling past
        const callback = () => {
          if (isElementInView(el)) {
            window.removeEventListener("scroll", this.debounce, false);
            makeImage();
            return;
          }

          // remove related event listener and add a new one back
          window.removeEventListener("scroll", this.debounce, false);
          window.addEventListener("scroll", this.debounce, false);
          return;
        };

        // SetTimeout to prevent false calls on scrolling
        setTimeout(callback, 300);

        // remove inital eventlistener to scope a new one inside the timeout function
        window.removeEventListener("scroll", this.debounce, false);

        return;
      }
    };

    if (isElementInView(el) || this.props.force) {
      makeImage();
      return;
    }

    this.debounce = new Debouncer(seeIfInView);
    window.addEventListener("scroll", this.debounce, false);
  }

  destroyLoader() {
    if (this.img) {
      this.img.onload = null;
      this.img.onerror = null;
      this.img = null;
    }

    if (this.debounce) {
      window.removeEventListener("scroll", this.debounce, false);
    }
  }

  handleLoad(event) {
    this.destroyLoader();
    this.setState({ status: Status.LOADED });

    if (this.props.onLoad) this.props.onLoad(event);
  }

  handleError(error) {
    this.destroyLoader();
    this.setState({ status: Status.FAILED });

    if (this.props.onError) this.props.onError(error);
  }

  renderImg() {
    const { src, imgProps } = this.props;
    const props = { src };

    imgProps.map((k) => {
      if ({}.hasOwnProperty.call(imgProps, "k")) {
        props[k] = imgProps[k];
      }
      return undefined;
    });

    return ;
  }

  render() {
    const wrapperProps = {
      className: this.getClassName(),
    };

    if (this.props.style) {
      const style = { ...this.props.style };
      delete style.backgroundImage;
      wrapperProps.style = style;

      if (this.state.status === Status.LOADED) {
        wrapperProps.style.backgroundImage = this.props.style.backgroundImage;
      }
    }

    const wrapperArgs = [wrapperProps];

    switch (this.state.status) {
      case Status.LOADED:
        if (this.props.renderElement) {
          wrapperArgs.push(this.props.renderElement());
        } else {
          wrapperArgs.push(this.renderImg());
        }
        break;

      case Status.FAILED:
        if (this.props.children) wrapperArgs.push(this.props.children);
        break;

      default:
        if (this.props.preloader) wrapperArgs.push(this.props.preloader());
        break;
    }

    return  (this.node = node)}>{this.props.wrapper(...wrapperArgs)};
  }
}
<commit_msg>fix issues with image loader<commit_after>import React, { Component, PropTypes } from "react";
import Debouncer from "./../../util/debounce";

const { span } = React.DOM;

const Status = {
  PENDING: "pending",
  LOADING: "loading",
  LOADED: "loaded",
  FAILED: "failed",
};

export default class ImageLoader extends Component {
  static propTypes = {
    wrapper: PropTypes.func,
    className: PropTypes.string,
    style: PropTypes.object,
    preloader: PropTypes.func,
    src: PropTypes.string,
    onLoad: PropTypes.func,
    onError: PropTypes.func,
    imgProps: PropTypes.object,
    force: PropTypes.bool,
    renderElement: PropTypes.func,
    children: PropTypes.object,
  };

  static defaultProps = {
    wrapper: span,
  };

  constructor(props) {
    super(props);
    this.state = { status: props.src ? Status.LOADING : Status.PENDING };
  }

  componentDidMount() {
    if (this.state.status === Status.LOADING) {
      this.createLoader();
    }
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.src !== nextProps.src) {
      this.setState({
        status: nextProps.src ? Status.LOADING : Status.PENDING,
      });
    }
  }

  componentDidUpdate() {
    if (this.state.status === Status.LOADING && !this.img) {
      this.createLoader();
    }
  }

  componentWillUnmount() {
    this.destroyLoader();
  }

  getClassName() {
    let className = `imageloader ${this.state.status}`;
    if (this.props.className) className = `${className} ${this.props.className}`;
    return className;
  }

  createLoader() {
    this.destroyLoader();  // We can only have one loader at a time.

    const makeImage = () => {
      this.img = new Image();
      this.img.onload = ::this.handleLoad;
      this.img.onerror = ::this.handleError;
      this.img.src = this.props.src;
    };

    if (Meteor.isServer) {
      makeImage();
      return;
    }

    // lazy load only if in view on client
    let el = this.loader;
    el = el.children[0];

    const isElementInView = (e) => {
      const coords = e.getBoundingClientRect();
      return (
        // if item is left of the screen's left side
        Math.abs(coords.left) >= 0 &&
        // if item is within two screens
        Math.abs(coords.top) <= (window.innerHeight || document.documentElement.clientHeight) * 2
      );
    };

    const seeIfInView = () => {
      if (isElementInView(el)) {
        // callback to make sure user really intends to view content
        // prevents accidental firing on scrolling past
        const callback = () => {
          if (isElementInView(el)) {
            window.removeEventListener("scroll", this.debounce, false);
            makeImage();
            return;
          }
          // remove related event listener and add a new one back
          window.removeEventListener("scroll", this.debounce, false);
          window.addEventListener("scroll", this.debounce, false);
          return;
        };
        // SetTimeout to prevent false calls on scrolling
        setTimeout(callback, 300);
        // remove inital eventlistener to scope a new one inside the timeout function
        window.removeEventListener("scroll", this.debounce, false);
        return;
      }
    };

    if (isElementInView(el) || this.props.force) {
      makeImage();
      return;
    }

    this.debounce = new Debouncer(seeIfInView);
    window.addEventListener("scroll", this.debounce, false);
  }

  destroyLoader() {
    if (this.img) {
      this.img.onload = null;
      this.img.onerror = null;
      this.img = null;
    }

    if (this.debounce) {
      window.removeEventListener("scroll", this.debounce, false);
    }
  }

  handleLoad(event) {
    this.destroyLoader();
    this.setState({ status: Status.LOADED });

    if (this.props.onLoad) this.props.onLoad(event);
  }

  handleError(error) {
    this.destroyLoader();
    this.setState({ status: Status.FAILED });

    if (this.props.onError) this.props.onError(error);
  }

  renderImg() {
    const { src, imgProps } = this.props;
    const props = { src };

    // eslint-disable-next-line no-restricted-syntax
    for (const k in imgProps) {
      // eslint-disable-next-line no-prototype-builtins
      if (imgProps.hasOwnProperty(k)) {
        props[k] = imgProps[k];
      }
    }

    return ;
  }

  render() {
    const wrapperProps = {
      className: this.getClassName(),
    };

    if (this.props.style) {
      const style = { ...this.props.style };
      delete style.backgroundImage;
      wrapperProps.style = style;

      if (this.state.status === Status.LOADED) {
        wrapperProps.style.backgroundImage = this.props.style.backgroundImage;
      }
    }

    const wrapperArgs = [wrapperProps];

    switch (this.state.status) {
      case Status.LOADED:
        if (this.props.renderElement) {
          wrapperArgs.push(this.props.renderElement());
        } else {
          wrapperArgs.push(this.renderImg());
        }
        break;

      case Status.FAILED:
        if (this.props.children) wrapperArgs.push(this.props.children);
        break;

      default:
        if (this.props.preloader) wrapperArgs.push(this.props.preloader());
        break;
    }

    return (
       { this.loader = node; }}>
        {this.props.wrapper(...wrapperArgs)}

    );
  }
}
<|endoftext|>


<commit_before>Imcms.define("imcms-appearance-tab-builder",
    ["imcms-bem-builder", "imcms-components-builder", "imcms-page-info-tabs-linker", "imcms-choose-image-builder"],
    function (BEM, components, linker, chooseImage) {
        var pageInfoInnerStructureBEM = new BEM({
            block: "imcms-field",
            elements: {
                "checkboxes": "imcms-checkboxes",
                "text-box": "imcms-text-box",
                "text-area": "imcms-text-area",
                "choose-image": "imcms-choose-image",
                "select": "imcms-select"
            }
        });

        var tabData = {};

        function buildCommonContents(commonContents) {
            return commonContents.map(buildDocumentCommonContent).reduce(function (cc1, cc2) {
                return cc1.concat(cc2);
            });
        }

        function buildDocumentCommonContent(commonContent) {
            var $checkbox = components.checkboxes.imcmsCheckbox("<div>", {
                    name: commonContent.language.name.toLowerCase(), // fixme: or native name?
                    text: commonContent.language.name,
                    checked: commonContent.enabled ? "checked" : undefined
                }),
                $checkboxWrapper = components.checkboxes.checkboxContainer("<div>", [$checkbox]),
                $checkboxContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [{"checkboxes": $checkboxWrapper}]),
                $pageTitle = components.texts.textBox("<div>", {
                    name: "title",
                    text: "Title",
                    value: commonContent.headline
                }),
                $pageTitleContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [{"text-box": $pageTitle}]),
                $menuText = components.texts.textArea("<div>", {
                    text: "Menu text",
                    value: commonContent.menuText,
                    name: "menu-text"
                }),
                $menuTextContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [{"text-area": $menuText}]),
                $linkToImage = chooseImage.container("<div>", {
                    id: "path-to-image",
                    name: "image",
                    value: commonContent.menuImageURL,
                    placeholder: "Image path",
                    "label-text": "Link to image",
                    "button-text": "choose..."
                }),
                $linkToImageContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [{"choose-image": $linkToImage}]);

            tabData.commonContents = tabData.commonContents || [];

            tabData.commonContents.push({
                name: commonContent.language.name,
                checkbox: $checkbox,
                pageTitle: $pageTitle,
                menuText: $menuText,
                linkToImage: $linkToImage
            });

            return [$checkboxContainer, $pageTitleContainer, $menuTextContainer, $linkToImageContainer];
        }

        return {

            name: "appearance",

            buildTab: function (index) {
                tabData.$showIn = components.selects.imcmsSelect("<div>", {
                    id: "show-in",
                    text: "Show in",
                    name: "show-in"
                }, [{
                    text: "Same frame",
                    "data-value": "_self"
                }, {
                    text: "New window",
                    "data-value": "_blank"
                }, {
                    text: "Replace all",
                    "data-value": "_top"
                }]);

                var $showInContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [{"select": tabData.$showIn}]);

                tabData.$documentAlias = components.texts.textBox("<div>", {
                    name: "alias",
                    text: "Document alias",
                    placeholder: "this-doc-alias"
                });

                var $documentAliasContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [
                    {"text-box": tabData.$documentAlias}
                ]);

                var tabElements = [$showInContainer, $documentAliasContainer];

                var $result = linker.buildFormBlock(tabElements, index);
                tabData.$result = $result;

                return $result;
            },

            fillTabDataFromDocument: function (document) {
                if (tabData.commonContentElements) {
                    tabData.commonContentElements.forEach(function ($element) {
                        $element.detach();
                    });
                }

                tabData.commonContentElements = buildCommonContents(document.commonContents);
                tabData.$result.prepend(tabData.commonContentElements);
                tabData.$showIn.selectValue(document.target);
                tabData.$documentAlias.setValue(document.alias);
            },

            saveData: function (documentDTO) {
                documentDTO.commonContents.forEach(function (docCommonContent) {
                    tabData.commonContents.forEach(function (commonContent) {

                        if (docCommonContent.language.name !== commonContent.name) {
                            // I can't come up with better solution than double forEach
                            return;
                        }

                        docCommonContent.enabled = commonContent.checkbox.isChecked();
                        docCommonContent.headline = commonContent.pageTitle.getValue();
                        docCommonContent.menuText = commonContent.menuText.getValue();
                        docCommonContent.menuImageURL = commonContent.linkToImage.getValue();
                    });
                });

                documentDTO.alias = tabData.$documentAlias.getValue();
                documentDTO.target = tabData.$showIn.getSelectedValue();
            },

            clearTabData: function () {
                var emptyString = '';

                tabData.commonContents.forEach(function (commonContent, index) {
                    commonContent.checkbox.setChecked(index === 0);//check only first
                    commonContent.pageTitle.setValue(emptyString);
                    commonContent.menuText.setValue(emptyString);
                    commonContent.linkToImage.setValue(emptyString);
                });

                tabData.$showIn.selectFirst();
                tabData.$documentAlias.setValue(emptyString);
            }
        };
    }
);
<commit_msg>IMCMS-255 - Upgrade server side to work with new client: - Fixed document's common contents data overgrowth.<commit_after>Imcms.define("imcms-appearance-tab-builder",
    ["imcms-bem-builder", "imcms-components-builder", "imcms-page-info-tabs-linker", "imcms-choose-image-builder"],
    function (BEM, components, linker, chooseImage) {
        var pageInfoInnerStructureBEM = new BEM({
            block: "imcms-field",
            elements: {
                "checkboxes": "imcms-checkboxes",
                "text-box": "imcms-text-box",
                "text-area": "imcms-text-area",
                "choose-image": "imcms-choose-image",
                "select": "imcms-select"
            }
        });

        var tabData = {};

        function buildCommonContents(commonContents) {
            tabData.commonContents = [];

            return commonContents.map(buildDocumentCommonContent).reduce(function (cc1, cc2) {
                return cc1.concat(cc2);
            });
        }

        function buildDocumentCommonContent(commonContent) {
            var $checkbox = components.checkboxes.imcmsCheckbox("<div>", {
                    name: commonContent.language.name.toLowerCase(), // fixme: or native name?
                    text: commonContent.language.name,
                    checked: commonContent.enabled ? "checked" : undefined
                }),
                $checkboxWrapper = components.checkboxes.checkboxContainer("<div>", [$checkbox]),
                $checkboxContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [{"checkboxes": $checkboxWrapper}]),
                $pageTitle = components.texts.textBox("<div>", {
                    name: "title",
                    text: "Title",
                    value: commonContent.headline
                }),
                $pageTitleContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [{"text-box": $pageTitle}]),
                $menuText = components.texts.textArea("<div>", {
                    text: "Menu text",
                    value: commonContent.menuText,
                    name: "menu-text"
                }),
                $menuTextContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [{"text-area": $menuText}]),
                $linkToImage = chooseImage.container("<div>", {
                    id: "path-to-image",
                    name: "image",
                    value: commonContent.menuImageURL,
                    placeholder: "Image path",
                    "label-text": "Link to image",
                    "button-text": "choose..."
                }),
                $linkToImageContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [{"choose-image": $linkToImage}]);

            tabData.commonContents = tabData.commonContents || [];

            tabData.commonContents.push({
                name: commonContent.language.name,
                checkbox: $checkbox,
                pageTitle: $pageTitle,
                menuText: $menuText,
                linkToImage: $linkToImage
            });

            return [$checkboxContainer, $pageTitleContainer, $menuTextContainer, $linkToImageContainer];
        }

        return {

            name: "appearance",

            buildTab: function (index) {
                tabData.$showIn = components.selects.imcmsSelect("<div>", {
                    id: "show-in",
                    text: "Show in",
                    name: "show-in"
                }, [{
                    text: "Same frame",
                    "data-value": "_self"
                }, {
                    text: "New window",
                    "data-value": "_blank"
                }, {
                    text: "Replace all",
                    "data-value": "_top"
                }]);

                var $showInContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [{"select": tabData.$showIn}]);

                tabData.$documentAlias = components.texts.textBox("<div>", {
                    name: "alias",
                    text: "Document alias",
                    placeholder: "this-doc-alias"
                });

                var $documentAliasContainer = pageInfoInnerStructureBEM.buildBlock("<div>", [
                    {"text-box": tabData.$documentAlias}
                ]);

                var tabElements = [$showInContainer, $documentAliasContainer];

                var $result = linker.buildFormBlock(tabElements, index);
                tabData.$result = $result;

                return $result;
            },

            fillTabDataFromDocument: function (document) {
                if (tabData.commonContentElements) {
                    tabData.commonContentElements.forEach(function ($element) {
                        $element.detach();
                    });
                }

                tabData.commonContentElements = buildCommonContents(document.commonContents);
                tabData.$result.prepend(tabData.commonContentElements);
                tabData.$showIn.selectValue(document.target);
                tabData.$documentAlias.setValue(document.alias);
            },

            saveData: function (documentDTO) {
                documentDTO.commonContents.forEach(function (docCommonContent) {
                    tabData.commonContents.forEach(function (commonContent) {

                        if (docCommonContent.language.name !== commonContent.name) {
                            // I can't come up with better solution than double forEach
                            return;
                        }

                        docCommonContent.enabled = commonContent.checkbox.isChecked();
                        docCommonContent.headline = commonContent.pageTitle.getValue();
                        docCommonContent.menuText = commonContent.menuText.getValue();
                        docCommonContent.menuImageURL = commonContent.linkToImage.getValue();
                    });
                });

                documentDTO.alias = tabData.$documentAlias.getValue();
                documentDTO.target = tabData.$showIn.getSelectedValue();
            },

            clearTabData: function () {
                var emptyString = '';

                tabData.commonContents.forEach(function (commonContent, index) {
                    commonContent.checkbox.setChecked(index === 0);//check only first
                    commonContent.pageTitle.setValue(emptyString);
                    commonContent.menuText.setValue(emptyString);
                    commonContent.linkToImage.setValue(emptyString);
                });

                tabData.$showIn.selectFirst();
                tabData.$documentAlias.setValue(emptyString);
            }
        };
    }
);
<|endoftext|>


<commit_before>const React = require('react');
const ReactDOM = require('react-dom');

//*------------------------------------------------------------------*

const DifferentialRouter = require('./DifferentialRouter.jsx');

//*------------------------------------------------------------------*

module.exports = function ({atlasHostUrl : hostUrl = "https://www.ebi.ac.uk", geneQuery, conditionQuery, species, target = 'gxaDifferentialTab', }) {
    ReactDOM.render(
        React.createElement(DifferentialRouter, {hostUrl: hostUrl, geneQuery: geneQuery, conditionQuery: conditionQuery, species: species}),
        document.getElementById(target)
    );
};
<commit_msg>Use the current protocol and host if none is specified<commit_after>const React = require('react');
const ReactDOM = require('react-dom');

//*------------------------------------------------------------------*

const DifferentialRouter = require('./DifferentialRouter.jsx');

//*------------------------------------------------------------------*

module.exports = function ({atlasHostUrl : hostUrl = window.location.protocol + "//" + window.location.host, geneQuery, conditionQuery, species, target = 'gxaDifferentialTab', }) {
    ReactDOM.render(
        React.createElement(DifferentialRouter, {hostUrl: hostUrl, geneQuery: geneQuery, conditionQuery: conditionQuery, species: species}),
        document.getElementById(target)
    );
};
<|endoftext|>


<commit_before>angular.module('materialscommons').directive('processSettings', processSettingsDirective);
function processSettingsDirective() {
    return {
        restrict: 'E',
        scope: {
            settings: '=',
            taskId: '=',
            templateId: '=',
            attribute: '='
        },
        controller: ProcessSettingsDirectiveController,
        controllerAs: 'ctrl',
        bindToController: true,
        templateUrl: 'app/project/processes/process/create/components/process-settings.html'
    }
}

/*@ngInject*/
function ProcessSettingsDirectiveController(experimentsService, toast, $stateParams) {
    var ctrl = this;
    ctrl.datePickerOptions = {
        formatYear: 'yy',
        startingDay: 1
    };
    ctrl.openDatePicker = openDatePicker;

    ctrl.updateSettingProperty = (property) => {
        if (!property.value) {
            return;
        }

        property.setup_attribute = ctrl.attribute;
        let propertyArgs = {
            template_id: ctrl.templateId,
            properties: [property]
        };
        experimentsService.updateTaskTemplateProperties($stateParams.project_id, $stateParams.experiment_id, ctrl.taskId, propertyArgs)
            .then(
                () => null,
                () => toast.error('Unable to update property')
            );
    };

    ///////////////////////////////////////

    function openDatePicker($event, prop) {
        $event.preventDefault();
        $event.stopPropagation();
        prop.opened = true;
    }
}<commit_msg>Add debug to handle date case (need to test).<commit_after>angular.module('materialscommons').directive('processSettings', processSettingsDirective);
function processSettingsDirective() {
    return {
        restrict: 'E',
        scope: {
            settings: '=',
            taskId: '=',
            templateId: '=',
            attribute: '='
        },
        controller: ProcessSettingsDirectiveController,
        controllerAs: 'ctrl',
        bindToController: true,
        templateUrl: 'app/project/processes/process/create/components/process-settings.html'
    }
}

/*@ngInject*/
function ProcessSettingsDirectiveController(experimentsService, toast, $stateParams) {
    var ctrl = this;
    ctrl.datePickerOptions = {
        formatYear: 'yy',
        startingDay: 1
    };
    ctrl.openDatePicker = openDatePicker;

    ctrl.updateSettingProperty = (property) => {
        if (!property.value) {
            return;
        }

        if (property._type === "date") {
            console.dir(property);
            return;
        }

        property.setup_attribute = ctrl.attribute;
        let propertyArgs = {
            template_id: ctrl.templateId,
            properties: [property]
        };
        experimentsService.updateTaskTemplateProperties($stateParams.project_id, $stateParams.experiment_id, ctrl.taskId, propertyArgs)
            .then(
                () => null,
                () => toast.error('Unable to update property')
            );
    };

    ///////////////////////////////////////

    function openDatePicker($event, prop) {
        $event.preventDefault();
        $event.stopPropagation();
        prop.opened = true;
    }
}<|endoftext|>


<commit_before>'use strict';

/* jshint -W098 */
angular.module('cooperativa').controller('Cooperativa.TransaccionCliente.CompraVenta.CrearController',
    function ($scope, $state, $modal, toastr, SGDialog, TasaInteresService, SessionService, CajaService, VoucherService) {

        $scope.working = false;

        $scope.view = {
            transaccion: {
                cliente: undefined,
                tipoCambio: 0,
                montoRecibido: 0,
                montoEntregado: 0,
                limpiar: function () {
                    $scope.view.transaccion.cliente = undefined;
                    $scope.view.transaccion.tipoCambio = 0;
                    $scope.view.transaccion.montoRecibido = 0;
                    $scope.view.transaccion.montoEntregado = 0;
                }
            },
            voucher: {
                list: [],
                limpiar: function () {
                    $scope.view.voucher.list = [];
                }
            }
        };
        $scope.view.load = {
            tipoCambio: undefined
        };

        $scope.combo = {
            operacion: undefined
        };
        $scope.combo.selected = {
            operacion: undefined
        };
        $scope.combo.groupFn = function (item) {
            return item.tipo;
        };

        $scope.access = {
            login: false
        };

        $scope.loadCombo = function () {
            $scope.combo.operacion = [
                {
                    denominacion: 'Compra Dolar (USD)',
                    tipo: 'COMPRA',
                    idMonedaRecibida: 0,
                    idMonedaEntregada: 1,
                    simboloMonedaRecibida: '$',
                    simboloMonedaEntregada: 'S/.',
                    denominacionMonedaRecibida: 'Dolares americanos',
                    denominacionMonedaEntregada: 'Nuevos soles'
                },
                {
                    denominacion: 'Venta Dolar (USD)',
                    tipo: 'VENTA',
                    idMonedaRecibida: 1,
                    idMonedaEntregada: 0,
                    simboloMonedaRecibida: 'S/.',
                    simboloMonedaEntregada: '$',
                    denominacionMonedaRecibida: 'Nuevos soles',
                    denominacionMonedaEntregada: 'Dolares americanos'
                },
                {
                    denominacion: 'Compra Euro (EUR)',
                    tipo: 'COMPRA',
                    idMonedaRecibida: 2,
                    idMonedaEntregada: 1,
                    simboloMonedaRecibida: 'E',
                    simboloMonedaEntregada: 'S/.',
                    denominacionMonedaRecibida: 'Euros',
                    denominacionMonedaEntregada: 'Nuevos soles'
                },
                {
                    denominacion: 'Venta Dolar (EUR)',
                    tipo: 'VENTA',
                    idMonedaRecibida: 1,
                    idMonedaEntregada: 2,
                    simboloMonedaRecibida: 'S/.',
                    simboloMonedaEntregada: 'E',
                    denominacionMonedaRecibida: 'Nuevos soles',
                    denominacionMonedaEntregada: 'Euros'
                }
            ];
        };
        $scope.loadCombo();

        $scope.loadTipoCambio = function () {
            $scope.$watch('combo.selected.operacion', function (newValue, oldValue) {
                if (angular.isDefined($scope.combo.selected.operacion)) {
                    TasaInteresService.getTasaCambio($scope.combo.selected.operacion.idMonedaRecibida, $scope.combo.selected.operacion.idMonedaEntregada).then(function (response) {
                        $scope.view.transaccion.tipoCambio = response.valor;
                    });
                } else {
                    $scope.view.transaccion.tipoCambio = 0;
                }
            }, true);
        };
        $scope.loadTipoCambio();

        $scope.loadMontoRecibidoEntregado = function () {
            $scope.$watch('view.transaccion.montoRecibido', function (newValue, oldValue) {
                if (angular.isDefined($scope.combo.selected.operacion)) {
                    if ($scope.combo.selected.operacion.tipo === 'COMPRA') {
                        $scope.view.transaccion.montoEntregado = $scope.view.transaccion.montoRecibido * $scope.view.transaccion.tipoCambio;
                    }
                } else {
                    $scope.view.transaccion.montoRecibido = 0;
                    $scope.view.transaccion.montoEntregado = 0;
                }
            }, true);
            $scope.$watch('view.transaccion.montoEntregado', function (newValue, oldValue) {
                if (angular.isDefined($scope.combo.selected.operacion)) {
                    if ($scope.combo.selected.operacion.tipo === 'VENTA') {
                        $scope.view.transaccion.montoRecibido = $scope.view.transaccion.montoEntregado * $scope.view.transaccion.tipoCambio;
                    }
                } else {
                    $scope.view.transaccion.montoRecibido = 0;
                    $scope.view.transaccion.montoEntregado = 0;
                }
            }, true);
        };
        $scope.loadMontoRecibidoEntregado();

        $scope.login = function () {
            var modalInstance = $modal.open({
                animation: true,
                templateUrl: '/modules/cooperativa/client/views/login/form-login.html',
                controller: 'Cooperativa.LoginController',
                resolve: {}
            });
            modalInstance.result.then(function () {
                $scope.access.login = true;
            }, function () {
            });
        };

        $scope.save = function () {
            var modalInstance = $modal.open({
                animation: true,
                templateUrl: '/modules/cooperativa/client/views/transaccionCliente/compraVenta/form-transaccion-compraVenta-verificar.html',
                controller: 'Cooperativa.TransaccionCliente.CompraVenta.Crear.VerificarController',
                resolve: {
                    transaccion: function () {
                        return $scope.view.transaccion;
                    },
                    operacion: function () {
                        return $scope.combo.selected.operacion;
                    }
                }
            });
            modalInstance.result.then(function () {
                var transaccion = {};
                transaccion.tipoOperacion = $scope.combo.selected.operacion.tipo;
                transaccion.idMonedaRecibida = $scope.combo.selected.operacion.idMonedaRecibida;
                transaccion.idMonedaEntregada = $scope.combo.selected.operacion.idMonedaEntregada;
                transaccion.montoRecibido = $scope.view.transaccion.montoRecibido;
                transaccion.montoEntregado = $scope.view.transaccion.montoEntregado;
                transaccion.tasaCambio = $scope.view.transaccion.tipoCambio;
                transaccion.referencia = $scope.view.transaccion.cliente;
                $scope.working = true;
                SessionService.crearTransaccionCompraVenta(transaccion).then(
                    function (response) {
                        $scope.working = false;
                        toastr.success('Transaccion creada satisfactoriamente.');
                        //$state.go('app.transaccion.compraVentaVoucher', {id: response.id});
                        CajaService.getVoucherCompraVenta(response.id).then(function (voucher) {
                            $scope.view.voucher.list.push(voucher);
                        });
                    },
                    function error(err) {
                        toastr.error(err.data.message);
                    }
                );
            }, function () {
            });
        };

        $scope.imprimir = function (item) {
            VoucherService.imprimirVoucherCompraVenta(item);
        };
        $scope.extornar = function (item, index) {
            SGDialog.confirmDelete('Transaccion', '', function () {
                SessionService.extornarTransaccion(item.id).then(
                    function (response) {
                        toastr.success('Transaccion extornada satisfactoriamente.');
                        $scope.view.voucher.list.splice(index, 1);
                    }, function error(err) {
                        toastr.error(err.data.message);
                        $scope.search();
                    }
                );
            });
        };

    }
);
<commit_msg>Update crear.client.controller.js<commit_after>'use strict';

/* jshint -W098 */
angular.module('cooperativa').controller('Cooperativa.TransaccionCliente.CompraVenta.CrearController',
    function ($scope, $state, $modal, toastr, SGDialog, TasaInteresService, SessionService, CajaService, VoucherService) {

        $scope.working = false;

        $scope.view = {
            transaccion: {
                cliente: undefined,
                tipoCambio: 0,
                montoRecibido: 0,
                montoEntregado: 0,
                limpiar: function () {
                    $scope.view.transaccion.cliente = undefined;
                    $scope.view.transaccion.tipoCambio = 0;
                    $scope.view.transaccion.montoRecibido = 0;
                    $scope.view.transaccion.montoEntregado = 0;
                }
            },
            voucher: {
                list: [],
                limpiar: function () {
                    $scope.view.voucher.list = [];
                }
            }
        };
        $scope.view.load = {
            tipoCambio: undefined
        };

        $scope.combo = {
            operacion: undefined
        };
        $scope.combo.selected = {
            operacion: undefined
        };
        $scope.combo.groupFn = function (item) {
            return item.tipo;
        };

        $scope.access = {
            login: false
        };

        $scope.loadCombo = function () {
            $scope.combo.operacion = [
                {
                    denominacion: 'Compra Dolar (USD)',
                    tipo: 'COMPRA',
                    idMonedaRecibida: 0,
                    idMonedaEntregada: 1,
                    simboloMonedaRecibida: '$',
                    simboloMonedaEntregada: 'S/.',
                    denominacionMonedaRecibida: 'Dolares americanos',
                    denominacionMonedaEntregada: 'Nuevos soles'
                },
                {
                    denominacion: 'Venta Dolar (USD)',
                    tipo: 'VENTA',
                    idMonedaRecibida: 1,
                    idMonedaEntregada: 0,
                    simboloMonedaRecibida: 'S/.',
                    simboloMonedaEntregada: '$',
                    denominacionMonedaRecibida: 'Nuevos soles',
                    denominacionMonedaEntregada: 'Dolares americanos'
                },
                {
                    denominacion: 'Compra Euro (EUR)',
                    tipo: 'COMPRA',
                    idMonedaRecibida: 2,
                    idMonedaEntregada: 1,
                    simboloMonedaRecibida: 'E',
                    simboloMonedaEntregada: 'S/.',
                    denominacionMonedaRecibida: 'Euros',
                    denominacionMonedaEntregada: 'Nuevos soles'
                },
                {
                    denominacion: 'Venta Dolar (EUR)',
                    tipo: 'VENTA',
                    idMonedaRecibida: 1,
                    idMonedaEntregada: 2,
                    simboloMonedaRecibida: 'S/.',
                    simboloMonedaEntregada: 'E',
                    denominacionMonedaRecibida: 'Nuevos soles',
                    denominacionMonedaEntregada: 'Euros'
                }
            ];
        };
        $scope.loadCombo();

        $scope.loadTipoCambio = function () {
            $scope.$watch('combo.selected.operacion', function (newValue, oldValue) {
                if (angular.isDefined($scope.combo.selected.operacion)) {
                    TasaInteresService.getTasaCambio($scope.combo.selected.operacion.idMonedaRecibida, $scope.combo.selected.operacion.idMonedaEntregada).then(function (response) {
                        $scope.view.transaccion.tipoCambio = response.valor;
                    });
                } else {
                    $scope.view.transaccion.tipoCambio = 0;
                }
            }, true);
        };
        $scope.loadTipoCambio();

        $scope.loadMontoRecibidoEntregado = function () {
            $scope.$watch('view.transaccion.montoRecibido', function (newValue, oldValue) {
                if (angular.isDefined($scope.combo.selected.operacion)) {
                    if ($scope.combo.selected.operacion.tipo === 'COMPRA') {
                        $scope.view.transaccion.montoEntregado = $scope.view.transaccion.montoRecibido * $scope.view.transaccion.tipoCambio;
                    }
                } else {
                    $scope.view.transaccion.montoRecibido = 0;
                    $scope.view.transaccion.montoEntregado = 0;
                }
            }, true);
            $scope.$watch('view.transaccion.montoEntregado', function (newValue, oldValue) {
                if (angular.isDefined($scope.combo.selected.operacion)) {
                    if ($scope.combo.selected.operacion.tipo === 'VENTA') {
                        $scope.view.transaccion.montoRecibido = $scope.view.transaccion.montoEntregado * $scope.view.transaccion.tipoCambio;
                    }
                } else {
                    $scope.view.transaccion.montoRecibido = 0;
                    $scope.view.transaccion.montoEntregado = 0;
                }
            }, true);
        };
        $scope.loadMontoRecibidoEntregado();

        $scope.login = function () {
            var modalInstance = $modal.open({
                animation: true,
                templateUrl: 'modules/cooperativa/views/login/form-login.html',
                controller: 'Cooperativa.LoginController',
                resolve: {}
            });
            modalInstance.result.then(function () {
                $scope.access.login = true;
            }, function () {
            });
        };

        $scope.save = function () {
            var modalInstance = $modal.open({
                animation: true,
                templateUrl: '/modules/cooperativa/client/views/transaccionCliente/compraVenta/form-transaccion-compraVenta-verificar.html',
                controller: 'Cooperativa.TransaccionCliente.CompraVenta.Crear.VerificarController',
                resolve: {
                    transaccion: function () {
                        return $scope.view.transaccion;
                    },
                    operacion: function () {
                        return $scope.combo.selected.operacion;
                    }
                }
            });
            modalInstance.result.then(function () {
                var transaccion = {};
                transaccion.tipoOperacion = $scope.combo.selected.operacion.tipo;
                transaccion.idMonedaRecibida = $scope.combo.selected.operacion.idMonedaRecibida;
                transaccion.idMonedaEntregada = $scope.combo.selected.operacion.idMonedaEntregada;
                transaccion.montoRecibido = $scope.view.transaccion.montoRecibido;
                transaccion.montoEntregado = $scope.view.transaccion.montoEntregado;
                transaccion.tasaCambio = $scope.view.transaccion.tipoCambio;
                transaccion.referencia = $scope.view.transaccion.cliente;
                $scope.working = true;
                SessionService.crearTransaccionCompraVenta(transaccion).then(
                    function (response) {
                        $scope.working = false;
                        toastr.success('Transaccion creada satisfactoriamente.');
                        //$state.go('app.transaccion.compraVentaVoucher', {id: response.id});
                        CajaService.getVoucherCompraVenta(response.id).then(function (voucher) {
                            $scope.view.voucher.list.push(voucher);
                        });
                    },
                    function error(err) {
                        toastr.error(err.data.message);
                    }
                );
            }, function () {
            });
        };

        $scope.imprimir = function (item) {
            VoucherService.imprimirVoucherCompraVenta(item);
        };
        $scope.extornar = function (item, index) {
            SGDialog.confirmDelete('Transaccion', '', function () {
                SessionService.extornarTransaccion(item.id).then(
                    function (response) {
                        toastr.success('Transaccion extornada satisfactoriamente.');
                        $scope.view.voucher.list.splice(index, 1);
                    }, function error(err) {
                        toastr.error(err.data.message);
                        $scope.search();
                    }
                );
            });
        };

    }
);
<|endoftext|>


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

	/**
	 ** DEPENDENCIES
	 **  - angular translate
	 **  - underscore
	 **/

	angular.module("lui.translate")
		.directive("luidTranslations", ["$translate", "_", "$filter", "$timeout", function ($translate, _, $filter, $timeout) {
			function link(scope, element, attrs, ctrls) {
				var ngModelCtrl = ctrls[1];
				var translateCtrl = ctrls[0];

				/** Associations language/code */
				var languagesToCodes = { en: 1033, de: 1031, es: 1034, fr: 1036, it: 1040, nl: 2067 };

				/** List of all the available languages labels */
				var cultures = _.keys(languagesToCodes);
				scope.cultures = cultures;

				scope.currentCulture = $translate.preferredLanguage() || "en";

				var mode = !!attrs.mode ? attrs.mode : "dictionary";
				if (mode === "dictionary" && ngModelCtrl.$viewValue !== undefined) {
					_.each(cultures, function (culture) {
						scope.$watch(
							function () { return !!ngModelCtrl.$viewValue ? ngModelCtrl.$viewValue[culture] : ngModelCtrl.$viewValue; },
							function () { ngModelCtrl.$render(); }
						);
					});
				}

				ngModelCtrl.$render = function () {
					scope.internal = parse(ngModelCtrl.$viewValue);
					translateCtrl.updateTooltip();
				};

				translateCtrl.updateViewValue = function () {
					switch (mode) {
						case "dictionary":
							return updateDictionary(scope.internal);
						case "|":
						case "pipe":
							return updatePipe(scope.internal);
						case "lucca":
							return updateLucca(scope.internal);
					}
				};

				translateCtrl.updateTooltip = function () {
					var tooltipText = "";
					if(!!!scope.internal) {
						scope.tooltipText = undefined;
						return;
					}
					for(var i = 0; i < scope.cultures.length; i++) {
						if(!!scope.internal[scope.cultures[i]]) {
							tooltipText += "[" + scope.cultures[i].toUpperCase() + "]";
							tooltipText += " : ";
							tooltipText += scope.internal[scope.cultures[i]];
							tooltipText += "\n";
						}
					}
					scope.tooltipText = tooltipText;
				};

				var parse = function (value) {
					if (value === undefined) { return undefined; }
					switch (mode) {
						case "dictionary":
							return parseDictionary(value);
						case "|":
						case "pipe":
							return parsePipe(value);
						case "lucca":
							return parseLucca(value);
						default:
							return undefined;
					}
				};

				// Mode lucca
				var parseLucca = function (value) {
					return _.reduce(cultures, function (memo, culture) {
						var targetLabel = _.findWhere(value, { cultureCode: languagesToCodes[culture] });
						memo[culture] = !!targetLabel ? targetLabel.value : undefined;
						// We need to keep the original id
						memo[culture + "_id"] = !!targetLabel ? targetLabel.id : undefined;
						return memo;
					}, {});
				};
				var updateLucca = function (value) {
					var allEmpty = true;
					var viewValue = [];
					_.each(cultures, function (culture) {
						if (!!value[culture] && value[culture] !== "") {
							viewValue.push({ id: value[culture + "_id"], cultureCode: languagesToCodes[culture], value: value[culture] });
							allEmpty = false;
						}
					});
					ngModelCtrl.$setViewValue(allEmpty ? undefined : viewValue);
					scope.$parent.$eval(attrs.ngChange);
				};

				// Mode dictionary
				var parseDictionary = function (value) {
					return _.reduce(cultures, function (memo, culture) {
						memo[culture] = value[culture];
						return memo;
					}, {});
				};
				var updateDictionary = function (value) {
					var allEmpty = true;
					var viewValue = {};
					_.each(cultures, function (culture) {
						viewValue[culture] = value[culture];
						allEmpty &= value[culture] === undefined || value[culture] === "";
					});
					ngModelCtrl.$setViewValue(allEmpty ? undefined : viewValue);
					scope.$parent.$eval(attrs.ngChange); // needs to be called manually cuz the object ref of the $viewValue didn't change
				};

				// Mode pipe
				var parsePipe = function (value) {
					// value looks like this "en:some stuff|de:|nl:|fr:des bidules|it:|es:"
					var translations = value.split("|");
					var result = {};
					_.each(translations, function (t) {
						var key = t.substring(0, 2);
						var val = t.substring(3);
						result[key] = val;
					});
					return _.pick(result, cultures);
				};
				var updatePipe = function (value) {
					if (!_.find(cultures, function (culture) { return value[culture] !== undefined && value[culture] !== ""; })) {
						ngModelCtrl.$setViewValue(undefined);
					} else {
						var newVal = _.map(cultures, function (c) {
							if (!!value[c]) {
								return c + ":" + value[c];
							}
							return c + ":";
						}).join("|");
						ngModelCtrl.$setViewValue(newVal);
					}
				};
			}
			return {
				require: ['luidTranslations', '^ngModel'],
				controller: 'luidTranslationsController',
				scope: {
					mode: '@', // allowed values: "pipe" (or "|"), "dictionary", "lucca" (lucca proprietary format)
					size: "@", // the size of the input (short, long, x-long, fitting)
					isDisabled: "=ngDisabled"
				},
				templateUrl: "lui/directives/luidTranslations.html",
				restrict: 'EA',
				link: link
			};
		}])
		.controller('luidTranslationsController', ['$scope', '$translate', '$timeout', function ($scope, $filter, $timeout) {
			var ctrl = this;
			/******************
			* UPDATE          *
			******************/
			$scope.update = function () { ctrl.updateViewValue(); ctrl.updateTooltip(); };

			/******************
			* FOCUS & BLUR    *
			******************/

			$scope.focusInput = function () {
				$scope.focused = true;
			};
			$scope.blurInput = function () {
				$scope.focused = false;
			};

			$scope.blurOnEnter = function($event) {
				$event.target.blur();
				$event.preventDefault();
			};
		}]);

	/**************************/
	/***** TEMPLATES      *****/
	/**************************/
	angular.module("lui").run(["$templateCache", function ($templateCache) {
		$templateCache.put("lui/directives/luidTranslations.html",
			"
" +
			"
" +
			"		<input type=\"text\" ng-disabled=\"isDisabled\" ng-model=\"internal[currentCulture]\" ng-focus=\"focusInput()\" ng-blur=\"blurInput()\" ng-keypress=\"$event.keyCode === 13 && blurOnEnter($event)\" ng-change=\"update()\">" +
			"		{{currentCulture}}" +
			"
" +
			"
" +
			"
" +
			"
" +
			"				<input type=\"text\" ng-disabled=\"isDisabled\" ng-model=\"internal[culture]\" ng-focus=\"focusInput()\" ng-blur=\"blurInput()\" ng-keypress=\"$event.keyCode === 13 && blurOnEnter($event)\" ng-change=\"update()\">" +
			"				{{culture}}" +
			"
" +
			"
" +
			"
" +
			"
" +
			"");
	}]);
})();
<commit_msg>Reduced updateTooltip to one concatenation per culture<commit_after>(function () {
	"use strict";

	/**
	 ** DEPENDENCIES
	 **  - angular translate
	 **  - underscore
	 **/

	angular.module("lui.translate")
		.directive("luidTranslations", ["$translate", "_", "$filter", "$timeout", function ($translate, _, $filter, $timeout) {
			function link(scope, element, attrs, ctrls) {
				var ngModelCtrl = ctrls[1];
				var translateCtrl = ctrls[0];

				/** Associations language/code */
				var languagesToCodes = { en: 1033, de: 1031, es: 1034, fr: 1036, it: 1040, nl: 2067 };

				/** List of all the available languages labels */
				var cultures = _.keys(languagesToCodes);
				scope.cultures = cultures;

				scope.currentCulture = $translate.preferredLanguage() || "en";

				var mode = !!attrs.mode ? attrs.mode : "dictionary";
				if (mode === "dictionary" && ngModelCtrl.$viewValue !== undefined) {
					_.each(cultures, function (culture) {
						scope.$watch(
							function () { return !!ngModelCtrl.$viewValue ? ngModelCtrl.$viewValue[culture] : ngModelCtrl.$viewValue; },
							function () { ngModelCtrl.$render(); }
						);
					});
				}

				ngModelCtrl.$render = function () {
					scope.internal = parse(ngModelCtrl.$viewValue);
					translateCtrl.updateTooltip();
				};

				translateCtrl.updateViewValue = function () {
					switch (mode) {
						case "dictionary":
							return updateDictionary(scope.internal);
						case "|":
						case "pipe":
							return updatePipe(scope.internal);
						case "lucca":
							return updateLucca(scope.internal);
					}
				};

				translateCtrl.updateTooltip = function () {
					var tooltipText = "";
					if(!!!scope.internal) {
						scope.tooltipText = undefined;
						return;
					}
					for(var i = 0; i < scope.cultures.length; i++) {
						if(!!scope.internal[scope.cultures[i]]) {
							tooltipText += "["+scope.cultures[i].toUpperCase()+"] : "+ scope.internal[scope.cultures[i]] + "\n";
						}
					}
					scope.tooltipText = tooltipText;
				};

				var parse = function (value) {
					if (value === undefined) { return undefined; }
					switch (mode) {
						case "dictionary":
							return parseDictionary(value);
						case "|":
						case "pipe":
							return parsePipe(value);
						case "lucca":
							return parseLucca(value);
						default:
							return undefined;
					}
				};

				// Mode lucca
				var parseLucca = function (value) {
					return _.reduce(cultures, function (memo, culture) {
						var targetLabel = _.findWhere(value, { cultureCode: languagesToCodes[culture] });
						memo[culture] = !!targetLabel ? targetLabel.value : undefined;
						// We need to keep the original id
						memo[culture + "_id"] = !!targetLabel ? targetLabel.id : undefined;
						return memo;
					}, {});
				};
				var updateLucca = function (value) {
					var allEmpty = true;
					var viewValue = [];
					_.each(cultures, function (culture) {
						if (!!value[culture] && value[culture] !== "") {
							viewValue.push({ id: value[culture + "_id"], cultureCode: languagesToCodes[culture], value: value[culture] });
							allEmpty = false;
						}
					});
					ngModelCtrl.$setViewValue(allEmpty ? undefined : viewValue);
					scope.$parent.$eval(attrs.ngChange);
				};

				// Mode dictionary
				var parseDictionary = function (value) {
					return _.reduce(cultures, function (memo, culture) {
						memo[culture] = value[culture];
						return memo;
					}, {});
				};
				var updateDictionary = function (value) {
					var allEmpty = true;
					var viewValue = {};
					_.each(cultures, function (culture) {
						viewValue[culture] = value[culture];
						allEmpty &= value[culture] === undefined || value[culture] === "";
					});
					ngModelCtrl.$setViewValue(allEmpty ? undefined : viewValue);
					scope.$parent.$eval(attrs.ngChange); // needs to be called manually cuz the object ref of the $viewValue didn't change
				};

				// Mode pipe
				var parsePipe = function (value) {
					// value looks like this "en:some stuff|de:|nl:|fr:des bidules|it:|es:"
					var translations = value.split("|");
					var result = {};
					_.each(translations, function (t) {
						var key = t.substring(0, 2);
						var val = t.substring(3);
						result[key] = val;
					});
					return _.pick(result, cultures);
				};
				var updatePipe = function (value) {
					if (!_.find(cultures, function (culture) { return value[culture] !== undefined && value[culture] !== ""; })) {
						ngModelCtrl.$setViewValue(undefined);
					} else {
						var newVal = _.map(cultures, function (c) {
							if (!!value[c]) {
								return c + ":" + value[c];
							}
							return c + ":";
						}).join("|");
						ngModelCtrl.$setViewValue(newVal);
					}
				};
			}
			return {
				require: ['luidTranslations', '^ngModel'],
				controller: 'luidTranslationsController',
				scope: {
					mode: '@', // allowed values: "pipe" (or "|"), "dictionary", "lucca" (lucca proprietary format)
					size: "@", // the size of the input (short, long, x-long, fitting)
					isDisabled: "=ngDisabled"
				},
				templateUrl: "lui/directives/luidTranslations.html",
				restrict: 'EA',
				link: link
			};
		}])
		.controller('luidTranslationsController', ['$scope', '$translate', '$timeout', function ($scope, $filter, $timeout) {
			var ctrl = this;
			/******************
			* UPDATE          *
			******************/
			$scope.update = function () { ctrl.updateViewValue(); ctrl.updateTooltip(); };

			/******************
			* FOCUS & BLUR    *
			******************/

			$scope.focusInput = function () {
				$scope.focused = true;
			};
			$scope.blurInput = function () {
				$scope.focused = false;
			};

			$scope.blurOnEnter = function($event) {
				$event.target.blur();
				$event.preventDefault();
			};
		}]);

	/**************************/
	/***** TEMPLATES      *****/
	/**************************/
	angular.module("lui").run(["$templateCache", function ($templateCache) {
		$templateCache.put("lui/directives/luidTranslations.html",
			"
" +
			"
" +
			"		<input type=\"text\" ng-disabled=\"isDisabled\" ng-model=\"internal[currentCulture]\" ng-focus=\"focusInput()\" ng-blur=\"blurInput()\" ng-keypress=\"$event.keyCode === 13 && blurOnEnter($event)\" ng-change=\"update()\">" +
			"		{{currentCulture}}" +
			"
" +
			"
" +
			"
" +
			"
" +
			"				<input type=\"text\" ng-disabled=\"isDisabled\" ng-model=\"internal[culture]\" ng-focus=\"focusInput()\" ng-blur=\"blurInput()\" ng-keypress=\"$event.keyCode === 13 && blurOnEnter($event)\" ng-change=\"update()\">" +
			"				{{culture}}" +
			"
" +
			"
" +
			"
" +
			"
" +
			"");
	}]);
})();
<|endoftext|>


<commit_before>/**
 * Korean translation for bootstrap-datetimepicker
 * Gu Youn <http://github.com/guyoun>
 * Baekjoon Choi <http://github.com/Baekjoon>
 */
;(function($){
	$.fn.datetimepicker.dates['kr'] = {
		days: ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일", "일요일"],
		daysShort: ["일", "월", "화", "수", "목", "금", "토", "일"],
		daysMin: ["일", "월", "화", "수", "목", "금", "토", "일"],
		months: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
		monthsShort: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
		suffix: [],
		meridiem: ["오전", "오후"],
        today: "오늘",
	};
}(jQuery));
<commit_msg>Use correct langCode<commit_after>/**
 * Korean translation for bootstrap-datetimepicker
 * Gu Youn <http://github.com/guyoun>
 * Baekjoon Choi <http://github.com/Baekjoon>
 */
;(function($){
	$.fn.datetimepicker.dates['ko'] = {
		days: ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일", "일요일"],
		daysShort: ["일", "월", "화", "수", "목", "금", "토", "일"],
		daysMin: ["일", "월", "화", "수", "목", "금", "토", "일"],
		months: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
		monthsShort: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
		suffix: [],
		meridiem: ["오전", "오후"],
        today: "오늘",
	};
}(jQuery));
<|endoftext|>


<commit_before>// Copyright 2021-2022, University of Colorado Boulder

/**
 * A ToggleSwitch decorated with a visual label and description with layout for each. To be used in the
 * PreferencesDialog.
 *
 * @author Jesse Greenberg (PhET Interactive Simulations)
 */

import Dimension2 from '../../../dot/js/Dimension2.js';
import merge from '../../../phet-core/js/merge.js';
import { Node } from '../../../scenery/js/imports.js';
import ToggleSwitch from '../../../sun/js/ToggleSwitch.js';
import Tandem from '../../../tandem/js/Tandem.js';
import Utterance from '../../../utterance-queue/js/Utterance.js';
import joist from '../joist.js';

class PreferencesToggleSwitch extends Node {

  /**
   * @param {Property} property
   * @param {*} leftValue
   * @param {*} rightValue
   * @param {Object} [options]
   */
  constructor( property, leftValue, rightValue, options ) {
    options = merge( {

      // {null|Node} - if provided, a label Node to the left of the toggle switch
      labelNode: null,

      // {number} - horizontal spacing between label and toggle switch IF there is no descriptionNode.
      // If a descriptionNode is provided, layout of the labelNode will be relative to this.
      labelSpacing: 10,

      // {null|Node} - if provided, a Node under the ToggleSwitch and label that is meant to describe the purpose
      // of the switch
      descriptionNode: null,

      // {number} - vertical spacing between ToggleSwitch and description Node
      descriptionSpacing: 5,

      // {string|null} - Sets both the inner content and the voicing response for the toggle switch on focus.
      // NOTE: Seeing more overlap like this between PDOM and voicing (which is good) but not sure how it will
      // all work yet. This option is the first of its kind.
      a11yLabel: null,

      // a11y
      // {IAlertable|null} - If provided, these responses will be spoken to describe the change in simulation context
      // for both Voicing and Interactive Description features when the value changes to either leftValue or
      // rightValue.
      leftValueContextResponse: null,
      rightValueContextResponse: null,

      // {Object} - options passed to the actual ToggleSwitch
      toggleSwitchOptions: {
        size: new Dimension2( 36, 18 ),
        trackFillRight: '#64bd5a'
      },

      // phet-io
      tandem: Tandem.REQUIRED
    }, options );
    assert && assert( options.labelNode === null || options.labelNode instanceof Node, 'labelNode is null or inserted as child' );
    assert && assert( options.descriptionNode === null || options.descriptionNode instanceof Node, 'labelNode is null or inserted as child' );

    if ( options.toggleSwitchOptions ) {
      assert && assert( options.toggleSwitchOptions.voicingIgnoreVoicingManagerProperties === undefined, 'PreferencesToggleSwitch creates object responses with a11yLabel option' );
      assert && assert( options.toggleSwitchOptions.innerContent === undefined, 'PreferencesToggleSwitch creates object response with a11yLabel option' );
    }

    super( options );

    const toggleSwitch = new ToggleSwitch( property, leftValue, rightValue, merge( options.toggleSwitchOptions, {

      // enabled:true by default, but disable if fuzzing when supporting voicing
      enabled: !( phet.chipper.isFuzzEnabled() && phet.chipper.queryParameters.supportsVoicing ),

      // pdom
      innerContent: options.a11yLabel,

      // voicing
      voicingIgnoreVoicingManagerProperties: true,
      voicingNameResponse: options.a11yLabel,

      // tandem
      tandem: options.tandem.createTandem( 'toggleSwitch' )
    } ) );

    // a11y - Describe the change in value if context responses were provided in options. Listener needs to be
    // removed on dispose.
    const valueListener = value => {
      const alert = value === rightValue ? options.rightValueContextResponse : options.leftValueContextResponse;

      if ( alert ) {
        this.alertDescriptionUtterance( alert );
        toggleSwitch.voicingSpeakResponse( {
          contextResponse: Utterance.alertableToText( alert )
        } );
      }
    };
    property.lazyLink( valueListener );

    this.addChild( toggleSwitch );
    options.descriptionNode && this.addChild( options.descriptionNode );
    options.labelNode && this.addChild( options.labelNode );

    // layout code, dependent on which components are available
    if ( options.descriptionNode && options.labelNode ) {

      options.descriptionNode.leftTop = options.labelNode.leftBottom.plusXY( 0, options.descriptionSpacing );
      toggleSwitch.centerY = options.labelNode.centerY;

      if ( options.descriptionNode.right - options.labelNode.right < toggleSwitch.width + options.labelSpacing ) {

        // right-aligning the toggleSwitch with the description would result in the toggleSwitch and label overlapping,
        // put the toggleSwitch to the right of the label
        toggleSwitch.left = options.labelNode.right + options.labelSpacing;
      }
      else {

        // the description extends far enough to the right of the label that the toggleSwitch should be right-aligned
        // with it
        toggleSwitch.right = options.descriptionNode.right;
      }
    }
    else if ( options.descriptionNode ) {

      // only a description node, right align with the toggle switch
      options.descriptionNode.rightTop = toggleSwitch.rightBottom.plusXY( 0, options.descriptionSpacing );
    }
    else if ( options.labelNode ) {

      // only a label node, it goes to the left of the toggleSwitch
      options.labelNode.rightCenter = toggleSwitch.leftCenter.minusXY( options.labelSpacing, 0 );
    }

    // @private
    this.disposePreferencesToggleSwitch = () => {
      property.unlink( valueListener );
      toggleSwitch.dispose();
    };
  }

  /**
   * @public
   */
  dispose() {
    this.disposePreferencesToggleSwitch();
    super.dispose();
  }
}

joist.register( 'PreferencesToggleSwitch', PreferencesToggleSwitch );
export default PreferencesToggleSwitch;
<commit_msg>add left and right labels to the PreferencesToggleSwitch, see https://github.com/phetsims/energy-skate-park/issues/338<commit_after>// Copyright 2021-2022, University of Colorado Boulder

/**
 * A ToggleSwitch decorated with a visual label and description with layout for each. To be used in the
 * PreferencesDialog.
 *
 * @author Jesse Greenberg (PhET Interactive Simulations)
 */

import Dimension2 from '../../../dot/js/Dimension2.js';
import merge from '../../../phet-core/js/merge.js';
import { Node } from '../../../scenery/js/imports.js';
import ToggleSwitch from '../../../sun/js/ToggleSwitch.js';
import Tandem from '../../../tandem/js/Tandem.js';
import Utterance from '../../../utterance-queue/js/Utterance.js';
import joist from '../joist.js';

class PreferencesToggleSwitch extends Node {

  /**
   * @param {Property} property
   * @param {*} leftValue
   * @param {*} rightValue
   * @param {Object} [options]
   */
  constructor( property, leftValue, rightValue, options ) {
    options = merge( {

      // {null|Node} - if provided, a label Node to the left of the toggle switch control
      labelNode: null,

      // {number} - horizontal spacing between label for the component and toggle switch IF there is no descriptionNode.
      // If a descriptionNode is provided, layout of the labelNode will be relative to the description.
      // If there is a leftValueLabel it will be the horizontal spacing between the labelNode and the leftValueLabel.
      labelSpacing: 10,

      // {null|Node} - if provided, a label to the left and right of the switch describing the state values
      leftValueLabel: null,
      rightValueLabel: null,

      // {number} - horizontal spacing between the toggle switch and left/right value labels
      valueLabelXSpacing: 8,

      // {null|Node} - if provided, a Node under the ToggleSwitch and label that is meant to describe the purpose
      // of the switch
      descriptionNode: null,

      // {number} - vertical spacing between ToggleSwitch and description Node
      descriptionSpacing: 5,

      // {string|null} - Sets both the inner content and the voicing response for the toggle switch on focus.
      // NOTE: Seeing more overlap like this between PDOM and voicing (which is good) but not sure how it will
      // all work yet. This option is the first of its kind.
      a11yLabel: null,

      // a11y
      // {IAlertable|null} - If provided, these responses will be spoken to describe the change in simulation context
      // for both Voicing and Interactive Description features when the value changes to either leftValue or
      // rightValue.
      leftValueContextResponse: null,
      rightValueContextResponse: null,

      // {Object} - options passed to the actual ToggleSwitch
      toggleSwitchOptions: {
        size: new Dimension2( 36, 18 ),
        trackFillRight: '#64bd5a'
      },

      // phet-io
      tandem: Tandem.REQUIRED
    }, options );
    assert && assert( options.labelNode === null || options.labelNode instanceof Node, 'labelNode is null or inserted as child' );
    assert && assert( options.descriptionNode === null || options.descriptionNode instanceof Node, 'labelNode is null or inserted as child' );

    if ( options.toggleSwitchOptions ) {
      assert && assert( options.toggleSwitchOptions.voicingIgnoreVoicingManagerProperties === undefined, 'PreferencesToggleSwitch creates object responses with a11yLabel option' );
      assert && assert( options.toggleSwitchOptions.innerContent === undefined, 'PreferencesToggleSwitch creates object response with a11yLabel option' );
    }

    super( options );

    const toggleSwitch = new ToggleSwitch( property, leftValue, rightValue, merge( options.toggleSwitchOptions, {

      // enabled:true by default, but disable if fuzzing when supporting voicing
      enabled: !( phet.chipper.isFuzzEnabled() && phet.chipper.queryParameters.supportsVoicing ),

      // pdom
      innerContent: options.a11yLabel,

      // voicing
      voicingIgnoreVoicingManagerProperties: true,
      voicingNameResponse: options.a11yLabel,

      // tandem
      tandem: options.tandem.createTandem( 'toggleSwitch' )
    } ) );

    if ( options.leftValueLabel ) {
      options.leftValueLabel.right = toggleSwitch.left - options.valueLabelXSpacing;
      options.leftValueLabel.centerY = toggleSwitch.centerY;
      toggleSwitch.addChild( options.leftValueLabel );
    }
    if ( options.rightValueLabel ) {
      options.rightValueLabel.left = toggleSwitch.right + options.valueLabelXSpacing;
      options.rightValueLabel.centerY = toggleSwitch.centerY;
      toggleSwitch.addChild( options.rightValueLabel );
    }

    // a11y - Describe the change in value if context responses were provided in options. Listener needs to be
    // removed on dispose.
    const valueListener = value => {
      const alert = value === rightValue ? options.rightValueContextResponse : options.leftValueContextResponse;

      if ( alert ) {
        this.alertDescriptionUtterance( alert );
        toggleSwitch.voicingSpeakResponse( {
          contextResponse: Utterance.alertableToText( alert )
        } );
      }
    };
    property.lazyLink( valueListener );

    this.addChild( toggleSwitch );
    options.descriptionNode && this.addChild( options.descriptionNode );
    options.labelNode && this.addChild( options.labelNode );

    // layout code, dependent on which components are available
    if ( options.descriptionNode && options.labelNode ) {

      options.descriptionNode.leftTop = options.labelNode.leftBottom.plusXY( 0, options.descriptionSpacing );
      toggleSwitch.centerY = options.labelNode.centerY;

      if ( options.descriptionNode.right - options.labelNode.right < toggleSwitch.width + options.labelSpacing ) {

        // right-aligning the toggleSwitch with the description would result in the toggleSwitch and label overlapping,
        // put the toggleSwitch to the right of the label
        toggleSwitch.left = options.labelNode.right + options.labelSpacing;
      }
      else {

        // the description extends far enough to the right of the label that the toggleSwitch should be right-aligned
        // with it
        toggleSwitch.right = options.descriptionNode.right;
      }
    }
    else if ( options.descriptionNode ) {

      // only a description node, right align with the toggle switch
      options.descriptionNode.rightTop = toggleSwitch.rightBottom.plusXY( 0, options.descriptionSpacing );
    }
    else if ( options.labelNode ) {

      // only a label node, it goes to the left of the toggleSwitch
      options.labelNode.rightCenter = toggleSwitch.leftCenter.minusXY( options.labelSpacing, 0 );
    }

    // @private
    this.disposePreferencesToggleSwitch = () => {
      property.unlink( valueListener );
      toggleSwitch.dispose();
    };
  }

  /**
   * @public
   */
  dispose() {
    this.disposePreferencesToggleSwitch();
    super.dispose();
  }
}

joist.register( 'PreferencesToggleSwitch', PreferencesToggleSwitch );
export default PreferencesToggleSwitch;
<|endoftext|>