<commit_before>

define(['glMatrix','../shaders/shader-program','../shaders/letter/vertex','../shaders/letter/fragment','../shaders/stand/vertex','../shaders/stand/fragment', '../shaders/glass/vertex','../shaders/glass/fragment'],
 function(glM,      ShaderProgram, LetterVertex, LetterFragment, StandVertex, StandFragment, GlassVertex, GlassFragment) {

    /**
     * @constructor
     */
    var Renderer = function(gl,scene,viewportWidth, viewportHeight){

        this.viewportWidth = viewportWidth;

        this.viewportHeight = viewportHeight;

        this.viewAngle = 0;

        this.scene = scene;

        this.gl = gl;

        this.letterShader = new ShaderProgram(gl, new LetterVertex(), new LetterFragment());

        this.standShader = new ShaderProgram(gl, new StandVertex(), new StandFragment());

        this.glassShader = new ShaderProgram(gl, new GlassVertex(), new GlassFragment());

    };

    Renderer.LAYER_LETTERS = 'letters';

    Renderer.LAYER_STAND = 'stand';

    Renderer.LAYER_GLASS = 'glass';

    Renderer.prototype.updateViewport = function(width, height){
        this.viewportWidth = width;
        this.viewportHeight = height;
    };

    /**
     * Render all layers and objects
     */
    Renderer.prototype.render = function(){

        var gl = this.gl;

        gl.enable(gl.DEPTH_TEST);                               // Enable depth testing
        gl.depthFunc(gl.LEQUAL);                                // Near things obscure far things

        //gl.clearColor(0.3, 0.3, 0.3, 1.0);                      // Set clear color to black, fully opaque
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);      // Clear the color as well as the depth buffer.

        var cameraPosition = [0,5,4];
        var lightPosition = [2,4,2];

        var c = cameraPosition;
        var point = glM.vec3.fromValues(c[0],c[1],c[2]);

        var mat = glM.mat4.create();

        glM.mat4.identity(mat);

        glM.mat4.rotateY(mat,mat,this.viewAngle);

        glM.vec3.transformMat4(point,point,mat);

        var l = glM.vec3.create();
        glM.vec3.transformMat4(l,lightPosition,mat);

        var viewMatrix = glM.mat4.create();

        glM.mat4.lookAt(viewMatrix,point,[0,1.5,0],[0,1,0]);

        var perspectiveMatrix = glM.mat4.create();

        glM.mat4.perspective(perspectiveMatrix, 45, this.viewportWidth / this.viewportHeight, 0.1, 100);

        glM.mat4.multiply(perspectiveMatrix, perspectiveMatrix,viewMatrix);

        var lightPos = new Float32Array(l);

        this.letterShader.use();

        var uniformLoc = this.letterShader.getUniformLocation("uProjectionMatrix");

        gl.uniformMatrix4fv(uniformLoc, false, perspectiveMatrix); // modify uniform 4x4 matrix

        var resolutionLoc = this.letterShader.getUniformLocation("uRes");

        gl.uniform2fv(resolutionLoc, new Float32Array([this.viewportWidth, this.viewportHeight]));

        var lightPosLoc = this.letterShader.getUniformLocation("uLightPos");
        gl.uniform3fv(lightPosLoc, lightPos);

        this.scene.renderLayer(this.gl, Renderer.LAYER_LETTERS,this.letterShader);

        this.standShader.use();

        uniformLoc = this.standShader.getUniformLocation("uProjectionMatrix");

        gl.uniformMatrix4fv(uniformLoc, false, perspectiveMatrix); // modify uniform 4x4 matrix

        resolutionLoc = this.standShader.getUniformLocation("uRes");

        gl.uniform2fv(resolutionLoc, new Float32Array([this.viewportWidth, this.viewportHeight]));

        lightPosLoc = this.standShader.getUniformLocation("uLightPos");
        gl.uniform3fv(lightPosLoc, lightPos);

        this.scene.renderLayer(this.gl, Renderer.LAYER_STAND,this.standShader);

        this.glassShader.use();

        uniformLoc = this.glassShader.getUniformLocation("uProjectionMatrix");

        gl.uniformMatrix4fv(uniformLoc, false, perspectiveMatrix); // modify uniform 4x4 matrix

        resolutionLoc = this.glassShader.getUniformLocation("uRes");

        gl.uniform2fv(resolutionLoc, new Float32Array([this.viewportWidth, this.viewportHeight]));

        lightPosLoc = this.glassShader.getUniformLocation("uLightPos");
        gl.uniform3fv(lightPosLoc, lightPos);

        var transparentObjects = this.scene.getObjectsByLayer(Renderer.LAYER_GLASS);

        //###########
        //Z-SORTING (pseudocode):
        //###########
        //for each transparent object:
        //
        //objMatrix =  object.getMatrix()
        //
        //resultMatrix = perspectiveMatrix * objMatrix // using glM.mat4.multiply
        //
        //avgVertex = average of object vertices
        //
        //object.screenCoords = apply resultMatrix to avgVertex
        //
        //
        //sort objects by screencoords.z // using transparentObjects = transparentObjects.sort(comparatorFunction); //http://www.w3schools.com/jsref/jsref_sort.asp

        for(var i in transparentObjects){
            var object = transparentObjects[i];
            object.render(this.gl, this.glassShader);

            objMatrix =  object.getMatrix();
            resultMatrix = perspectiveMatrix * objMatrix;

            console.log(object.model.vertices[0]);
        }

    };

    return Renderer;

});
<commit_msg>alfa-Blending<commit_after>

define(['glMatrix','../shaders/shader-program','../shaders/letter/vertex','../shaders/letter/fragment','../shaders/stand/vertex','../shaders/stand/fragment', '../shaders/glass/vertex','../shaders/glass/fragment'],
 function(glM,      ShaderProgram, LetterVertex, LetterFragment, StandVertex, StandFragment, GlassVertex, GlassFragment) {

    /**
     * @constructor
     */
    var Renderer = function(gl,scene,viewportWidth, viewportHeight){

        this.viewportWidth = viewportWidth;

        this.viewportHeight = viewportHeight;

        this.viewAngle = 0;

        this.scene = scene;

        this.gl = gl;

        this.letterShader = new ShaderProgram(gl, new LetterVertex(), new LetterFragment());

        this.standShader = new ShaderProgram(gl, new StandVertex(), new StandFragment());

        this.glassShader = new ShaderProgram(gl, new GlassVertex(), new GlassFragment());

    };

    Renderer.LAYER_LETTERS = 'letters';

    Renderer.LAYER_STAND = 'stand';

    Renderer.LAYER_GLASS = 'glass';

    Renderer.prototype.updateViewport = function(width, height){
        this.viewportWidth = width;
        this.viewportHeight = height;
    };

    /**
     * Render all layers and objects
     */
    Renderer.prototype.render = function(){

        var gl = this.gl;

        gl.enable(gl.DEPTH_TEST);                               // Enable depth testing
        gl.depthFunc(gl.LEQUAL);                                // Near things obscure far things

        //gl.clearColor(0.3, 0.3, 0.3, 1.0);                      // Set clear color to black, fully opaque
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);      // Clear the color as well as the depth buffer.

        gl.enable(gl.BLEND);
        gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

        var cameraPosition = [0,5,4];
        var lightPosition = [2,4,2];

        var c = cameraPosition;
        var point = glM.vec3.fromValues(c[0],c[1],c[2]);

        var mat = glM.mat4.create();

        glM.mat4.identity(mat);

        glM.mat4.rotateY(mat,mat,this.viewAngle);

        glM.vec3.transformMat4(point,point,mat);

        var l = glM.vec3.create();
        glM.vec3.transformMat4(l,lightPosition,mat);

        var viewMatrix = glM.mat4.create();

        glM.mat4.lookAt(viewMatrix,point,[0,1.5,0],[0,1,0]);

        var perspectiveMatrix = glM.mat4.create();

        glM.mat4.perspective(perspectiveMatrix, 45, this.viewportWidth / this.viewportHeight, 0.1, 100);

        glM.mat4.multiply(perspectiveMatrix, perspectiveMatrix,viewMatrix);

        var lightPos = new Float32Array(l);

        this.letterShader.use();

        var uniformLoc = this.letterShader.getUniformLocation("uProjectionMatrix");

        gl.uniformMatrix4fv(uniformLoc, false, perspectiveMatrix); // modify uniform 4x4 matrix

        var resolutionLoc = this.letterShader.getUniformLocation("uRes");

        gl.uniform2fv(resolutionLoc, new Float32Array([this.viewportWidth, this.viewportHeight]));

        var lightPosLoc = this.letterShader.getUniformLocation("uLightPos");

        gl.uniform3fv(lightPosLoc, lightPos);

        this.scene.renderLayer(this.gl, Renderer.LAYER_LETTERS,this.letterShader);

        this.standShader.use();

        uniformLoc = this.standShader.getUniformLocation("uProjectionMatrix");

        gl.uniformMatrix4fv(uniformLoc, false, perspectiveMatrix); // modify uniform 4x4 matrix

        resolutionLoc = this.standShader.getUniformLocation("uRes");

        gl.uniform2fv(resolutionLoc, new Float32Array([this.viewportWidth, this.viewportHeight]));

        lightPosLoc = this.standShader.getUniformLocation("uLightPos");

        gl.uniform3fv(lightPosLoc, lightPos);

        this.scene.renderLayer(this.gl, Renderer.LAYER_STAND,this.standShader);

        this.glassShader.use();

        uniformLoc = this.glassShader.getUniformLocation("uProjectionMatrix");

        gl.uniformMatrix4fv(uniformLoc, false, perspectiveMatrix); // modify uniform 4x4 matrix

        resolutionLoc = this.glassShader.getUniformLocation("uRes");

        gl.uniform2fv(resolutionLoc, new Float32Array([this.viewportWidth, this.viewportHeight]));

        lightPosLoc = this.glassShader.getUniformLocation("uLightPos");
        gl.uniform3fv(lightPosLoc, lightPos);

        var transparentObjects = this.scene.getObjectsByLayer(Renderer.LAYER_GLASS);

        //###########
        //Z-SORTING (pseudocode):
        //###########
        //for each transparent object:
        //
        //objMatrix =  object.getMatrix()
        //
        //resultMatrix = perspectiveMatrix * objMatrix // using glM.mat4.multiply
        //
        //avgVertex = average of object vertices
        //
        //object.screenCoords = apply resultMatrix to avgVertex
        //
        //
        //sort objects by screencoords.z // using transparentObjects = transparentObjects.sort(comparatorFunction); //http://www.w3schools.com/jsref/jsref_sort.asp

        for(var i in transparentObjects){
            var object = transparentObjects[i];
            object.render(this.gl, this.glassShader);

            objMatrix =  object.getMatrix();
            resultMatrix = perspectiveMatrix * objMatrix;

            var xAvg = object.model.vertices[0];
            var yAvg = object.model.vertices[1];
            var zAvg = object.model.vertices[2];

        }

    };

    return Renderer;

});
<|endoftext|>


<commit_before>import { render } from "preact";
import { useState, useRef, useEffect, useMemo } from "preact/hooks";
import { html } from "htm/preact";
import { max as d3max } from "d3-array";
import { scaleSqrt } from "d3-scale";

import { format as formatBytes } from "bytes";

import webcola from "webcola";

import {
  COLOR_DEFAULT_OWN_SOURCE,
  COLOR_DEFAULT_VENDOR_SOURCE,
  COLOR_BASE
} from "./color";

import { LABELS, getAvailableSizeOptions } from "./sizes";

import "./style/style-network.scss";

const NODE_MODULES = /.*(?:\/|\\\\)?node_modules(?:\/|\\\\)([^/\\]+)(?:\/|\\\\).+/;

const color = ({ renderedLength, id }) =>
  renderedLength === 0
    ? COLOR_BASE
    : id.match(NODE_MODULES)
    ? COLOR_DEFAULT_VENDOR_SOURCE
    : COLOR_DEFAULT_OWN_SOURCE;

const Tooltip = ({
  node,
  visible,
  importedByCache,
  sizeProperty,
  availableSizeProperties
}) => {
  const ref = useRef();
  const [style, setStyle] = useState({});
  const content = useMemo(() => {
    if (!node) return null;

    const uid = node.uid;

    return html`

${node.id}

      ${availableSizeProperties.map(sizeProp => {
        if (sizeProp === sizeProperty) {
          return html`

              ${LABELS[sizeProp]}:${" "}${formatBytes(node[sizeProp])}

          `;
        } else {
          return html`

              ${LABELS[sizeProp]}:${" "} ${formatBytes(node[sizeProp])}

          `;
        }
      })}
      ${uid &&
        importedByCache.has(uid) &&
        html`

Imported By:

            ${[...new Set(importedByCache.get(uid).map(({ id }) => id))].map(
              id =>
                html`

${id}

                `
            )}

        `}
    `;
  }, [node]);

  const updatePosition = mouseCoords => {
    const pos = {
      left: mouseCoords.x + Tooltip.marginX,
      top: mouseCoords.y + Tooltip.marginY
    };

    const boundingRect = ref.current.getBoundingClientRect();

    if (pos.left + boundingRect.width > window.innerWidth) {
      // Shifting horizontally
      pos.left = window.innerWidth - boundingRect.width;
    }

    if (pos.top + boundingRect.height > window.innerHeight) {
      // Flipping vertically
      pos.top = mouseCoords.y - Tooltip.marginY - boundingRect.height;
    }

    setStyle(pos);
  };

  const handleMouseMove = event => {
    updatePosition({
      x: event.pageX,
      y: event.pageY
    });
  };

  useEffect(() => {
    document.addEventListener("mousemove", handleMouseMove, true);
    return () => {
      document.removeEventListener("mousemove", handleMouseMove, true);
    };
  }, []);

  return html`

      ${content}

  `;
};

Tooltip.marginX = 10;
Tooltip.marginY = 30;

const Network = ({
  width,
  height,
  links,
  nodes,
  size,
  onNodeHover,
  sizeProperty
}) => {
  return html`
    <svg xmlns="http://www.w3.org/2000/svg" viewBox=${`0 0 ${width} ${height}`}>
      <g stroke="#999" stroke-opacity="0.6">
        ${links.map(link => {
          return html`
            <line
              stroke-width="1"
              x1=${link.source.x}
              y1=${link.source.y}
              x2=${link.target.x}
              y2=${link.target.y}
            />
          `;
        })}
      </g>
      <g stroke="#fff" stroke-width="1.5">
        ${nodes.map(node => {
          return html`
            <circle
              r=${size(node[sizeProperty])}
              fill=${color(node)}
              cx=${node.x}
              cy=${node.y}
              onMouseOver=${evt => {
                evt.stopPropagation();
                onNodeHover(node);
              }}
            />
          `;
        })}
      </g>
    </svg>
  `;
};

const Chart = ({
  width,
  height,
  nodes,
  links,
  size,
  sizeProperty,
  availableSizeProperties,
  importedCache,
  importedByCache
}) => {
  const [showTooltip, setShowTooltip] = useState(false);
  const [tooltipNode, setTooltipNode] = useState(null);

  const handleMouseOut = () => {
    setShowTooltip(false);
  };

  useEffect(() => {
    document.addEventListener("mouseover", handleMouseOut);
    return () => {
      document.removeEventListener("mouseover", handleMouseOut);
    };
  }, []);

  return html`
    <${Network}
      width=${width}
      height=${height}
      links=${links}
      nodes=${nodes}
      size=${size}
      onNodeHover=${node => {
        setTooltipNode(node);
        setShowTooltip(true);
      }}
      sizeProperty=${sizeProperty}
      availableSizeProperties=${availableSizeProperties}
    />
    <${Tooltip}
      visible=${showTooltip}
      node=${tooltipNode}
      importedByCache=${importedByCache}
      importedCache=${importedCache}
      sizeProperty=${sizeProperty}
      availableSizeProperties=${availableSizeProperties}
    />
  `;
};

const SideBar = ({
  availableSizeProperties,
  sizeProperty,
  setSizeProperty
}) => {
  const handleChange = sizeProp => () => {
    if (sizeProp !== sizeProperty) {
      setSizeProperty(sizeProp);
    }
  };
  return html`

        ${availableSizeProperties.length > 1 &&
          availableSizeProperties.map(sizeProp => {
            const id = `selector-${sizeProp}`;
            return html`

                <input
                  type="radio"
                  id=${id}
                  checked=${sizeProp === sizeProperty}
                  onChange=${handleChange(sizeProp)}
                />

                  ${LABELS[sizeProp]}

            `;
          })}

  `;
};

const Main = ({
  width,
  height,
  data: { nodes: origNodes, links: origLinks, options }
}) => {
  const availableSizeProperties = getAvailableSizeOptions(options);

  const [sizeProperty, setSizeProperty] = useState(availableSizeProperties[0]);

  const maxLines = d3max(Object.values(origNodes), d => d[sizeProperty]);
  const size = scaleSqrt()
    .domain([1, maxLines])
    .range([5, 30]);

  const nodes = Object.entries(origNodes).map(([uid, node]) => {
    const radius = size(node[sizeProperty]) + 1;
    return {
      uid,
      ...node,
      width: radius * 2,
      height: radius * 2,
      radius
    };
  });
  const nodesCache = new Map(nodes.map(d => [d.uid, d]));
  const links = origLinks.map(({ source, target }) => ({
    source: nodesCache.get(source),
    target: nodesCache.get(target),
    value: 1
  }));

  const cola = webcola.adaptor({}).size([width, height]);

  const paddingX = 50;
  const paddingY = 50;

  const pageBounds = {
    x: paddingX,
    y: paddingY,
    width: width - paddingX,
    height: height - paddingY
  };

  const realGraphNodes = nodes.slice(0);
  const topLeft = { x: pageBounds.x, y: pageBounds.y, fixed: true };
  const tlIndex = nodes.push(topLeft) - 1;
  const bottomRight = {
    x: pageBounds.x + pageBounds.width,
    y: pageBounds.y + pageBounds.height,
    fixed: true
  };
  const brIndex = nodes.push(bottomRight) - 1;
  const constraints = [];
  for (let i = 0; i < realGraphNodes.length; i++) {
    const node = realGraphNodes[i];
    constraints.push({
      axis: "x",
      type: "separation",
      left: tlIndex,
      right: i,
      gap: node.radius
    });
    constraints.push({
      axis: "y",
      type: "separation",
      left: tlIndex,
      right: i,
      gap: node.radius
    });
    constraints.push({
      axis: "x",
      type: "separation",
      left: i,
      right: brIndex,
      gap: node.radius
    });
    constraints.push({
      axis: "y",
      type: "separation",
      left: i,
      right: brIndex,
      gap: node.radius
    });
  }
  cola
    .nodes(nodes)
    .links(links)
    .constraints(constraints)
    .jaccardLinkLengths(50, 0.7)
    .avoidOverlaps(true)
    .start(50, 50, 50)
    .stop();

  const importedByCache = new Map();
  const importedCache = new Map();

  for (const { source, target } of origLinks || []) {
    if (!importedByCache.has(target)) {
      importedByCache.set(target, []);
    }
    if (!importedCache.has(source)) {
      importedCache.set(source, []);
    }

    importedByCache.get(target).push({ uid: source, ...origNodes[source] });
    importedCache.get(source).push({ uid: target, ...origNodes[target] });
  }

  return html`
    <${SideBar}
      sizeProperty=${sizeProperty}
      availableSizeProperties=${availableSizeProperties}
      setSizeProperty=${setSizeProperty}
    />
    <${Chart}
      nodes=${realGraphNodes}
      links=${links}
      size=${size}
      color=${color}
      width=${width}
      height=${height}
      sizeProperty=${sizeProperty}
      availableSizeProperties=${availableSizeProperties}
      importedByCache=${importedByCache}
      importedCache=${importedCache}
    />
  `;
};

const drawChart = (parentNode, data, width, height) => {
  render(
    html`
      <${Main} data=${data} width=${width} height=${height} />
    `,
    parentNode
  );
};

export default drawChart;
<commit_msg>Add console time statements<commit_after>import { render } from "preact";
import { useState, useRef, useEffect, useMemo } from "preact/hooks";
import { html } from "htm/preact";
import { max as d3max } from "d3-array";
import { scaleSqrt } from "d3-scale";

import { format as formatBytes } from "bytes";

import webcola from "webcola";

import {
  COLOR_DEFAULT_OWN_SOURCE,
  COLOR_DEFAULT_VENDOR_SOURCE,
  COLOR_BASE
} from "./color";

import { LABELS, getAvailableSizeOptions } from "./sizes";

import "./style/style-network.scss";

const NODE_MODULES = /.*(?:\/|\\\\)?node_modules(?:\/|\\\\)([^/\\]+)(?:\/|\\\\).+/;

const color = ({ renderedLength, id }) =>
  renderedLength === 0
    ? COLOR_BASE
    : id.match(NODE_MODULES)
    ? COLOR_DEFAULT_VENDOR_SOURCE
    : COLOR_DEFAULT_OWN_SOURCE;

const Tooltip = ({
  node,
  visible,
  importedByCache,
  sizeProperty,
  availableSizeProperties
}) => {
  const ref = useRef();
  const [style, setStyle] = useState({});
  const content = useMemo(() => {
    if (!node) return null;

    const uid = node.uid;

    return html`

${node.id}

      ${availableSizeProperties.map(sizeProp => {
        if (sizeProp === sizeProperty) {
          return html`

              ${LABELS[sizeProp]}:${" "}${formatBytes(node[sizeProp])}

          `;
        } else {
          return html`

              ${LABELS[sizeProp]}:${" "} ${formatBytes(node[sizeProp])}

          `;
        }
      })}
      ${uid &&
        importedByCache.has(uid) &&
        html`

Imported By:

            ${[...new Set(importedByCache.get(uid).map(({ id }) => id))].map(
              id =>
                html`

${id}

                `
            )}

        `}
    `;
  }, [node]);

  const updatePosition = mouseCoords => {
    const pos = {
      left: mouseCoords.x + Tooltip.marginX,
      top: mouseCoords.y + Tooltip.marginY
    };

    const boundingRect = ref.current.getBoundingClientRect();

    if (pos.left + boundingRect.width > window.innerWidth) {
      // Shifting horizontally
      pos.left = window.innerWidth - boundingRect.width;
    }

    if (pos.top + boundingRect.height > window.innerHeight) {
      // Flipping vertically
      pos.top = mouseCoords.y - Tooltip.marginY - boundingRect.height;
    }

    setStyle(pos);
  };

  const handleMouseMove = event => {
    updatePosition({
      x: event.pageX,
      y: event.pageY
    });
  };

  useEffect(() => {
    document.addEventListener("mousemove", handleMouseMove, true);
    return () => {
      document.removeEventListener("mousemove", handleMouseMove, true);
    };
  }, []);

  return html`

      ${content}

  `;
};

Tooltip.marginX = 10;
Tooltip.marginY = 30;

const Network = ({
  width,
  height,
  links,
  nodes,
  size,
  onNodeHover,
  sizeProperty
}) => {
  return html`
    <svg xmlns="http://www.w3.org/2000/svg" viewBox=${`0 0 ${width} ${height}`}>
      <g stroke="#999" stroke-opacity="0.6">
        ${links.map(link => {
          return html`
            <line
              stroke-width="1"
              x1=${link.source.x}
              y1=${link.source.y}
              x2=${link.target.x}
              y2=${link.target.y}
            />
          `;
        })}
      </g>
      <g stroke="#fff" stroke-width="1.5">
        ${nodes.map(node => {
          return html`
            <circle
              r=${size(node[sizeProperty])}
              fill=${color(node)}
              cx=${node.x}
              cy=${node.y}
              onMouseOver=${evt => {
                evt.stopPropagation();
                onNodeHover(node);
              }}
            />
          `;
        })}
      </g>
    </svg>
  `;
};

const Chart = ({
  width,
  height,
  nodes,
  links,
  size,
  sizeProperty,
  availableSizeProperties,
  importedCache,
  importedByCache
}) => {
  const [showTooltip, setShowTooltip] = useState(false);
  const [tooltipNode, setTooltipNode] = useState(null);

  const handleMouseOut = () => {
    setShowTooltip(false);
  };

  useEffect(() => {
    document.addEventListener("mouseover", handleMouseOut);
    return () => {
      document.removeEventListener("mouseover", handleMouseOut);
    };
  }, []);

  return html`
    <${Network}
      width=${width}
      height=${height}
      links=${links}
      nodes=${nodes}
      size=${size}
      onNodeHover=${node => {
        setTooltipNode(node);
        setShowTooltip(true);
      }}
      sizeProperty=${sizeProperty}
      availableSizeProperties=${availableSizeProperties}
    />
    <${Tooltip}
      visible=${showTooltip}
      node=${tooltipNode}
      importedByCache=${importedByCache}
      importedCache=${importedCache}
      sizeProperty=${sizeProperty}
      availableSizeProperties=${availableSizeProperties}
    />
  `;
};

const SideBar = ({
  availableSizeProperties,
  sizeProperty,
  setSizeProperty
}) => {
  const handleChange = sizeProp => () => {
    if (sizeProp !== sizeProperty) {
      setSizeProperty(sizeProp);
    }
  };
  return html`

        ${availableSizeProperties.length > 1 &&
          availableSizeProperties.map(sizeProp => {
            const id = `selector-${sizeProp}`;
            return html`

                <input
                  type="radio"
                  id=${id}
                  checked=${sizeProp === sizeProperty}
                  onChange=${handleChange(sizeProp)}
                />

                  ${LABELS[sizeProp]}

            `;
          })}

  `;
};

const Main = ({
  width,
  height,
  data: { nodes: origNodes, links: origLinks, options }
}) => {
  const availableSizeProperties = getAvailableSizeOptions(options);

  const [sizeProperty, setSizeProperty] = useState(availableSizeProperties[0]);

  console.time("size scale");
  const maxLines = d3max(Object.values(origNodes), d => d[sizeProperty]);
  const size = scaleSqrt()
    .domain([1, maxLines])
    .range([5, 30]);
  console.timeEnd("size scale");

  console.time("nodes & links");
  const nodes = Object.entries(origNodes).map(([uid, node]) => {
    const radius = size(node[sizeProperty]) + 1;
    return {
      uid,
      ...node,
      width: radius * 2,
      height: radius * 2,
      radius
    };
  });
  const nodesCache = new Map(nodes.map(d => [d.uid, d]));
  const links = origLinks.map(({ source, target }) => ({
    source: nodesCache.get(source),
    target: nodesCache.get(target),
    value: 1
  }));
  console.timeEnd("nodes & links");

  console.time("constraints");
  const cola = webcola.adaptor({}).size([width, height]);

  const paddingX = 50;
  const paddingY = 50;

  const pageBounds = {
    x: paddingX,
    y: paddingY,
    width: width - paddingX,
    height: height - paddingY
  };

  const realGraphNodes = nodes.slice(0);
  const topLeft = { x: pageBounds.x, y: pageBounds.y, fixed: true };
  const tlIndex = nodes.push(topLeft) - 1;
  const bottomRight = {
    x: pageBounds.x + pageBounds.width,
    y: pageBounds.y + pageBounds.height,
    fixed: true
  };
  const brIndex = nodes.push(bottomRight) - 1;
  const constraints = [];
  for (let i = 0; i < realGraphNodes.length; i++) {
    const node = realGraphNodes[i];
    constraints.push({
      axis: "x",
      type: "separation",
      left: tlIndex,
      right: i,
      gap: node.radius
    });
    constraints.push({
      axis: "y",
      type: "separation",
      left: tlIndex,
      right: i,
      gap: node.radius
    });
    constraints.push({
      axis: "x",
      type: "separation",
      left: i,
      right: brIndex,
      gap: node.radius
    });
    constraints.push({
      axis: "y",
      type: "separation",
      left: i,
      right: brIndex,
      gap: node.radius
    });
  }
  console.timeEnd("constraints");

  console.time("cola");
  cola
    .nodes(nodes)
    .links(links)
    .constraints(constraints)
    .jaccardLinkLengths(50, 0.7)
    .avoidOverlaps(true)
    .start(50, 50, 50)
    .stop();

  console.timeEnd("cola");
  const importedByCache = new Map();
  const importedCache = new Map();

  for (const { source, target } of origLinks || []) {
    if (!importedByCache.has(target)) {
      importedByCache.set(target, []);
    }
    if (!importedCache.has(source)) {
      importedCache.set(source, []);
    }

    importedByCache.get(target).push({ uid: source, ...origNodes[source] });
    importedCache.get(source).push({ uid: target, ...origNodes[target] });
  }

  return html`
    <${SideBar}
      sizeProperty=${sizeProperty}
      availableSizeProperties=${availableSizeProperties}
      setSizeProperty=${setSizeProperty}
    />
    <${Chart}
      nodes=${realGraphNodes}
      links=${links}
      size=${size}
      color=${color}
      width=${width}
      height=${height}
      sizeProperty=${sizeProperty}
      availableSizeProperties=${availableSizeProperties}
      importedByCache=${importedByCache}
      importedCache=${importedCache}
    />
  `;
};

const drawChart = (parentNode, data, width, height) => {
  render(
    html`
      <${Main} data=${data} width=${width} height=${height} />
    `,
    parentNode
  );
};

export default drawChart;
<|endoftext|>


<commit_before>// @flow

import type { ThreadStore } from 'lib/types/thread-types';
import { type EntryStore } from 'lib/types/entry-types';
import type { LoadingStatus } from 'lib/types/loading-types';
import type { CurrentUserInfo, UserInfo } from 'lib/types/user-types';
import type { MessageStore } from 'lib/types/message-types';
import type { PersistState } from 'redux-persist/src/types';
import {
  type NotifPermissionAlertInfo,
  defaultNotifPermissionAlertInfo,
} from '../push/alerts';
import {
  type CalendarFilter,
  defaultCalendarFilters,
} from 'lib/types/filter-types';
import { setNewSessionActionType } from 'lib/utils/action-utils';
import { updateTypes } from 'lib/types/update-types';
import { setDeviceTokenActionTypes } from 'lib/actions/device-actions';
import {
  type ConnectionInfo,
  defaultConnectionInfo,
  incrementalStateSyncActionType,
} from 'lib/types/socket-types';
import type { Dimensions } from 'lib/types/media-types';
import {
  type ConnectivityInfo,
  defaultConnectivityInfo,
} from '../types/connectivity';
import type { Dispatch } from 'lib/types/redux-types';
import { type GlobalThemeInfo, defaultGlobalThemeInfo } from '../types/themes';
import {
  type DeviceCameraInfo,
  defaultDeviceCameraInfo,
} from '../types/camera';
import type { Orientations } from 'react-native-orientation-locker';
import type { ClientReportCreationRequest } from 'lib/types/report-types';
import type { SetSessionPayload } from 'lib/types/session-types';

import thunk from 'redux-thunk';
import { createStore, applyMiddleware, type Store, compose } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import { createReactNavigationReduxMiddleware } from 'react-navigation-redux-helpers';
import {
  AppState as NativeAppState,
  Platform,
  Dimensions as NativeDimensions,
  Alert,
} from 'react-native';
import Orientation from 'react-native-orientation-locker';

import baseReducer from 'lib/reducers/master-reducer';
import { reduxLoggerMiddleware } from 'lib/utils/redux-logger';
import { invalidSessionDowngrade } from 'lib/shared/account-utils';
import {
  logOutActionTypes,
  deleteAccountActionTypes,
} from 'lib/actions/user-actions';

import { activeMessageListSelector } from '../navigation/nav-selectors';
import {
  resetUserStateActionType,
  recordNotifPermissionAlertActionType,
  recordAndroidNotificationActionType,
  clearAndroidNotificationsActionType,
  rescindAndroidNotificationActionType,
  updateDimensionsActiveType,
  updateConnectivityActiveType,
  updateThemeInfoActionType,
  updateDeviceCameraInfoActionType,
  updateDeviceOrientationActionType,
  updateThreadLastNavigatedActionType,
  backgroundActionTypes,
} from './action-types';
import RootNavigator from '../navigation/root-navigator.react';
import { type NavInfo, defaultNavInfo } from '../navigation/default-state';
import { reduceThreadIDsToNotifIDs } from '../push/reducer';
import { persistConfig, setPersistor } from './persist';
import {
  defaultURLPrefix,
  natServer,
  setCustomServer,
} from '../utils/url-utils';
import reactotron from '../reactotron';
import reduceDrafts from '../reducers/draft-reducer';

export type AppState = {|
  navInfo: NavInfo,
  currentUserInfo: ?CurrentUserInfo,
  entryStore: EntryStore,
  threadStore: ThreadStore,
  userInfos: { [id: string]: UserInfo },
  messageStore: MessageStore,
  drafts: { [key: string]: string },
  updatesCurrentAsOf: number,
  loadingStatuses: { [key: string]: { [idx: number]: LoadingStatus } },
  calendarFilters: $ReadOnlyArray<CalendarFilter>,
  cookie: ?string,
  deviceToken: ?string,
  urlPrefix: string,
  customServer: ?string,
  threadIDsToNotifIDs: { [threadID: string]: string[] },
  notifPermissionAlertInfo: NotifPermissionAlertInfo,
  connection: ConnectionInfo,
  watchedThreadIDs: $ReadOnlyArray<string>,
  foreground: boolean,
  nextLocalID: number,
  queuedReports: $ReadOnlyArray<ClientReportCreationRequest>,
  _persist: ?PersistState,
  sessionID?: void,
  dimensions: Dimensions,
  connectivity: ConnectivityInfo,
  globalThemeInfo: GlobalThemeInfo,
  deviceCameraInfo: DeviceCameraInfo,
  deviceOrientation: Orientations,
  frozen: boolean,
|};

const { height, width } = NativeDimensions.get('window');
const defaultState = ({
  navInfo: defaultNavInfo,
  currentUserInfo: null,
  entryStore: {
    entryInfos: {},
    daysToEntries: {},
    lastUserInteractionCalendar: 0,
    inconsistencyReports: [],
  },
  threadStore: {
    threadInfos: {},
    inconsistencyReports: [],
  },
  userInfos: {},
  messageStore: {
    messages: {},
    threads: {},
    local: {},
    currentAsOf: 0,
  },
  drafts: {},
  updatesCurrentAsOf: 0,
  loadingStatuses: {},
  calendarFilters: defaultCalendarFilters,
  cookie: null,
  deviceToken: null,
  urlPrefix: defaultURLPrefix(),
  customServer: natServer,
  threadIDsToNotifIDs: {},
  notifPermissionAlertInfo: defaultNotifPermissionAlertInfo,
  connection: defaultConnectionInfo(Platform.OS),
  watchedThreadIDs: [],
  foreground: true,
  nextLocalID: 0,
  queuedReports: [],
  _persist: null,
  dimensions: { height, width },
  connectivity: defaultConnectivityInfo,
  globalThemeInfo: defaultGlobalThemeInfo,
  deviceCameraInfo: defaultDeviceCameraInfo,
  deviceOrientation: Orientation.getInitialOrientation(),
  frozen: false,
}: AppState);

function reducer(state: AppState = defaultState, action: *) {
  if (
    (action.type === setNewSessionActionType &&
      invalidSessionDowngrade(
        state,
        action.payload.sessionChange.currentUserInfo,
        action.payload.preRequestUserState,
      )) ||
    (action.type === logOutActionTypes.success &&
      invalidSessionDowngrade(
        state,
        action.payload.currentUserInfo,
        action.payload.preRequestUserState,
      )) ||
    (action.type === deleteAccountActionTypes.success &&
      invalidSessionDowngrade(
        state,
        action.payload.currentUserInfo,
        action.payload.preRequestUserState,
      ))
  ) {
    return state;
  }
  if (
    action.type === recordAndroidNotificationActionType ||
    action.type === clearAndroidNotificationsActionType ||
    action.type === rescindAndroidNotificationActionType
  ) {
    return {
      ...state,
      threadIDsToNotifIDs: reduceThreadIDsToNotifIDs(
        state.threadIDsToNotifIDs,
        action,
      ),
    };
  } else if (action.type === setCustomServer) {
    return {
      ...state,
      customServer: action.payload,
    };
  } else if (action.type === recordNotifPermissionAlertActionType) {
    return {
      ...state,
      notifPermissionAlertInfo: {
        totalAlerts: state.notifPermissionAlertInfo.totalAlerts + 1,
        lastAlertTime: action.payload.time,
      },
    };
  } else if (action.type === resetUserStateActionType) {
    const cookie =
      state.cookie && state.cookie.startsWith('anonymous=')
        ? state.cookie
        : null;
    const currentUserInfo =
      state.currentUserInfo && state.currentUserInfo.anonymous
        ? state.currentUserInfo
        : null;
    return {
      ...state,
      currentUserInfo,
      cookie,
    };
  } else if (action.type === updateDimensionsActiveType) {
    return {
      ...state,
      dimensions: action.payload,
    };
  } else if (action.type === updateConnectivityActiveType) {
    return {
      ...state,
      connectivity: action.payload,
    };
  } else if (action.type === updateThemeInfoActionType) {
    return {
      ...state,
      globalThemeInfo: {
        ...state.globalThemeInfo,
        ...action.payload,
      },
    };
  } else if (action.type === updateDeviceCameraInfoActionType) {
    return {
      ...state,
      deviceCameraInfo: {
        ...state.deviceCameraInfo,
        ...action.payload,
      },
    };
  } else if (action.type === updateDeviceOrientationActionType) {
    return {
      ...state,
      deviceOrientation: action.payload,
    };
  } else if (action.type === setDeviceTokenActionTypes.started) {
    return {
      ...state,
      deviceToken: action.payload,
    };
  } else if (action.type === updateThreadLastNavigatedActionType) {
    const { threadID, time } = action.payload;
    if (state.messageStore.threads[threadID]) {
      state = {
        ...state,
        messageStore: {
          ...state.messageStore,
          threads: {
            ...state.messageStore.threads,
            [threadID]: {
              ...state.messageStore.threads[threadID],
              lastNavigatedTo: time,
            },
          },
        },
      };
    }
  }

  if (action.type === setNewSessionActionType) {
    sessionInvalidationAlert(action.payload);
    state = {
      ...state,
      cookie: action.payload.sessionChange.cookie,
    };
  } else if (action.type === incrementalStateSyncActionType) {
    let wipeDeviceToken = false;
    for (let update of action.payload.updatesResult.newUpdates) {
      if (
        update.type === updateTypes.BAD_DEVICE_TOKEN &&
        update.deviceToken === state.deviceToken
      ) {
        wipeDeviceToken = true;
        break;
      }
    }
    if (wipeDeviceToken) {
      state = {
        ...state,
        deviceToken: null,
      };
    }
  }

  state = {
    ...baseReducer(state, action),
    drafts: reduceDrafts(state.drafts, action),
  };

  const navigationState = RootNavigator.router.getStateForAction(
    action,
    state.navInfo.navigationState,
  );
  if (navigationState && navigationState !== state.navInfo.navigationState) {
    state = {
      ...state,
      navInfo: {
        ...state.navInfo,
        navigationState,
      },
    };
  }

  return validateState(state, action);
}

function sessionInvalidationAlert(payload: SetSessionPayload) {
  if (!payload.sessionChange.cookieInvalidated) {
    return;
  }
  if (payload.error === 'client_version_unsupported') {
    const app = Platform.select({
      ios: 'Testflight',
      android: 'Play Store',
    });
    Alert.alert(
      'App out of date',
      "Your app version is pretty old, and the server doesn't know how to " +
        `speak to it anymore. Please use the ${app} app to update!`,
      [{ text: 'OK' }],
    );
  } else {
    Alert.alert(
      'Session invalidated',
      "We're sorry, but your session was invalidated by the server. " +
        'Please log in again.',
      [{ text: 'OK' }],
    );
  }
}

function validateState(state: AppState, action: *): AppState {
  const activeThread = activeMessageListSelector({
    state: state.navInfo.navigationState,
    dispatch: () => true,
  });
  if (
    activeThread &&
    (NativeAppState.currentState === 'active' ||
      (appLastBecameInactive + 10000 < Date.now() &&
        !backgroundActionTypes.has(action.type))) &&
    state.threadStore.threadInfos[activeThread] &&
    state.threadStore.threadInfos[activeThread].currentUser.unread
  ) {
    // Makes sure a currently focused thread is never unread. Note that we
    // consider a backgrounded NativeAppState to actually be active if it last
    // changed to inactive more than 10 seconds ago. This is because there is a
    // delay when NativeAppState is updating in response to a foreground, and
    // actions don't get processed more than 10 seconds after a backgrounding
    // anyways. However we don't consider this for action types that can be
    // expected to happen while the app is backgrounded.
    state = {
      ...state,
      threadStore: {
        ...state.threadStore,
        threadInfos: {
          ...state.threadStore.threadInfos,
          [activeThread]: {
            ...state.threadStore.threadInfos[activeThread],
            currentUser: {
              ...state.threadStore.threadInfos[activeThread].currentUser,
              unread: false,
            },
          },
        },
      },
    };
  }
  return state;
}

let appLastBecameInactive = 0;
function appBecameInactive() {
  appLastBecameInactive = Date.now();
}

const middleware = applyMiddleware(
  thunk,
  createReactNavigationReduxMiddleware(
    (state: AppState) => state.navInfo.navigationState,
  ),
  reduxLoggerMiddleware,
);
const composeFunc = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  : compose;
let enhancers;
if (reactotron) {
  enhancers = composeFunc(middleware, reactotron.createEnhancer());
} else {
  enhancers = composeFunc(middleware);
}

const store: Store<AppState, *> = createStore(
  persistReducer(persistConfig, reducer),
  defaultState,
  enhancers,
);
const persistor = persistStore(store);
setPersistor(persistor);

const unsafeDispatch: any = store.dispatch;
const dispatch: Dispatch = unsafeDispatch;

export { store, dispatch, appBecameInactive };
<commit_msg>[native] Use global nav context in fixUnreadActiveThread<commit_after>// @flow

import type { ThreadStore } from 'lib/types/thread-types';
import { type EntryStore } from 'lib/types/entry-types';
import type { LoadingStatus } from 'lib/types/loading-types';
import type { CurrentUserInfo, UserInfo } from 'lib/types/user-types';
import type { MessageStore } from 'lib/types/message-types';
import type { PersistState } from 'redux-persist/src/types';
import {
  type NotifPermissionAlertInfo,
  defaultNotifPermissionAlertInfo,
} from '../push/alerts';
import {
  type CalendarFilter,
  defaultCalendarFilters,
} from 'lib/types/filter-types';
import { setNewSessionActionType } from 'lib/utils/action-utils';
import { updateTypes } from 'lib/types/update-types';
import { setDeviceTokenActionTypes } from 'lib/actions/device-actions';
import {
  type ConnectionInfo,
  defaultConnectionInfo,
  incrementalStateSyncActionType,
} from 'lib/types/socket-types';
import type { Dimensions } from 'lib/types/media-types';
import {
  type ConnectivityInfo,
  defaultConnectivityInfo,
} from '../types/connectivity';
import type { Dispatch } from 'lib/types/redux-types';
import { type GlobalThemeInfo, defaultGlobalThemeInfo } from '../types/themes';
import {
  type DeviceCameraInfo,
  defaultDeviceCameraInfo,
} from '../types/camera';
import type { Orientations } from 'react-native-orientation-locker';
import type { ClientReportCreationRequest } from 'lib/types/report-types';
import type { SetSessionPayload } from 'lib/types/session-types';

import thunk from 'redux-thunk';
import { createStore, applyMiddleware, type Store, compose } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import { createReactNavigationReduxMiddleware } from 'react-navigation-redux-helpers';
import {
  AppState as NativeAppState,
  Platform,
  Dimensions as NativeDimensions,
  Alert,
} from 'react-native';
import Orientation from 'react-native-orientation-locker';

import baseReducer from 'lib/reducers/master-reducer';
import { reduxLoggerMiddleware } from 'lib/utils/redux-logger';
import { invalidSessionDowngrade } from 'lib/shared/account-utils';
import {
  logOutActionTypes,
  deleteAccountActionTypes,
} from 'lib/actions/user-actions';

import { activeMessageListSelector } from '../navigation/nav-selectors';
import {
  resetUserStateActionType,
  recordNotifPermissionAlertActionType,
  recordAndroidNotificationActionType,
  clearAndroidNotificationsActionType,
  rescindAndroidNotificationActionType,
  updateDimensionsActiveType,
  updateConnectivityActiveType,
  updateThemeInfoActionType,
  updateDeviceCameraInfoActionType,
  updateDeviceOrientationActionType,
  updateThreadLastNavigatedActionType,
  backgroundActionTypes,
} from './action-types';
import RootNavigator from '../navigation/root-navigator.react';
import { type NavInfo, defaultNavInfo } from '../navigation/default-state';
import { reduceThreadIDsToNotifIDs } from '../push/reducer';
import { persistConfig, setPersistor } from './persist';
import {
  defaultURLPrefix,
  natServer,
  setCustomServer,
} from '../utils/url-utils';
import reactotron from '../reactotron';
import reduceDrafts from '../reducers/draft-reducer';
import { getGlobalNavContext } from '../navigation/icky-global';

export type AppState = {|
  navInfo: NavInfo,
  currentUserInfo: ?CurrentUserInfo,
  entryStore: EntryStore,
  threadStore: ThreadStore,
  userInfos: { [id: string]: UserInfo },
  messageStore: MessageStore,
  drafts: { [key: string]: string },
  updatesCurrentAsOf: number,
  loadingStatuses: { [key: string]: { [idx: number]: LoadingStatus } },
  calendarFilters: $ReadOnlyArray<CalendarFilter>,
  cookie: ?string,
  deviceToken: ?string,
  urlPrefix: string,
  customServer: ?string,
  threadIDsToNotifIDs: { [threadID: string]: string[] },
  notifPermissionAlertInfo: NotifPermissionAlertInfo,
  connection: ConnectionInfo,
  watchedThreadIDs: $ReadOnlyArray<string>,
  foreground: boolean,
  nextLocalID: number,
  queuedReports: $ReadOnlyArray<ClientReportCreationRequest>,
  _persist: ?PersistState,
  sessionID?: void,
  dimensions: Dimensions,
  connectivity: ConnectivityInfo,
  globalThemeInfo: GlobalThemeInfo,
  deviceCameraInfo: DeviceCameraInfo,
  deviceOrientation: Orientations,
  frozen: boolean,
|};

const { height, width } = NativeDimensions.get('window');
const defaultState = ({
  navInfo: defaultNavInfo,
  currentUserInfo: null,
  entryStore: {
    entryInfos: {},
    daysToEntries: {},
    lastUserInteractionCalendar: 0,
    inconsistencyReports: [],
  },
  threadStore: {
    threadInfos: {},
    inconsistencyReports: [],
  },
  userInfos: {},
  messageStore: {
    messages: {},
    threads: {},
    local: {},
    currentAsOf: 0,
  },
  drafts: {},
  updatesCurrentAsOf: 0,
  loadingStatuses: {},
  calendarFilters: defaultCalendarFilters,
  cookie: null,
  deviceToken: null,
  urlPrefix: defaultURLPrefix(),
  customServer: natServer,
  threadIDsToNotifIDs: {},
  notifPermissionAlertInfo: defaultNotifPermissionAlertInfo,
  connection: defaultConnectionInfo(Platform.OS),
  watchedThreadIDs: [],
  foreground: true,
  nextLocalID: 0,
  queuedReports: [],
  _persist: null,
  dimensions: { height, width },
  connectivity: defaultConnectivityInfo,
  globalThemeInfo: defaultGlobalThemeInfo,
  deviceCameraInfo: defaultDeviceCameraInfo,
  deviceOrientation: Orientation.getInitialOrientation(),
  frozen: false,
}: AppState);

function reducer(state: AppState = defaultState, action: *) {
  if (
    (action.type === setNewSessionActionType &&
      invalidSessionDowngrade(
        state,
        action.payload.sessionChange.currentUserInfo,
        action.payload.preRequestUserState,
      )) ||
    (action.type === logOutActionTypes.success &&
      invalidSessionDowngrade(
        state,
        action.payload.currentUserInfo,
        action.payload.preRequestUserState,
      )) ||
    (action.type === deleteAccountActionTypes.success &&
      invalidSessionDowngrade(
        state,
        action.payload.currentUserInfo,
        action.payload.preRequestUserState,
      ))
  ) {
    return state;
  }
  if (
    action.type === recordAndroidNotificationActionType ||
    action.type === clearAndroidNotificationsActionType ||
    action.type === rescindAndroidNotificationActionType
  ) {
    return {
      ...state,
      threadIDsToNotifIDs: reduceThreadIDsToNotifIDs(
        state.threadIDsToNotifIDs,
        action,
      ),
    };
  } else if (action.type === setCustomServer) {
    return {
      ...state,
      customServer: action.payload,
    };
  } else if (action.type === recordNotifPermissionAlertActionType) {
    return {
      ...state,
      notifPermissionAlertInfo: {
        totalAlerts: state.notifPermissionAlertInfo.totalAlerts + 1,
        lastAlertTime: action.payload.time,
      },
    };
  } else if (action.type === resetUserStateActionType) {
    const cookie =
      state.cookie && state.cookie.startsWith('anonymous=')
        ? state.cookie
        : null;
    const currentUserInfo =
      state.currentUserInfo && state.currentUserInfo.anonymous
        ? state.currentUserInfo
        : null;
    return {
      ...state,
      currentUserInfo,
      cookie,
    };
  } else if (action.type === updateDimensionsActiveType) {
    return {
      ...state,
      dimensions: action.payload,
    };
  } else if (action.type === updateConnectivityActiveType) {
    return {
      ...state,
      connectivity: action.payload,
    };
  } else if (action.type === updateThemeInfoActionType) {
    return {
      ...state,
      globalThemeInfo: {
        ...state.globalThemeInfo,
        ...action.payload,
      },
    };
  } else if (action.type === updateDeviceCameraInfoActionType) {
    return {
      ...state,
      deviceCameraInfo: {
        ...state.deviceCameraInfo,
        ...action.payload,
      },
    };
  } else if (action.type === updateDeviceOrientationActionType) {
    return {
      ...state,
      deviceOrientation: action.payload,
    };
  } else if (action.type === setDeviceTokenActionTypes.started) {
    return {
      ...state,
      deviceToken: action.payload,
    };
  } else if (action.type === updateThreadLastNavigatedActionType) {
    const { threadID, time } = action.payload;
    if (state.messageStore.threads[threadID]) {
      state = {
        ...state,
        messageStore: {
          ...state.messageStore,
          threads: {
            ...state.messageStore.threads,
            [threadID]: {
              ...state.messageStore.threads[threadID],
              lastNavigatedTo: time,
            },
          },
        },
      };
    }
  }

  if (action.type === setNewSessionActionType) {
    sessionInvalidationAlert(action.payload);
    state = {
      ...state,
      cookie: action.payload.sessionChange.cookie,
    };
  } else if (action.type === incrementalStateSyncActionType) {
    let wipeDeviceToken = false;
    for (let update of action.payload.updatesResult.newUpdates) {
      if (
        update.type === updateTypes.BAD_DEVICE_TOKEN &&
        update.deviceToken === state.deviceToken
      ) {
        wipeDeviceToken = true;
        break;
      }
    }
    if (wipeDeviceToken) {
      state = {
        ...state,
        deviceToken: null,
      };
    }
  }

  state = {
    ...baseReducer(state, action),
    drafts: reduceDrafts(state.drafts, action),
  };

  const navigationState = RootNavigator.router.getStateForAction(
    action,
    state.navInfo.navigationState,
  );
  if (navigationState && navigationState !== state.navInfo.navigationState) {
    state = {
      ...state,
      navInfo: {
        ...state.navInfo,
        navigationState,
      },
    };
  }

  return fixUnreadActiveThread(state, action);
}

function sessionInvalidationAlert(payload: SetSessionPayload) {
  if (!payload.sessionChange.cookieInvalidated) {
    return;
  }
  if (payload.error === 'client_version_unsupported') {
    const app = Platform.select({
      ios: 'Testflight',
      android: 'Play Store',
    });
    Alert.alert(
      'App out of date',
      "Your app version is pretty old, and the server doesn't know how to " +
        `speak to it anymore. Please use the ${app} app to update!`,
      [{ text: 'OK' }],
    );
  } else {
    Alert.alert(
      'Session invalidated',
      "We're sorry, but your session was invalidated by the server. " +
        'Please log in again.',
      [{ text: 'OK' }],
    );
  }
}

// Makes sure a currently focused thread is never unread. Note that we consider
// a backgrounded NativeAppState to actually be active if it last changed to
// inactive more than 10 seconds ago. This is because there is a delay when
// NativeAppState is updating in response to a foreground, and actions don't get
// processed more than 10 seconds after a backgrounding anyways. However we
// don't consider this for action types that can be expected to happen while the
// app is backgrounded.
function fixUnreadActiveThread(state: AppState, action: *): AppState {
  const navContext = getGlobalNavContext();
  const activeThread = activeMessageListSelector(navContext);
  if (
    activeThread &&
    (NativeAppState.currentState === 'active' ||
      (appLastBecameInactive + 10000 < Date.now() &&
        !backgroundActionTypes.has(action.type))) &&
    state.threadStore.threadInfos[activeThread] &&
    state.threadStore.threadInfos[activeThread].currentUser.unread
  ) {
    state = {
      ...state,
      threadStore: {
        ...state.threadStore,
        threadInfos: {
          ...state.threadStore.threadInfos,
          [activeThread]: {
            ...state.threadStore.threadInfos[activeThread],
            currentUser: {
              ...state.threadStore.threadInfos[activeThread].currentUser,
              unread: false,
            },
          },
        },
      },
    };
  }
  return state;
}

let appLastBecameInactive = 0;
function appBecameInactive() {
  appLastBecameInactive = Date.now();
}

const middleware = applyMiddleware(
  thunk,
  createReactNavigationReduxMiddleware(
    (state: AppState) => state.navInfo.navigationState,
  ),
  reduxLoggerMiddleware,
);
const composeFunc = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  : compose;
let enhancers;
if (reactotron) {
  enhancers = composeFunc(middleware, reactotron.createEnhancer());
} else {
  enhancers = composeFunc(middleware);
}

const store: Store<AppState, *> = createStore(
  persistReducer(persistConfig, reducer),
  defaultState,
  enhancers,
);
const persistor = persistStore(store);
setPersistor(persistor);

const unsafeDispatch: any = store.dispatch;
const dispatch: Dispatch = unsafeDispatch;

export { store, dispatch, appBecameInactive };
<|endoftext|>


<commit_before>// @flow

import type {
  Dimensions,
  MediaType,
  MediaMissionStep,
  MediaMissionFailure,
} from 'lib/types/media-types';

import { Platform, Image } from 'react-native';
import base64 from 'base-64';
import ImageResizer from 'react-native-image-resizer';
import invariant from 'invariant';

import {
  fileInfoFromData,
  mimeTypesToMediaTypes,
} from 'lib/utils/file-utils';

import { transcodeVideo } from './video-utils';

async function getBlobFromURI(
  uri: string,
  type: MediaType,
): Promise<ReactNativeBlob> {
  // React Native always resolves FBMediaKit's ph:// scheme as an image so that
  // the Image component can render thumbnails of videos. In order to force
  // fetch() to return a blob of the video, we need to use the ph-upload://
  // scheme. https://git.io/Jerlh
  const fbMediaKitURL = uri.startsWith('ph://');
  const fixedURI = (fbMediaKitURL && type === "video")
    ? uri.replace(/^ph:/, 'ph-upload:')
    : uri;
  const start = Date.now();
  const response = await fetch(fixedURI);
  return await response.blob();
}

type ClientPhotoInfo = {|
  type: "photo",
  uri: string,
  dimensions: Dimensions,
  filename: string,
|};
type ClientVideoInfo = {|
  type: "video",
  uri: string,
  dimensions: Dimensions,
  filename: string,
|};
type ClientMediaInfo =
  | ClientPhotoInfo
  | ClientVideoInfo;

type ReactNativeBlob =
  & Blob
  & { data: { type: string, name: string, size: number } };
export type MediaValidationResult =
  | {|
      success: true,
      ...ClientPhotoInfo,
      blob: ReactNativeBlob,
    |}
  | {|
      success: true,
      ...ClientVideoInfo,
      blob: ?ReactNativeBlob,
    |};
async function validateMedia(
  mediaInfo: ClientMediaInfo,
): Promise<{|
  steps: $ReadOnlyArray<MediaMissionStep>,
  result: MediaMissionFailure | MediaValidationResult,
|}> {
  const { dimensions, uri, type, filename } = mediaInfo;
  const blobFetchStart = Date.now();

  let blob, reportedMIME, reportedMediaType;
  try {
    blob = await getBlobFromURI(uri, type);
    reportedMIME =
      (uri.startsWith('ph://') && blob.type === "application/octet-stream")
        ? "video/quicktime"
        : blob.type;
    reportedMediaType = mimeTypesToMediaTypes[reportedMIME];
  } catch { }

  const validationStep = {
    step: "validation",
    type,
    success: type === reportedMediaType,
    time: Date.now() - blobFetchStart,
    blobFetched: !!blob,
    blobMIME: blob ? blob.type : null,
    reportedMIME,
    blobName: (blob && blob.data) ? blob.data.name : null,
    size: blob ? blob.size : null,
  };

  let result;
  if (type === "photo") {
    if (blob && reportedMediaType === "photo") {
      result = {
        success: true,
        type: "photo",
        uri,
        dimensions,
        filename,
        blob,
      };
    } else {
      result = {
        success: false,
        reason: "blob_reported_mime_issue",
        mime: reportedMIME,
      };
    }
  } else {
    result = {
      success: true,
      type: "video",
      uri,
      dimensions,
      filename,
      blob,
    };
  }

  return { steps: [ validationStep ], result };
}

function blobToDataURI(blob: Blob): Promise<string> {
  const fileReader = new FileReader();
  return new Promise((resolve, reject) => {
    fileReader.onerror = error => {
      fileReader.abort();
      reject(error);
    };
    fileReader.onload = event => {
      resolve(event.target.result);
    };
    fileReader.readAsDataURL(blob);
  });
}

function stringToIntArray(str: string): Uint8Array {
  const array = new Uint8Array(str.length);
  for (let i = 0; i < str.length; i++) {
    array[i] = str.charCodeAt(i);
  }
  return array;
}

function dataURIToIntArray(dataURI: string): Uint8Array {
  const uri = dataURI.replace(/\r?\n/g, '');

  const firstComma = uri.indexOf(',');
  if (firstComma <= 4) {
    throw new TypeError('malformed data-URI');
  }

  const meta = uri.substring(5, firstComma).split(';');
  const base64Encoded = meta.some(metum => metum === 'base64');

  let data = unescape(uri.substring(firstComma + 1));
  if (base64Encoded) {
    data = base64.decode(data);
  }

  return stringToIntArray(data);
}

function getDimensions(uri: string): Promise<Dimensions> {
  return new Promise((resolve, reject) => {
    Image.getSize(
      uri,
      (width: number, height: number) => resolve({ height, width }),
      reject,
    );
  });
}

export type MediaConversionResult = {|
  success: true,
  uploadURI: string,
  shouldDisposePath: ?string,
  name: string,
  mime: string,
  mediaType: MediaType,
  dimensions: Dimensions,
|};
async function convertMedia(
  validationResult: MediaValidationResult,
): Promise<{|
  steps: $ReadOnlyArray<MediaMissionStep>,
  result: MediaMissionFailure | MediaConversionResult,
|}> {
  let steps = [];
  let uploadURI = validationResult.uri,
    dimensions = validationResult.dimensions,
    shouldDisposePath = null,
    name = validationResult.filename,
    mime = null,
    mediaType = validationResult.type;
  const finish = (failure?: MediaMissionFailure) => {
    if (failure) {
      return { steps, result: failure };
    }
    invariant(
      mime,
      "if we're finishing successfully we should have a MIME type",
    );
    return {
      steps,
      result: {
        success: true,
        uploadURI,
        shouldDisposePath,
        name,
        mime,
        mediaType,
        dimensions,
      },
    };
  };

  if (validationResult.type === "video") {
    const {
      steps: transcodeSteps,
      result: transcodeResult,
    } = await transcodeVideo(validationResult);
    steps = [ ...steps, ...transcodeSteps ];
    if (!transcodeResult.success) {
      return finish(transcodeResult);
    }
    uploadURI = transcodeResult.uri;
    name = transcodeResult.filename;
    shouldDisposePath = validationResult.uri !== uploadURI ? uploadURI : null;
    mime = "video/mp4";
  } else if (validationResult.type === "photo") {
    const { type: reportedMIME, size } = validationResult.blob;
    if (
      reportedMIME === "image/heic" ||
      size > 5e6 ||
      (size > 5e5 && (dimensions.width > 3000 || dimensions.height > 2000))
    ) {
      const photoResizeStart = Date.now();
      try {
        const compressFormat = reportedMIME === "image/png" ? "PNG" : "JPEG";
        const compressQuality = size > 5e6 ? 92 : 100;
        const { uri: resizedURI, path, name: resizedName } =
          await ImageResizer.createResizedImage(
            uploadURI,
            3000,
            2000,
            compressFormat,
            compressQuality,
          );
        uploadURI = resizedURI;
        name = resizedName;
        shouldDisposePath = path;
        mime = reportedMIME === "image/png" ? "image/png" : "image/jpeg";
        dimensions = await getDimensions(resizedURI);
      } catch (e) { }
      const success = uploadURI !== validationResult.uri;
      steps.push({
        step: "photo_resize_transcode",
        success,
        time: Date.now() - photoResizeStart,
        newMIME: success ? mime : null,
        newDimensions: success ? dimensions : null,
        newURI: success ? uploadURI : null,
        newPath: success ? shouldDisposePath : null,
        newName: success ? name : null,
      });
      if (!success) {
        return finish({
          success: false,
          reason: "too_large_cant_downscale",
          size,
        });
      }
    }
  }

  let { blob } = validationResult;
  if (uploadURI !== validationResult.uri) {
    const newMediaInfo = validationResult.type === "video"
      ? {
          type: "video",
          uri: uploadURI,
          dimensions,
          filename: name,
        }
      : {
          type: "photo",
          uri: uploadURI,
          dimensions,
          filename: name,
        };
    const {
      steps: newValidationSteps,
      result: newValidationResult,
    } = await validateMedia(newMediaInfo);
    steps = [ ...steps, ...newValidationSteps ];
    if (!newValidationResult.success) {
      return finish(newValidationResult);
    }
    blob = newValidationResult.blob;
  }

  let fileDataDetectionStep;
  if (blob) {
    const fileDataDetectionStart = Date.now();
    const dataURI = await blobToDataURI(blob);
    const intArray = dataURIToIntArray(dataURI);

    const blobName = blob.data.name;
    const fileDetectionResult = fileInfoFromData(intArray, blobName);
    fileDataDetectionStep = {
      step: "final_file_data_analysis",
      success: !!fileDetectionResult.name &&
        !!fileDetectionResult.mime &&
        fileDetectionResult.mediaType === validationResult.type,
      time: Date.now() - fileDataDetectionStart,
      uri: uploadURI,
      detectedMIME: fileDetectionResult.mime,
      detectedMediaType: fileDetectionResult.mediaType,
      newName: fileDetectionResult.name,
    };
    steps.push(fileDataDetectionStep);
    if (fileDetectionResult.name) {
      name = fileDetectionResult.name;
    }
    if (fileDetectionResult.mime) {
      mime = fileDetectionResult.mime;
    }
  }

  if (
    validationResult.type === "photo" &&
    uploadURI === validationResult.uri &&
    (!fileDataDetectionStep || !fileDataDetectionStep.success)
  ) {
    const reportedMIME = validationResult.blob.type;
    return finish({
      success: false,
      reason: "file_data_detected_mime_issue",
      reportedMIME,
      reportedMediaType: validationResult.type,
      detectedMIME: mime,
      detectedMediaType: mediaType,
    });
  }

  return finish();
}

function getCompatibleMediaURI(uri: string, ext: string): string {
  if (!uri.startsWith('ph://') && !uri.startsWith('ph-upload://')) {
    return uri;
  }
  const photoKitLocalIdentifier = uri.split('/')[2];
  if (!photoKitLocalIdentifier) {
    return uri;
  }
  // While the ph:// scheme is a Facebook hack used by FBMediaKit, the
  // assets-library:// scheme is a legacy Apple identifier. Certain components
  // and libraries (namely react-native-video) don't know how to handle the
  // ph:// scheme yet, so we have to map to the legacy assets-library:// scheme
  return `assets-library://asset/asset.${ext}` +
    `?id=${photoKitLocalIdentifier}&ext=${ext}`;
}

async function processMedia(
  mediaInfo: ClientMediaInfo,
): Promise<{|
  steps: $ReadOnlyArray<MediaMissionStep>,
  result: MediaMissionFailure | MediaConversionResult,
|}> {
  const {
    result: validationResult,
    steps: validationSteps,
  } = await validateMedia(mediaInfo);
  if (!validationResult.success) {
    return { result: validationResult, steps: validationSteps };
  }
  const {
    result: conversionResult,
    steps: conversionSteps,
  } = await convertMedia(validationResult);
  const steps = [ ...validationSteps, ...conversionSteps ];
  return { steps, result: conversionResult };
}

export {
  blobToDataURI,
  dataURIToIntArray,
  processMedia,
  getCompatibleMediaURI,
};
<commit_msg>[native] Try to keep original media filename<commit_after>// @flow

import type {
  Dimensions,
  MediaType,
  MediaMissionStep,
  MediaMissionFailure,
} from 'lib/types/media-types';

import { Platform, Image } from 'react-native';
import base64 from 'base-64';
import ImageResizer from 'react-native-image-resizer';
import invariant from 'invariant';

import {
  fileInfoFromData,
  mimeTypesToMediaTypes,
  stripExtension,
} from 'lib/utils/file-utils';

import { transcodeVideo } from './video-utils';

async function getBlobFromURI(
  uri: string,
  type: MediaType,
): Promise<ReactNativeBlob> {
  // React Native always resolves FBMediaKit's ph:// scheme as an image so that
  // the Image component can render thumbnails of videos. In order to force
  // fetch() to return a blob of the video, we need to use the ph-upload://
  // scheme. https://git.io/Jerlh
  const fbMediaKitURL = uri.startsWith('ph://');
  const fixedURI = (fbMediaKitURL && type === "video")
    ? uri.replace(/^ph:/, 'ph-upload:')
    : uri;
  const start = Date.now();
  const response = await fetch(fixedURI);
  return await response.blob();
}

type ClientPhotoInfo = {|
  type: "photo",
  uri: string,
  dimensions: Dimensions,
  filename: string,
|};
type ClientVideoInfo = {|
  type: "video",
  uri: string,
  dimensions: Dimensions,
  filename: string,
|};
type ClientMediaInfo =
  | ClientPhotoInfo
  | ClientVideoInfo;

type ReactNativeBlob =
  & Blob
  & { data: { type: string, name: string, size: number } };
export type MediaValidationResult =
  | {|
      success: true,
      ...ClientPhotoInfo,
      blob: ReactNativeBlob,
    |}
  | {|
      success: true,
      ...ClientVideoInfo,
      blob: ?ReactNativeBlob,
    |};
async function validateMedia(
  mediaInfo: ClientMediaInfo,
): Promise<{|
  steps: $ReadOnlyArray<MediaMissionStep>,
  result: MediaMissionFailure | MediaValidationResult,
|}> {
  const { dimensions, uri, type, filename } = mediaInfo;
  const blobFetchStart = Date.now();

  let blob, reportedMIME, reportedMediaType;
  try {
    blob = await getBlobFromURI(uri, type);
    reportedMIME =
      (uri.startsWith('ph://') && blob.type === "application/octet-stream")
        ? "video/quicktime"
        : blob.type;
    reportedMediaType = mimeTypesToMediaTypes[reportedMIME];
  } catch { }

  const validationStep = {
    step: "validation",
    type,
    success: type === reportedMediaType,
    time: Date.now() - blobFetchStart,
    blobFetched: !!blob,
    blobMIME: blob ? blob.type : null,
    reportedMIME,
    blobName: (blob && blob.data) ? blob.data.name : null,
    size: blob ? blob.size : null,
  };

  let result;
  if (type === "photo") {
    if (blob && reportedMediaType === "photo") {
      result = {
        success: true,
        type: "photo",
        uri,
        dimensions,
        filename,
        blob,
      };
    } else {
      result = {
        success: false,
        reason: "blob_reported_mime_issue",
        mime: reportedMIME,
      };
    }
  } else {
    result = {
      success: true,
      type: "video",
      uri,
      dimensions,
      filename,
      blob,
    };
  }

  return { steps: [ validationStep ], result };
}

function blobToDataURI(blob: Blob): Promise<string> {
  const fileReader = new FileReader();
  return new Promise((resolve, reject) => {
    fileReader.onerror = error => {
      fileReader.abort();
      reject(error);
    };
    fileReader.onload = event => {
      resolve(event.target.result);
    };
    fileReader.readAsDataURL(blob);
  });
}

function stringToIntArray(str: string): Uint8Array {
  const array = new Uint8Array(str.length);
  for (let i = 0; i < str.length; i++) {
    array[i] = str.charCodeAt(i);
  }
  return array;
}

function dataURIToIntArray(dataURI: string): Uint8Array {
  const uri = dataURI.replace(/\r?\n/g, '');

  const firstComma = uri.indexOf(',');
  if (firstComma <= 4) {
    throw new TypeError('malformed data-URI');
  }

  const meta = uri.substring(5, firstComma).split(';');
  const base64Encoded = meta.some(metum => metum === 'base64');

  let data = unescape(uri.substring(firstComma + 1));
  if (base64Encoded) {
    data = base64.decode(data);
  }

  return stringToIntArray(data);
}

function getDimensions(uri: string): Promise<Dimensions> {
  return new Promise((resolve, reject) => {
    Image.getSize(
      uri,
      (width: number, height: number) => resolve({ height, width }),
      reject,
    );
  });
}

export type MediaConversionResult = {|
  success: true,
  uploadURI: string,
  shouldDisposePath: ?string,
  name: string,
  mime: string,
  mediaType: MediaType,
  dimensions: Dimensions,
|};
async function convertMedia(
  validationResult: MediaValidationResult,
): Promise<{|
  steps: $ReadOnlyArray<MediaMissionStep>,
  result: MediaMissionFailure | MediaConversionResult,
|}> {
  let steps = [];
  let uploadURI = validationResult.uri,
    dimensions = validationResult.dimensions,
    shouldDisposePath = null,
    name = validationResult.filename,
    mime = null,
    mediaType = validationResult.type;
  const finish = (failure?: MediaMissionFailure) => {
    if (failure) {
      return { steps, result: failure };
    }
    invariant(
      mime,
      "if we're finishing successfully we should have a MIME type",
    );
    return {
      steps,
      result: {
        success: true,
        uploadURI,
        shouldDisposePath,
        name,
        mime,
        mediaType,
        dimensions,
      },
    };
  };

  if (validationResult.type === "video") {
    const {
      steps: transcodeSteps,
      result: transcodeResult,
    } = await transcodeVideo(validationResult);
    steps = [ ...steps, ...transcodeSteps ];
    if (!transcodeResult.success) {
      return finish(transcodeResult);
    }
    uploadURI = transcodeResult.uri;
    name = transcodeResult.filename;
    shouldDisposePath = validationResult.uri !== uploadURI ? uploadURI : null;
    mime = "video/mp4";
  } else if (validationResult.type === "photo") {
    const { type: reportedMIME, size } = validationResult.blob;
    if (
      reportedMIME === "image/heic" ||
      size > 5e6 ||
      (size > 5e5 && (dimensions.width > 3000 || dimensions.height > 2000))
    ) {
      const photoResizeStart = Date.now();
      try {
        const compressFormat = reportedMIME === "image/png" ? "PNG" : "JPEG";
        const compressQuality = size > 5e6 ? 92 : 100;
        const { uri: resizedURI, path, name: resizedName } =
          await ImageResizer.createResizedImage(
            uploadURI,
            3000,
            2000,
            compressFormat,
            compressQuality,
          );
        uploadURI = resizedURI;
        if (reportedMIME === "image/png" && !name.endsWith('.png')) {
          name = `${stripExtension(name)}.png`;
        } else if (reportedMIME !== "image/png" && !name.endsWith('.jpg')) {
          name = `${stripExtension(name)}.jpg`;
        }
        shouldDisposePath = path;
        mime = reportedMIME === "image/png" ? "image/png" : "image/jpeg";
        dimensions = await getDimensions(resizedURI);
      } catch (e) { }
      const success = uploadURI !== validationResult.uri;
      steps.push({
        step: "photo_resize_transcode",
        success,
        time: Date.now() - photoResizeStart,
        newMIME: success ? mime : null,
        newDimensions: success ? dimensions : null,
        newURI: success ? uploadURI : null,
        newPath: success ? shouldDisposePath : null,
        newName: success ? name : null,
      });
      if (!success) {
        return finish({
          success: false,
          reason: "too_large_cant_downscale",
          size,
        });
      }
    }
  }

  let { blob } = validationResult;
  if (uploadURI !== validationResult.uri) {
    const newMediaInfo = validationResult.type === "video"
      ? {
          type: "video",
          uri: uploadURI,
          dimensions,
          filename: name,
        }
      : {
          type: "photo",
          uri: uploadURI,
          dimensions,
          filename: name,
        };
    const {
      steps: newValidationSteps,
      result: newValidationResult,
    } = await validateMedia(newMediaInfo);
    steps = [ ...steps, ...newValidationSteps ];
    if (!newValidationResult.success) {
      return finish(newValidationResult);
    }
    blob = newValidationResult.blob;
  }

  let fileDataDetectionStep;
  if (blob) {
    const fileDataDetectionStart = Date.now();
    const dataURI = await blobToDataURI(blob);
    const intArray = dataURIToIntArray(dataURI);

    const fileDetectionResult = fileInfoFromData(intArray, name);
    fileDataDetectionStep = {
      step: "final_file_data_analysis",
      success: !!fileDetectionResult.name &&
        !!fileDetectionResult.mime &&
        fileDetectionResult.mediaType === validationResult.type,
      time: Date.now() - fileDataDetectionStart,
      uri: uploadURI,
      detectedMIME: fileDetectionResult.mime,
      detectedMediaType: fileDetectionResult.mediaType,
      newName: fileDetectionResult.name,
    };
    steps.push(fileDataDetectionStep);
    if (fileDetectionResult.name) {
      name = fileDetectionResult.name;
    }
    if (fileDetectionResult.mime) {
      mime = fileDetectionResult.mime;
    }
  }

  if (
    validationResult.type === "photo" &&
    uploadURI === validationResult.uri &&
    (!fileDataDetectionStep || !fileDataDetectionStep.success)
  ) {
    const reportedMIME = validationResult.blob.type;
    return finish({
      success: false,
      reason: "file_data_detected_mime_issue",
      reportedMIME,
      reportedMediaType: validationResult.type,
      detectedMIME: mime,
      detectedMediaType: mediaType,
    });
  }

  return finish();
}

function getCompatibleMediaURI(uri: string, ext: string): string {
  if (!uri.startsWith('ph://') && !uri.startsWith('ph-upload://')) {
    return uri;
  }
  const photoKitLocalIdentifier = uri.split('/')[2];
  if (!photoKitLocalIdentifier) {
    return uri;
  }
  // While the ph:// scheme is a Facebook hack used by FBMediaKit, the
  // assets-library:// scheme is a legacy Apple identifier. Certain components
  // and libraries (namely react-native-video) don't know how to handle the
  // ph:// scheme yet, so we have to map to the legacy assets-library:// scheme
  return `assets-library://asset/asset.${ext}` +
    `?id=${photoKitLocalIdentifier}&ext=${ext}`;
}

async function processMedia(
  mediaInfo: ClientMediaInfo,
): Promise<{|
  steps: $ReadOnlyArray<MediaMissionStep>,
  result: MediaMissionFailure | MediaConversionResult,
|}> {
  const {
    result: validationResult,
    steps: validationSteps,
  } = await validateMedia(mediaInfo);
  if (!validationResult.success) {
    return { result: validationResult, steps: validationSteps };
  }
  const {
    result: conversionResult,
    steps: conversionSteps,
  } = await convertMedia(validationResult);
  const steps = [ ...validationSteps, ...conversionSteps ];
  return { steps, result: conversionResult };
}

export {
  blobToDataURI,
  dataURIToIntArray,
  processMedia,
  getCompatibleMediaURI,
};
<|endoftext|>


<commit_before>var tpl_timeline = require('./templates/timeline.tpl.html');
import Timeline from './graph/Timeline';
import PropertiesEditor from './editor/PropertiesEditor';
import EditorMenu from './editor/EditorMenu';
import EditorControls from './editor/EditorControls';
import SelectionManager from './editor/SelectionManager';
import Exporter from './editor/Exporter';
import UndoManager from './editor/UndoManager';
let Signals = require('js-signals');

class Editor {
  constructor(tweenTime, options = {}) {
    this.tweenTime = tweenTime;
    this.options = options;
    this.timer = this.tweenTime.timer;
    this.lastTime = -1;

    this.onKeyAdded = this.onKeyAdded.bind(this);
    this.onKeyRemoved = this.onKeyRemoved.bind(this);

    this.$timeline = $(tpl_timeline());
    $('body').append(this.$timeline);
    $('body').addClass('has-editor');

    this.selectionManager = new SelectionManager(this.tweenTime);
    this.exporter = new Exporter(this);
    this.timeline = new Timeline(this, options);

    this.propertiesEditor = new PropertiesEditor(this, this.selectionManager);
    this.propertiesEditor.keyAdded.add(this.onKeyAdded);
    this.propertiesEditor.keyRemoved.add(this.onKeyRemoved);

    this.menu = new EditorMenu(this.tweenTime, this.$timeline, this);
    if (this.options.onMenuCreated !== undefined) {
      this.options.onMenuCreated(this.$timeline.find('.timeline__menu'), this);
    }

    this.controls = new EditorControls(this.tweenTime, this.$timeline);
    this.undoManager = new UndoManager(this);

    // Public events.
    this.onSelect = new Signals.Signal();
    var self = this;
    this.selectionManager.onSelect.add(function(selection, addToSelection) {
      // Propagate the event.
      self.onSelect.dispatch(selection, addToSelection);
    });

    // Will help resize the canvas to correct size (minus sidebar and timeline)
    window.editorEnabled = true;
    window.dispatchEvent(new Event('resize'));
    window.requestAnimationFrame(() => this.update());
  }

  select(item, addToSelection = false) {
    this.selectionManager.select(item, addToSelection);
  }

  getSelection() {
    return this.selectionManager.getSelection();
  }

  onKeyAdded() {
    this.undoManager.addState();
    this.render(false, false, true);
  }

  onKeyRemoved(item) {
    this.selectionManager.removeItem(item._id);
    this.undoManager.addState();
    if (this.selectionManager.selection.length) {
      this.selectionManager.triggerSelect();
    }
    this.render(false, false, true);
  }

  render(time = false, time_changed = false, force = false) {
    let time2 = time;
    if (time2 === false) {
      time2 = this.timer.time[0];
    }
    if (force) {
      this.timeline._isDirty = true;
    }
    this.timeline.render(time2, time_changed);
    this.controls.render(time2, time_changed);
    this.propertiesEditor.render(time2, time_changed);
  }

  update() {
    var time = this.timer.time[0];
    var time_changed = this.lastTime === time ? false : true;

    this.render(time, time_changed);
    this.lastTime = this.timer.time[0];
    window.requestAnimationFrame(() => this.update());
  }
}

module.exports = Editor;
<commit_msg>Add el option in Editor, by @ thorikawa<commit_after>var tpl_timeline = require('./templates/timeline.tpl.html');
import Timeline from './graph/Timeline';
import PropertiesEditor from './editor/PropertiesEditor';
import EditorMenu from './editor/EditorMenu';
import EditorControls from './editor/EditorControls';
import SelectionManager from './editor/SelectionManager';
import Exporter from './editor/Exporter';
import UndoManager from './editor/UndoManager';
let Signals = require('js-signals');

class Editor {
  constructor(tweenTime, options = {}) {
    this.tweenTime = tweenTime;
    this.options = options;
    this.timer = this.tweenTime.timer;
    this.lastTime = -1;

    this.onKeyAdded = this.onKeyAdded.bind(this);
    this.onKeyRemoved = this.onKeyRemoved.bind(this);

    var el = options.el || $('body');
    this.$timeline = $(tpl_timeline());
    el.append(this.$timeline);
    el.addClass('has-editor');

    this.selectionManager = new SelectionManager(this.tweenTime);
    this.exporter = new Exporter(this);
    this.timeline = new Timeline(this, options);

    this.propertiesEditor = new PropertiesEditor(this, this.selectionManager);
    this.propertiesEditor.keyAdded.add(this.onKeyAdded);
    this.propertiesEditor.keyRemoved.add(this.onKeyRemoved);

    this.menu = new EditorMenu(this.tweenTime, this.$timeline, this);
    if (this.options.onMenuCreated !== undefined) {
      this.options.onMenuCreated(this.$timeline.find('.timeline__menu'), this);
    }

    this.controls = new EditorControls(this.tweenTime, this.$timeline);
    this.undoManager = new UndoManager(this);

    // Public events.
    this.onSelect = new Signals.Signal();
    var self = this;
    this.selectionManager.onSelect.add(function(selection, addToSelection) {
      // Propagate the event.
      self.onSelect.dispatch(selection, addToSelection);
    });

    // Will help resize the canvas to correct size (minus sidebar and timeline)
    window.editorEnabled = true;
    window.dispatchEvent(new Event('resize'));
    window.requestAnimationFrame(() => this.update());
  }

  select(item, addToSelection = false) {
    this.selectionManager.select(item, addToSelection);
  }

  getSelection() {
    return this.selectionManager.getSelection();
  }

  onKeyAdded() {
    this.undoManager.addState();
    this.render(false, false, true);
  }

  onKeyRemoved(item) {
    this.selectionManager.removeItem(item._id);
    this.undoManager.addState();
    if (this.selectionManager.selection.length) {
      this.selectionManager.triggerSelect();
    }
    this.render(false, false, true);
  }

  render(time = false, time_changed = false, force = false) {
    let time2 = time;
    if (time2 === false) {
      time2 = this.timer.time[0];
    }
    if (force) {
      this.timeline._isDirty = true;
    }
    this.timeline.render(time2, time_changed);
    this.controls.render(time2, time_changed);
    this.propertiesEditor.render(time2, time_changed);
  }

  update() {
    var time = this.timer.time[0];
    var time_changed = this.lastTime === time ? false : true;

    this.render(time, time_changed);
    this.lastTime = this.timer.time[0];
    window.requestAnimationFrame(() => this.update());
  }
}

module.exports = Editor;
<|endoftext|>


<commit_before>import * as generators from '../common/generators';

import * as covers from '../dsymbols/covers';
import * as DS from '../dsymbols/delaney';
import * as DS2D from '../dsymbols/delaney2d';
import * as props from '../dsymbols/properties';

import { rationals } from '../arithmetic/types';
const Q = rationals;

const _loopless = (ds, i, j, D) => DS.orbit2(ds, i, j, D)
  .every(E => ds.s(i, E) != E && ds.s(j, E) != E);

const _openOrbits = ds => {
  const result = [];

  for (const [i, j] of [[0, 1], [1, 2]]) {
    for (const D of DS.orbitReps2(ds, i, j)) {
      if (!ds.v(i, j, D))
        result.push([i, D, DS.r(ds, i, j, D), _loopless(ds, i, j, D)]);
    }
  }

  return result;
};

const _withMinimalBranchings = ds => {
  const branchings = (i, j) =>
    DS.orbitReps2(ds, i, j)
    .filter(D => !ds.v(i, j, D))
    .map(D => [D, Math.ceil(3 / DS.r(ds, i, j, D))]);

  return DS.withBranchings(
    DS.withBranchings(ds, 0, branchings(0, 1)),
    1,
    branchings(1, 2)
  );
};

const _compareMapped = (ds, m) => {
  for (const D of ds.elements()) {
    for (const [i, j] of [[0, 1], [1, 2]]) {
      const d = ds.v(i, j, D) - ds.v(i, j, m[D]);
      if (d != 0) return d;
    }
  }
  return 0;
};

const _isCanonical = (ds, maps) => maps.every(m => _compareMapped(ds, m) >= 0);

const _newCurvature = (curv, loopless, v, vOld) =>
  Q.plus(
    curv,
    Q.times(loopless ? 2 : 1, Q.minus(Q.div(1, v), Q.div(1, vOld)))
  );

const branchings = ds => {
  const unused = _openOrbits(ds);
  const maps = props.automorphisms(ds);
  const ds0 = _withMinimalBranchings(ds);

  return generators.backtracker({
    root: [ds0, DS2D.curvature(ds0), unused],

    extract([ds, curv, unused]) {
      if (unused.length == 0 && _isCanonical(ds, maps))
        return ds;
    },

    children([ds, curv, unused]) {
      if (unused.length) {
        if (Q.lt(curv, 0)) {
          return [[ds, curv, []]];
        }
        else {
          const [i, D, r, loopless] = unused[0];
          const v0 = ds.v(i, i + 1, D);
          const out = [];
          for (let v = v0; v <= 7; ++v) {
            const newCurv = _newCurvature(curv, loopless, v, v0);
            out.push([
              DS.withBranchings(ds, i, [[D, v]]),
              newCurv,
              unused.slice(1)
            ]);
            if (Q.lt(newCurv, 0))
              break;
          }
          return out;
        }
      }
    }
  });
}

if (require.main == module) {
  const maxDsSize = parseInt(process.argv[2]);

  covers.covers(DS.parse('<1.1:1:1,1,1:0,0>'), maxDsSize)
    .flatMap(ds => generators.results(branchings(ds)))
    .forEach(ds => console.log(`${ds}`));
}
<commit_msg>scripts/daniel: ensure hyperbolic results are minimally hyperbolic<commit_after>import * as generators from '../common/generators';

import * as covers from '../dsymbols/covers';
import * as DS from '../dsymbols/delaney';
import * as DS2D from '../dsymbols/delaney2d';
import * as props from '../dsymbols/properties';

import { rationals } from '../arithmetic/types';
const Q = rationals;

const _loopless = (ds, i, j, D) => DS.orbit2(ds, i, j, D)
  .every(E => ds.s(i, E) != E && ds.s(j, E) != E);

const _orbits = ds => {
  const result = [];

  for (const [i, j] of [[0, 1], [1, 2]]) {
    for (const D of DS.orbitReps2(ds, i, j)) {
      result.push([i, D, DS.r(ds, i, j, D), _loopless(ds, i, j, D)]);
    }
  }

  return result;
};

const _openOrbits = ds =>
  _orbits(ds).filter(([i, D, r, loopless]) => !ds.v(i, i+1, D));

const _withMinimalBranchings = ds => {
  const branchings = (i, j) =>
    DS.orbitReps2(ds, i, j)
    .filter(D => !ds.v(i, j, D))
    .map(D => [D, Math.ceil(3 / DS.r(ds, i, j, D))]);

  return DS.withBranchings(
    DS.withBranchings(ds, 0, branchings(0, 1)),
    1,
    branchings(1, 2)
  );
};

const _compareMapped = (ds, m) => {
  for (const D of ds.elements()) {
    for (const [i, j] of [[0, 1], [1, 2]]) {
      const d = ds.v(i, j, D) - ds.v(i, j, m[D]);
      if (d != 0) return d;
    }
  }
  return 0;
};

const _isCanonical = (ds, maps) => maps.every(m => _compareMapped(ds, m) >= 0);

const _newCurvature = (curv, loopless, v, vOld) =>
  Q.plus(
    curv,
    Q.times(loopless ? 2 : 1, Q.minus(Q.div(1, v), Q.div(1, vOld)))
  );

const isMinimallyHyperbolic = ds => {
  const curv = DS2D.curvature(ds);
  if (Q.ge(curv, 0))
    return false;

  for (const [i, D, r, loopless] of _orbits(ds)) {
    const v = ds.v(i, i+1, D);

    if (v && v > Math.ceil(3 / r)) {
      const newCurv = _newCurvature(curv, loopless, v-1, v);
      if (Q.lt(newCurv, 0))
        return false;
    }
  }

  return true;
};

const branchings = ds => {
  const unused = _openOrbits(ds);
  const maps = props.automorphisms(ds);
  const ds0 = _withMinimalBranchings(ds);

  return generators.backtracker({
    root: [ds0, DS2D.curvature(ds0), unused],

    extract([ds, curv, unused]) {
      if (unused.length == 0 && _isCanonical(ds, maps))
        return ds;
    },

    children([ds, curv, unused]) {
      if (unused.length) {
        if (Q.lt(curv, 0)) {
          return [[ds, curv, []]];
        }
        else {
          const [i, D, r, loopless] = unused[0];
          const v0 = ds.v(i, i+1, D);
          const out = [];

          for (let v = v0; v <= 7; ++v) {
            const newCurv = _newCurvature(curv, loopless, v, v0);
            const newDs = DS.withBranchings(ds, i, [[D, v]]);

            if (Q.ge(newCurv, 0) || isMinimallyHyperbolic(newDs))
              out.push([ newDs, newCurv, unused.slice(1) ]);

            if (Q.lt(newCurv, 0))
              break;
          }

          return out;
        }
      }
    }
  });
}

if (require.main == module) {
  const maxDsSize = parseInt(process.argv[2]);

  covers.covers(DS.parse('<1.1:1:1,1,1:0,0>'), maxDsSize)
    .flatMap(ds => generators.results(branchings(ds)))
    .forEach(ds => console.log(`${ds}`));
}
<|endoftext|>