<commit_before>// ng-annotate-main.js
// MIT licensed, see LICENSE file
// Copyright (c) 2013-2014 Olov Lassus <olov.lassus@gmail.com>

"use strict";
const esprima = require("esprima").parse;
const is = require("simple-is");
const alter = require("alter");
const traverse = require("ast-traverse");

const chainedRouteProvider = 1;
const chainedUrlRouterProvider = 2;
const chainedStateProvider = 3;
const chainedRegular = 4;

function match(node, re) {
    return matchRegular(node, re) ||
        matchNgRoute(node) ||
        matchUiRouter(node) ||
        matchDirectiveReturnObject(node) ||
        matchProviderGet(node);
}

function matchDirectiveReturnObject(node) {
    // return { .. controller: function($scope, $timeout), ...}

    return node.type === "ReturnStatement" &&
        node.argument && node.argument.type === "ObjectExpression" &&
        matchProp("controller", node.argument.properties);
}

function matchProviderGet(node) {
    // this.$get = function($scope, $timeout)
    // { ... $get: function($scope, $timeout), ...}

    return (node.type === "AssignmentExpression" && node.left.type === "MemberExpression" &&
        node.left.object.type === "ThisExpression" && node.left.property.name === "$get" && node.right) ||
        (node.type === "ObjectExpression" && matchProp("$get", node.properties));
}

function matchNgRoute(node) {
    // $routeProvider.when("path", {
    //   ...
    //   controller: function($scope) {},
    //   resolve: {f: function($scope) {}, ..}
    // })
    if (node.type !== "CallExpression") {
        return;
    }

    const callee = node.callee;
    if (callee.type !== "MemberExpression") {
        return false;
    }

    const obj = callee.object;
    const prop = callee.property;
    const args = node.arguments;

    if (!(obj.$chained === chainedRouteProvider || (obj.type === "Identifier" && obj.name === "$routeProvider"))) {
        return false;
    }
    node.$chained = chainedRouteProvider;

    if (prop.name !== "when") {
        return false;
    }

    if (args.length !== 2) {
        return false;
    }
    const configArg = args[args.length - 1];
    if (configArg.type !== "ObjectExpression") {
        return false;
    }

    const props = configArg.properties;
    const res = [
        matchProp("controller", props)
    ];
    // {resolve: ..}
    res.push.apply(res, matchResolve(props));

    const filteredRes = res.filter(Boolean);
    return (filteredRes.length === 0 ? false : filteredRes);
}

function matchUiRouter(node) {
    // $stateProvider.state("myState", {
    //     ...
    //     controller: function($scope)
    //     controllerProvider: function($scope)
    //     templateProvider: function($scope)
    //     onEnter: function($scope)
    //     onExit: function($scope)
    // });
    // $stateProvider.state("myState", {... resolve: {f: function($scope) {}, ..} ..})
    // $stateProvider.state("myState", {... views: {... somename: {... controller: fn, templateProvider: fn, resolve: {f: fn}}}})
    //
    // $urlRouterProvider.when_otherwise_rule(.., function($scope) {})
    if (node.type !== "CallExpression") {
        return;
    }

    const callee = node.callee;
    if (callee.type !== "MemberExpression") {
        return false;
    }

    const obj = callee.object;
    const prop = callee.property;
    const args = node.arguments;

    // special shortcut for $urlRouterProvider.*(.., function($scope) {})
    if ((obj.$chained === chainedUrlRouterProvider || (obj.type === "Identifier" && obj.name === "$urlRouterProvider")) && args.length >= 1) {
        node.$chained = chainedUrlRouterProvider;
        return args[args.length - 1];
    }

    // everything below is for $stateProvider alone
    if (!(obj.$chained === chainedStateProvider || (obj.type === "Identifier" && obj.name === "$stateProvider"))) {
        return false;
    }
    node.$chained = chainedStateProvider;

    if (!prop || prop.name !== "state") {
        return false;
    }

    // $stateProvider.state({ ... }) and $stateProvider.state("name", { ... })
    if (is.noneof(args.length, [1, 2])) {
        return false;
    }

    const configArg = args[args.length - 1];
    if (configArg.type !== "ObjectExpression") {
        return false;
    }

    const props = configArg.properties;
    const res = [
        matchProp("controller", props),
        matchProp("controllerProvider", props),
        matchProp("templateProvider", props),
        matchProp("onEnter", props),
        matchProp("onExit", props),
    ];

    // {resolve: ..}
    res.push.apply(res, matchResolve(props));

    // {view: ...}
    const viewObject = matchProp("views", props);
    if (viewObject && viewObject.type === "ObjectExpression") {
        viewObject.properties.forEach(function(prop) {
            if (prop.value.type === "ObjectExpression") {
                res.push(matchProp("controller", prop.value.properties));
                res.push(matchProp("templateProvider", prop.value.properties));
                res.push.apply(res, matchResolve(prop.value.properties));
            }
        });
    }

    const filteredRes = res.filter(Boolean);
    return (filteredRes.length === 0 ? false : filteredRes);
}

function matchRegular(node, re) {
    // Short form: *.controller("MyCtrl", function($scope, $timeout) {});
    function isShortDef(node, re) {
        return node.type === "Identifier" && is.string(node.name) && (!re || re.test(node.name));
    }

    // Medium form: *.*.controller("MyCtrl", function($scope, $timeout) {});
    function isMediumDef(node, re) {
        if (node.type === "MemberExpression" && is.object(node.object) && is.object(node.property) && is.string(node.object.name) && is.string(node.property.name)) {
            return (!re || re.test(node.object.name + "." + node.property.name));
        }
        return false;
    }

    // Long form: angular.module(*).controller("MyCtrl", function($scope, $timeout) {});
    function isLongDef(node) {
        return node.callee &&
            node.callee.object && node.callee.object.name === "angular" &&
            node.callee.property && node.callee.property.name === "module";
    }

    if (node.type !== "CallExpression") {
        return;
    }

    const callee = node.callee;
    if (callee.type !== "MemberExpression") {
        return false;
    }
    const obj = callee.object;
    const prop = callee.property;
    if (!obj || !prop) {
        return false;
    }
    const matchAngularModule = (obj.$chained === chainedRegular || isShortDef(obj, re) || isMediumDef(obj, re) || isLongDef(obj)) &&
        is.someof(prop.name, ["provider", "value", "constant", "config", "factory", "directive", "filter", "run", "controller", "service", "decorator", "animation"]);
    if (!matchAngularModule) {
        return false;
    }
    node.$chained = chainedRegular;

    if (is.someof(prop.name, ["value", "constant"])) {
        return false; // affects matchAngularModule because of chaining
    }

    const args = node.arguments;
    return (is.someof(prop.name, ["config", "run"]) ?
        args.length === 1 && args[0] :
        args.length === 2 && args[0].type === "Literal" && is.string(args[0].value) && args[1]);
}

function last(arr) {
    return arr[arr.length - 1];
}

function matchProp(name, props) {
    for (let i = 0; i < props.length; i++) {
        const prop = props[i];
        if (prop.key.type === "Identifier" && prop.key.name === name) {
            return prop.value; // FunctionExpression or ArrayExpression
        }
    }
    return null;
}

function matchResolve(props) {
    const resolveObject = matchProp("resolve", props);
    if (resolveObject && resolveObject.type === "ObjectExpression") {
        return resolveObject.properties.map(function(prop) {
            return prop.value;
        });
    }
    return [];
};

function stringify(arr, quot) {
    return "[" + arr.map(function(arg) {
        return quot + arg.name + quot;
    }).join(", ") + "]";
}

function insertArray(functionExpression, fragments, quot) {
    const range = functionExpression.range;

    const args = stringify(functionExpression.params, quot);
    fragments.push({
        start: range[0],
        end: range[0],
        str: args.slice(0, -1) + ", ",
    });
    fragments.push({
        start: range[1],
        end: range[1],
        str: "]",
    });
}

function replaceArray(array, fragments, quot) {
    const functionExpression = last(array.elements);

    if (functionExpression.params.length === 0) {
        return removeArray(array, fragments);
    }
    const args = stringify(functionExpression.params, quot);
    fragments.push({
        start: array.range[0],
        end: functionExpression.range[0],
        str: args.slice(0, -1) + ", ",
    });
}

function removeArray(array, fragments) {
    const functionExpression = last(array.elements);

    fragments.push({
        start: array.range[0],
        end: functionExpression.range[0],
        str: "",
    });
    fragments.push({
        start: functionExpression.range[1],
        end: array.range[1],
        str: "",
    });
}

function isAnnotatedArray(node) {
    return node.type === "ArrayExpression" && node.elements.length >= 1 && last(node.elements).type === "FunctionExpression";
}
function isFunctionWithArgs(node) {
    return node.type === "FunctionExpression" && node.params.length >= 1;
}

module.exports = function ngAnnotate(src, options) {
    const mode = (options.add && options.remove ? "rebuild" :
        options.remove ? "remove" :
            options.add ? "add" : null);

    if (!mode) {
        return {src: src};
    }

    const quot = options.single_quotes ? "'" : '"';
    const re = (options.regexp && new RegExp(options.regexp));
    const ast = esprima(src, {
        range: true,
    });

    const fragments = [];

    traverse(ast, {post: function(node) {
        let targets = match(node, re);
        if (!targets) {
            return;
        }
        if (!is.array(targets)) {
            targets = [targets];
        }

        for (let i = 0; i < targets.length; i++) {
            const target = targets[i];
            if (mode === "rebuild" && isAnnotatedArray(target)) {
                replaceArray(target, fragments, quot);
            } else if (mode === "remove" && isAnnotatedArray(target)) {
                removeArray(target, fragments);
            } else if (is.someof(mode, ["add", "rebuild"]) && isFunctionWithArgs(target)) {
                insertArray(target, fragments, quot);
            }
        }
    }});

    const out = alter(src, fragments);

    return {
        src: out,
    };
}
<commit_msg>micro-optimizations<commit_after>// ng-annotate-main.js
// MIT licensed, see LICENSE file
// Copyright (c) 2013-2014 Olov Lassus <olov.lassus@gmail.com>

"use strict";
const esprima = require("esprima").parse;
const is = require("simple-is");
const alter = require("alter");
const traverse = require("ast-traverse");

const chainedRouteProvider = 1;
const chainedUrlRouterProvider = 2;
const chainedStateProvider = 3;
const chainedRegular = 4;

function match(node, re) {
    const isMethodCall = (
        node.type === "CallExpression" &&
            node.callee.type === "MemberExpression" &&
            node.callee.computed === false
        );

    const matchMethodCalls = (isMethodCall &&
        (matchRegular(node, re) || matchNgRoute(node) || matchUiRouter(node)));

    return matchMethodCalls ||
        matchDirectiveReturnObject(node) ||
        matchProviderGet(node);
}

function matchDirectiveReturnObject(node) {
    // return { .. controller: function($scope, $timeout), ...}

    return node.type === "ReturnStatement" &&
        node.argument && node.argument.type === "ObjectExpression" &&
        matchProp("controller", node.argument.properties);
}

function matchProviderGet(node) {
    // this.$get = function($scope, $timeout)
    // { ... $get: function($scope, $timeout), ...}

    return (node.type === "AssignmentExpression" && node.left.type === "MemberExpression" &&
        node.left.object.type === "ThisExpression" && node.left.property.name === "$get" && node.right) ||
        (node.type === "ObjectExpression" && matchProp("$get", node.properties));
}

function matchNgRoute(node) {
    // $routeProvider.when("path", {
    //   ...
    //   controller: function($scope) {},
    //   resolve: {f: function($scope) {}, ..}
    // })

    // we already know that node is a (non-computed) method call
    const callee = node.callee;
    const obj = callee.object; // identifier or expression
    if (!(obj.$chained === chainedRouteProvider || (obj.type === "Identifier" && obj.name === "$routeProvider"))) {
        return false;
    }
    node.$chained = chainedRouteProvider;

    const method = callee.property; // identifier
    if (method.name !== "when") {
        return false;
    }

    const args = node.arguments;
    if (args.length !== 2) {
        return false;
    }
    const configArg = args[args.length - 1];
    if (configArg.type !== "ObjectExpression") {
        return false;
    }

    const props = configArg.properties;
    const res = [
        matchProp("controller", props)
    ];
    // {resolve: ..}
    res.push.apply(res, matchResolve(props));

    const filteredRes = res.filter(Boolean);
    return (filteredRes.length === 0 ? false : filteredRes);
}

function matchUiRouter(node) {
    // $stateProvider.state("myState", {
    //     ...
    //     controller: function($scope)
    //     controllerProvider: function($scope)
    //     templateProvider: function($scope)
    //     onEnter: function($scope)
    //     onExit: function($scope)
    // });
    // $stateProvider.state("myState", {... resolve: {f: function($scope) {}, ..} ..})
    // $stateProvider.state("myState", {... views: {... somename: {... controller: fn, templateProvider: fn, resolve: {f: fn}}}})
    //
    // $urlRouterProvider.when_otherwise_rule(.., function($scope) {})

    // we already know that node is a (non-computed) method call
    const callee = node.callee;
    const obj = callee.object; // identifier or expression
    const args = node.arguments;

    // special shortcut for $urlRouterProvider.*(.., function($scope) {})
    if ((obj.$chained === chainedUrlRouterProvider || (obj.type === "Identifier" && obj.name === "$urlRouterProvider")) && args.length >= 1) {
        node.$chained = chainedUrlRouterProvider;
        return args[args.length - 1];
    }

    // everything below is for $stateProvider alone
    if (!(obj.$chained === chainedStateProvider || (obj.type === "Identifier" && obj.name === "$stateProvider"))) {
        return false;
    }
    node.$chained = chainedStateProvider;

    const method = callee.property; // identifier
    if (method.name !== "state") {
        return false;
    }

    // $stateProvider.state({ ... }) and $stateProvider.state("name", { ... })
    if (!(args.length >= 1 && args.length <= 2)) {
        return false;
    }

    const configArg = args[args.length - 1];
    if (configArg.type !== "ObjectExpression") {
        return false;
    }

    const props = configArg.properties;
    const res = [
        matchProp("controller", props),
        matchProp("controllerProvider", props),
        matchProp("templateProvider", props),
        matchProp("onEnter", props),
        matchProp("onExit", props),
    ];

    // {resolve: ..}
    res.push.apply(res, matchResolve(props));

    // {view: ...}
    const viewObject = matchProp("views", props);
    if (viewObject && viewObject.type === "ObjectExpression") {
        viewObject.properties.forEach(function(prop) {
            if (prop.value.type === "ObjectExpression") {
                res.push(matchProp("controller", prop.value.properties));
                res.push(matchProp("templateProvider", prop.value.properties));
                res.push.apply(res, matchResolve(prop.value.properties));
            }
        });
    }

    const filteredRes = res.filter(Boolean);
    return (filteredRes.length === 0 ? false : filteredRes);
}

function matchRegular(node, re) {
    // we already know that node is a (non-computed) method call
    const callee = node.callee;
    const obj = callee.object; // identifier or expression
    const method = callee.property; // identifier

    const matchAngularModule = (obj.$chained === chainedRegular || isShortDef(obj, re) || isMediumDef(obj, re) || isLongDef(obj)) &&
        is.someof(method.name, ["provider", "value", "constant", "config", "factory", "directive", "filter", "run", "controller", "service", "decorator", "animation"]);
    if (!matchAngularModule) {
        return false;
    }
    node.$chained = chainedRegular;

    if (is.someof(method.name, ["value", "constant"])) {
        return false; // affects matchAngularModule because of chaining
    }

    const args = node.arguments;
    return (is.someof(method.name, ["config", "run"]) ?
        args.length === 1 && args[0] :
        args.length === 2 && args[0].type === "Literal" && is.string(args[0].value) && args[1]);
}

// Short form: *.controller("MyCtrl", function($scope, $timeout) {});
function isShortDef(node, re) {
    return node.type === "Identifier" && is.string(node.name) && (!re || re.test(node.name));
}

// Medium form: *.*.controller("MyCtrl", function($scope, $timeout) {});
function isMediumDef(node, re) {
    if (node.type === "MemberExpression" && is.object(node.object) && is.object(node.property) && is.string(node.object.name) && is.string(node.property.name)) {
        return (!re || re.test(node.object.name + "." + node.property.name));
    }
    return false;
}

// Long form: angular.module(*).controller("MyCtrl", function($scope, $timeout) {});
function isLongDef(node) {
    return node.callee &&
        node.callee.object && node.callee.object.name === "angular" &&
        node.callee.property && node.callee.property.name === "module";
}

function last(arr) {
    return arr[arr.length - 1];
}

function matchProp(name, props) {
    for (let i = 0; i < props.length; i++) {
        const prop = props[i];
        if (prop.key.type === "Identifier" && prop.key.name === name) {
            return prop.value; // FunctionExpression or ArrayExpression
        }
    }
    return null;
}

function matchResolve(props) {
    const resolveObject = matchProp("resolve", props);
    if (resolveObject && resolveObject.type === "ObjectExpression") {
        return resolveObject.properties.map(function(prop) {
            return prop.value;
        });
    }
    return [];
};

function stringify(arr, quot) {
    return "[" + arr.map(function(arg) {
        return quot + arg.name + quot;
    }).join(", ") + "]";
}

function insertArray(functionExpression, fragments, quot) {
    const range = functionExpression.range;

    const args = stringify(functionExpression.params, quot);
    fragments.push({
        start: range[0],
        end: range[0],
        str: args.slice(0, -1) + ", ",
    });
    fragments.push({
        start: range[1],
        end: range[1],
        str: "]",
    });
}

function replaceArray(array, fragments, quot) {
    const functionExpression = last(array.elements);

    if (functionExpression.params.length === 0) {
        return removeArray(array, fragments);
    }
    const args = stringify(functionExpression.params, quot);
    fragments.push({
        start: array.range[0],
        end: functionExpression.range[0],
        str: args.slice(0, -1) + ", ",
    });
}

function removeArray(array, fragments) {
    const functionExpression = last(array.elements);

    fragments.push({
        start: array.range[0],
        end: functionExpression.range[0],
        str: "",
    });
    fragments.push({
        start: functionExpression.range[1],
        end: array.range[1],
        str: "",
    });
}

function isAnnotatedArray(node) {
    return node.type === "ArrayExpression" && node.elements.length >= 1 && last(node.elements).type === "FunctionExpression";
}
function isFunctionWithArgs(node) {
    return node.type === "FunctionExpression" && node.params.length >= 1;
}

module.exports = function ngAnnotate(src, options) {
    const mode = (options.add && options.remove ? "rebuild" :
        options.remove ? "remove" :
            options.add ? "add" : null);

    if (!mode) {
        return {src: src};
    }

    const quot = options.single_quotes ? "'" : '"';
    const re = (options.regexp && new RegExp(options.regexp));
    const ast = esprima(src, {
        range: true,
    });

    const fragments = [];

    traverse(ast, {post: function(node) {
        let targets = match(node, re);
        if (!targets) {
            return;
        }
        if (!is.array(targets)) {
            targets = [targets];
        }

        for (let i = 0; i < targets.length; i++) {
            const target = targets[i];
            if (mode === "rebuild" && isAnnotatedArray(target)) {
                replaceArray(target, fragments, quot);
            } else if (mode === "remove" && isAnnotatedArray(target)) {
                removeArray(target, fragments);
            } else if (is.someof(mode, ["add", "rebuild"]) && isFunctionWithArgs(target)) {
                insertArray(target, fragments, quot);
            }
        }
    }});

    const out = alter(src, fragments);

    return {
        src: out,
    };
}
<|endoftext|>


<commit_before>angular.module('JFC.router', ['ngRoute'])
	.config(["$routeProvider", "$locationProvider", routerFn]);

function routerFn($routeProvider, $locationProvider) {
    $routeProvider
            .when('/Home', {templateUrl: 'app/view/Home.html', controller: 'HomeController'})
            .when('/Home/:subpage', {templateUrl: 'app/view/Home.html', controller: 'HomeController'})
            .when('/Login', {templateUrl: 'app/view/Login.html', controller: 'LoginController'})
            .otherwise({
                    redirectTo:'/Home/select'
            });
    // configure html5 to get links working on jsfiddle
    $locationProvider.hashPrefix('!').html5Mode(false);
}

/*resolve: {
    // I will cause a 1 second delay
    delay: function($q, $timeout) {
            var delay = $q.defer();
            $timeout(delay.resolve, 1000);
            return delay.promise;
    }
}*/
<commit_msg>Added Route for admin page<commit_after>angular.module('JFC.router', ['ngRoute'])
	.config(["$routeProvider", "$locationProvider", routerFn]);

function routerFn($routeProvider, $locationProvider) {
    $routeProvider
            .when('/Home', {templateUrl: 'app/view/Home.html', controller: 'HomeController'})
            .when('/Home/:subpage', {templateUrl: 'app/view/Home.html', controller: 'HomeController'})
            .when('/Login', {templateUrl: 'app/view/Login.html', controller: 'LoginController'})
            .when('/Admin', {templateUrl: 'app/view/Admin.html', controller: 'AdminController'})
            .otherwise({
                    redirectTo:'/Home/select'
            });
    // configure html5 to get links working on jsfiddle
    $locationProvider.hashPrefix('!').html5Mode(false);
}

/*resolve: {
    // I will cause a 1 second delay
    delay: function($q, $timeout) {
            var delay = $q.defer();
            $timeout(delay.resolve, 1000);
            return delay.promise;
    }
}*/
<|endoftext|>


<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.

import ErrorBox from './ErrorBox'
import React, {Component} from 'react'

export default class ErrorBoundary extends Component {
  state = {hasError: false}

  componentDidCatch(error, info) {
    this.setState({hasError: true, error, info})
  }

  render() {
    return this.state.hasError
      ? <ErrorBox/>
      : this.props.children
  }
}
<commit_msg>Map and prepare stack trace<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.

import ErrorBox from './ErrorBox'
import React, {Component} from 'react'
import {mapStackTrace} from 'sourcemapped-stacktrace'

/**
 * Return the a new mapped stack where each item is an object with properties
 * for line, column and file path.
 */
function preparedMappedStack(stack) {
  let result = []

  for (let line of stack) {
    // at ... (webpack:///path:line:column)
    const MATCHED = line.match(/\(webpack:\/{3}(.+):(.+):(.+)\)/)

    // Ignore any line that is not expected.
    if (!MATCHED)
      continue

    result.push({
      path: MATCHED[1],
      line: MATCHED[2],
      column: MATCHED[3],
    })
  }

  return stack
}

export default class ErrorBoundary extends Component {
  state = {hasError: false}

  componentDidCatch(error, info) {
    mapStackTrace(error.stack, mappedStack => {
      // Prepare the mapped stack.
      error.stack = mappedStack
        |> preparedMappedStack

      // Show the error box.
      this.setState({
        hasError: true,
        error,
        info,
      })
    })
  }

  render() {
    return this.state.hasError
      ? <ErrorBox {...this.state}/>
      : this.props.children
  }
}
<|endoftext|>


<commit_before>import WebGLManager from './WebGLManager';
import RenderTarget from '../utils/RenderTarget';
import Quad from '../utils/Quad';
import { Rectangle } from '../../../math';
import Shader from '../../../Shader';
import filterTransforms from '../filters/filterTransforms';
import bitTwiddle from 'bit-twiddle';

/**
 * @ignore
 * @class
 */
class FilterState
{
    /**
     *
     */
    constructor()
    {
        this.renderTarget = null;
        this.sourceFrame = new Rectangle();
        this.destinationFrame = new Rectangle();
        this.filters = [];
        this.target = null;
        this.resolution = 1;
    }
}

/**
 * @class
 * @memberof PIXI
 * @extends PIXI.WebGLManager
 */
export default class FilterManager extends WebGLManager
{
    /**
     * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for.
     */
    constructor(renderer)
    {
        super(renderer);

        this.gl = this.renderer.gl;
        // know about sprites!
        this.quad = new Quad(this.gl, renderer.state.attribState);

        this.shaderCache = {};
        // todo add default!
        this.pool = {};

        this.filterData = null;
    }

    /**
     * Adds a new filter to the manager.
     *
     * @param {PIXI.DisplayObject} target - The target of the filter to render.
     * @param {PIXI.Filter[]} filters - The filters to apply.
     */
    pushFilter(target, filters)
    {
        const renderer = this.renderer;

        let filterData = this.filterData;

        if (!filterData)
        {
            filterData = this.renderer._activeRenderTarget.filterStack;

            // add new stack
            const filterState = new FilterState();

            filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
            filterState.renderTarget = renderer._activeRenderTarget;

            this.renderer._activeRenderTarget.filterData = filterData = {
                index: 0,
                stack: [filterState],
            };

            this.filterData = filterData;
        }

        // get the current filter state..
        let currentState = filterData.stack[++filterData.index];

        if (!currentState)
        {
            currentState = filterData.stack[filterData.index] = new FilterState();
        }

        // for now we go off the filter of the first resolution..
        const resolution = filters[0].resolution;
        const padding = filters[0].padding | 0;
        const targetBounds = target.filterArea || target.getBounds(true);
        const sourceFrame = currentState.sourceFrame;
        const destinationFrame = currentState.destinationFrame;

        sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
        sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
        sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
        sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;

        if (filterData.stack[0].renderTarget.transform)
        { //

            // TODO we should fit the rect around the transform..
        }
        else
        {
            sourceFrame.fit(filterData.stack[0].destinationFrame);
        }

         // lets pplay the padding After we fit the element to the screen.
        // this should stop the strange side effects that can occour when cropping to the edges
        sourceFrame.pad(padding);

        destinationFrame.width = sourceFrame.width;
        destinationFrame.height = sourceFrame.height;

        // lets play the padding after we fit the element to the screen.
        // this should stop the strange side effects that can occour when cropping to the edges

        const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);

        currentState.target = target;
        currentState.filters = filters;
        currentState.resolution = resolution;
        currentState.renderTarget = renderTarget;

        // bind the render taget to draw the shape in the top corner..

        renderTarget.setFrame(destinationFrame, sourceFrame);
        // bind the render target
        renderer.bindRenderTarget(renderTarget);

        // clear the renderTarget
        renderer.clear();// [0.5,0.5,0.5, 1.0]);
    }

    /**
     * Pops off the filter and applies it.
     *
     */
    popFilter()
    {
        const filterData = this.filterData;

        const lastState = filterData.stack[filterData.index - 1];
        const currentState = filterData.stack[filterData.index];

        this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();

        const filters = currentState.filters;

        if (filters.length === 1)
        {
            filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false);
            this.freePotRenderTarget(currentState.renderTarget);
        }
        else
        {
            let flip = currentState.renderTarget;
            let flop = this.getPotRenderTarget(
                this.renderer.gl,
                currentState.sourceFrame.width,
                currentState.sourceFrame.height,
                1
            );

            flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);

            let i = 0;

            for (i = 0; i < filters.length - 1; ++i)
            {
                filters[i].apply(this, flip, flop, true);

                const t = flip;

                flip = flop;
                flop = t;
            }

            filters[i].apply(this, flip, lastState.renderTarget, false);

            this.freePotRenderTarget(flip);
            this.freePotRenderTarget(flop);
        }

        filterData.index--;

        if (filterData.index === 0)
        {
            this.filterData = null;
        }
    }

    /**
     * Draws a filter.
     *
     * @param {PIXI.Filter} filter - The filter to draw.
     * @param {PIXI.RenderTarget} input - The input render target.
     * @param {PIXI.RenderTarget} output - The target to output to.
     * @param {boolean} clear - Should the output be cleared before rendering to it
     */
    applyFilter(filter, input, output, clear)
    {
        const renderer = this.renderer;
        let shader = filter.glShaders[renderer.CONTEXT_UID];

        // cacheing..
        if (!shader)
        {
            if (filter.glShaderKey)
            {
                shader = this.shaderCache[filter.glShaderKey];

                if (!shader)
                {
                    shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);

                    filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
                }
            }
            else
            {
                shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
            }

            // TODO - this only needs to be done once?
            this.quad.initVao(shader);
        }

        renderer.bindRenderTarget(output);

        if (clear)
        {
            const gl = renderer.gl;

            gl.disable(gl.SCISSOR_TEST);
            renderer.clear();// [1, 1, 1, 1]);
            gl.enable(gl.SCISSOR_TEST);
        }

        // in case the render target is being masked using a scissor rect
        if (output === renderer.maskManager.scissorRenderTarget)
        {
            renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData);
        }

        renderer.bindShader(shader);

        // this syncs the pixi filters  uniforms with glsl uniforms
        this.syncUniforms(shader, filter);

        // bind the input texture..
        input.texture.bind(0);
        // when you manually bind a texture, please switch active texture location to it
        renderer._activeTextureLocation = 0;

        renderer.state.setBlendMode(filter.blendMode);

        this.quad.draw();
    }

    /**
     * Uploads the uniforms of the filter.
     *
     * @param {GLShader} shader - The underlying gl shader.
     * @param {PIXI.Filter} filter - The filter we are synchronizing.
     */
    syncUniforms(shader, filter)
    {
        const uniformData = filter.uniformData;
        const uniforms = filter.uniforms;

        // 0 is reserverd for the pixi texture so we start at 1!
        let textureCount = 1;
        let currentState;

        if (shader.uniforms.data.filterArea)
        {
            currentState = this.filterData.stack[this.filterData.index];
            const filterArea = shader.uniforms.filterArea;

            filterArea[0] = currentState.renderTarget.size.width;
            filterArea[1] = currentState.renderTarget.size.height;
            filterArea[2] = currentState.sourceFrame.x;
            filterArea[3] = currentState.sourceFrame.y;

            shader.uniforms.filterArea = filterArea;
        }

        // use this to clamp displaced texture coords so they belong to filterArea
        // see displacementFilter fragment shader for an example
        if (shader.uniforms.data.filterClamp)
        {
            currentState = this.filterData.stack[this.filterData.index];

            const filterClamp = shader.uniforms.filterClamp;

            filterClamp[0] = 0;
            filterClamp[1] = 0;
            filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
            filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;

            shader.uniforms.filterClamp = filterClamp;
        }

        // TODO Cacheing layer..
        for (const i in uniformData)
        {
            if (uniformData[i].type === 'sampler2D')
            {
                shader.uniforms[i] = textureCount;

                if (uniforms[i].baseTexture)
                {
                    this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
                }
                else
                {
                    // this is helpful as renderTargets can also be set.
                    // Although thinking about it, we could probably
                    // make the filter texture cache return a RenderTexture
                    // rather than a renderTarget
                    const gl = this.renderer.gl;

                    this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount;

                    gl.activeTexture(gl.TEXTURE0 + textureCount);
                    uniforms[i].texture.bind();
                }

                textureCount++;
            }
            else if (uniformData[i].type === 'mat3')
            {
                // check if its pixi matrix..
                if (uniforms[i].a !== undefined)
                {
                    shader.uniforms[i] = uniforms[i].toArray(true);
                }
                else
                {
                    shader.uniforms[i] = uniforms[i];
                }
            }
            else if (uniformData[i].type === 'vec2')
            {
                // check if its a point..
                if (uniforms[i].x !== undefined)
               {
                    const val = shader.uniforms[i] || new Float32Array(2);

                    val[0] = uniforms[i].x;
                    val[1] = uniforms[i].y;
                    shader.uniforms[i] = val;
                }
                else
               {
                    shader.uniforms[i] = uniforms[i];
                }
            }
            else if (uniformData[i].type === 'float')
            {
                if (shader.uniforms.data[i].value !== uniformData[i])
                {
                    shader.uniforms[i] = uniforms[i];
                }
            }
            else
            {
                shader.uniforms[i] = uniforms[i];
            }
        }
    }

    /**
     * Gets a render target from the pool, or creates a new one.
     *
     * @param {boolean} clear - Should we clear the render texture when we get it?
     * @param {number} resolution - The resolution of the target.
     * @return {PIXI.RenderTarget} The new render target
     */
    getRenderTarget(clear, resolution)
    {
        const currentState = this.filterData.stack[this.filterData.index];
        const renderTarget = this.getPotRenderTarget(
            this.renderer.gl,
            currentState.sourceFrame.width,
            currentState.sourceFrame.height,
            resolution || currentState.resolution
        );

        renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);

        return renderTarget;
    }

    /**
     * Returns a render target to the pool.
     *
     * @param {PIXI.RenderTarget} renderTarget - The render target to return.
     */
    returnRenderTarget(renderTarget)
    {
        this.freePotRenderTarget(renderTarget);
    }

    /**
     * Calculates the mapped matrix.
     *
     * TODO playing around here.. this is temporary - (will end up in the shader)
     * this returns a matrix that will normalise map filter cords in the filter to screen space
     *
     * @param {PIXI.Matrix} outputMatrix - the matrix to output to.
     * @return {PIXI.Matrix} The mapped matrix.
     */
    calculateScreenSpaceMatrix(outputMatrix)
    {
        const currentState = this.filterData.stack[this.filterData.index];

        return filterTransforms.calculateScreenSpaceMatrix(
            outputMatrix,
            currentState.sourceFrame,
            currentState.renderTarget.size
        );
    }

    /**
     * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
     *
     * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
     * @return {PIXI.Matrix} The mapped matrix.
     */
    calculateNormalizedScreenSpaceMatrix(outputMatrix)
    {
        const currentState = this.filterData.stack[this.filterData.index];

        return filterTransforms.calculateNormalizedScreenSpaceMatrix(
            outputMatrix,
            currentState.sourceFrame,
            currentState.renderTarget.size,
            currentState.destinationFrame
        );
    }

    /**
     * This will map the filter coord so that a texture can be used based on the transform of a sprite
     *
     * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
     * @param {PIXI.Sprite} sprite - The sprite to map to.
     * @return {PIXI.Matrix} The mapped matrix.
     */
    calculateSpriteMatrix(outputMatrix, sprite)
    {
        const currentState = this.filterData.stack[this.filterData.index];

        return filterTransforms.calculateSpriteMatrix(
            outputMatrix,
            currentState.sourceFrame,
            currentState.renderTarget.size,
            sprite
        );
    }

    /**
     * Destroys this Filter Manager.
     *
     */
    destroy()
    {
        this.shaderCache = [];
        this.emptyPool();
    }

    /**
     * Gets a Power-of-Two render texture.
     *
     * TODO move to a seperate class could be on renderer?
     * also - could cause issue with multiple contexts?
     *
     * @private
     * @param {WebGLRenderingContext} gl - The webgl rendering context
     * @param {number} minWidth - The minimum width of the render target.
     * @param {number} minHeight - The minimum height of the render target.
     * @param {number} resolution - The resolution of the render target.
     * @return {PIXI.RenderTarget} The new render target.
     */
    getPotRenderTarget(gl, minWidth, minHeight, resolution)
    {
        // TODO you coud return a bigger texture if there is not one in the pool?
        minWidth = bitTwiddle.nextPow2(minWidth * resolution);
        minHeight = bitTwiddle.nextPow2(minHeight * resolution);

        const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);

        if (!this.pool[key])
        {
            this.pool[key] = [];
        }

        const renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1);

        // manually tweak the resolution...
        // this will not modify the size of the frame buffer, just its resolution.
        renderTarget.resolution = resolution;
        renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
        renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;

        return renderTarget;
    }

    /**
     * Empties the texture pool.
     *
     */
    emptyPool()
    {
        for (const i in this.pool)
        {
            const textures = this.pool[i];

            if (textures)
            {
                for (let j = 0; j < textures.length; j++)
                {
                    textures[j].destroy(true);
                }
            }
        }

        this.pool = {};
    }

    /**
     * Frees a render target back into the pool.
     *
     * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
     */
    freePotRenderTarget(renderTarget)
    {
        const minWidth = renderTarget.size.width * renderTarget.resolution;
        const minHeight = renderTarget.size.height * renderTarget.resolution;
        const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);

        this.pool[key].push(renderTarget);
    }
}
<commit_msg>strange imports problem<commit_after>import WebGLManager from './WebGLManager';
import RenderTarget from '../utils/RenderTarget';
import Quad from '../utils/Quad';
import { Rectangle } from '../../../math';
import Shader from '../../../Shader';
import * as filterTransforms from '../filters/filterTransforms';
import bitTwiddle from 'bit-twiddle';

/**
 * @ignore
 * @class
 */
class FilterState
{
    /**
     *
     */
    constructor()
    {
        this.renderTarget = null;
        this.sourceFrame = new Rectangle();
        this.destinationFrame = new Rectangle();
        this.filters = [];
        this.target = null;
        this.resolution = 1;
    }
}

/**
 * @class
 * @memberof PIXI
 * @extends PIXI.WebGLManager
 */
export default class FilterManager extends WebGLManager
{
    /**
     * @param {PIXI.WebGLRenderer} renderer - The renderer this manager works for.
     */
    constructor(renderer)
    {
        super(renderer);

        this.gl = this.renderer.gl;
        // know about sprites!
        this.quad = new Quad(this.gl, renderer.state.attribState);

        this.shaderCache = {};
        // todo add default!
        this.pool = {};

        this.filterData = null;
    }

    /**
     * Adds a new filter to the manager.
     *
     * @param {PIXI.DisplayObject} target - The target of the filter to render.
     * @param {PIXI.Filter[]} filters - The filters to apply.
     */
    pushFilter(target, filters)
    {
        const renderer = this.renderer;

        let filterData = this.filterData;

        if (!filterData)
        {
            filterData = this.renderer._activeRenderTarget.filterStack;

            // add new stack
            const filterState = new FilterState();

            filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size;
            filterState.renderTarget = renderer._activeRenderTarget;

            this.renderer._activeRenderTarget.filterData = filterData = {
                index: 0,
                stack: [filterState],
            };

            this.filterData = filterData;
        }

        // get the current filter state..
        let currentState = filterData.stack[++filterData.index];

        if (!currentState)
        {
            currentState = filterData.stack[filterData.index] = new FilterState();
        }

        // for now we go off the filter of the first resolution..
        const resolution = filters[0].resolution;
        const padding = filters[0].padding | 0;
        const targetBounds = target.filterArea || target.getBounds(true);
        const sourceFrame = currentState.sourceFrame;
        const destinationFrame = currentState.destinationFrame;

        sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution;
        sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution;
        sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution;
        sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution;

        if (filterData.stack[0].renderTarget.transform)
        { //

            // TODO we should fit the rect around the transform..
        }
        else
        {
            sourceFrame.fit(filterData.stack[0].destinationFrame);
        }

         // lets pplay the padding After we fit the element to the screen.
        // this should stop the strange side effects that can occour when cropping to the edges
        sourceFrame.pad(padding);

        destinationFrame.width = sourceFrame.width;
        destinationFrame.height = sourceFrame.height;

        // lets play the padding after we fit the element to the screen.
        // this should stop the strange side effects that can occour when cropping to the edges

        const renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution);

        currentState.target = target;
        currentState.filters = filters;
        currentState.resolution = resolution;
        currentState.renderTarget = renderTarget;

        // bind the render taget to draw the shape in the top corner..

        renderTarget.setFrame(destinationFrame, sourceFrame);
        // bind the render target
        renderer.bindRenderTarget(renderTarget);

        // clear the renderTarget
        renderer.clear();// [0.5,0.5,0.5, 1.0]);
    }

    /**
     * Pops off the filter and applies it.
     *
     */
    popFilter()
    {
        const filterData = this.filterData;

        const lastState = filterData.stack[filterData.index - 1];
        const currentState = filterData.stack[filterData.index];

        this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload();

        const filters = currentState.filters;

        if (filters.length === 1)
        {
            filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false);
            this.freePotRenderTarget(currentState.renderTarget);
        }
        else
        {
            let flip = currentState.renderTarget;
            let flop = this.getPotRenderTarget(
                this.renderer.gl,
                currentState.sourceFrame.width,
                currentState.sourceFrame.height,
                1
            );

            flop.setFrame(currentState.destinationFrame, currentState.sourceFrame);

            let i = 0;

            for (i = 0; i < filters.length - 1; ++i)
            {
                filters[i].apply(this, flip, flop, true);

                const t = flip;

                flip = flop;
                flop = t;
            }

            filters[i].apply(this, flip, lastState.renderTarget, false);

            this.freePotRenderTarget(flip);
            this.freePotRenderTarget(flop);
        }

        filterData.index--;

        if (filterData.index === 0)
        {
            this.filterData = null;
        }
    }

    /**
     * Draws a filter.
     *
     * @param {PIXI.Filter} filter - The filter to draw.
     * @param {PIXI.RenderTarget} input - The input render target.
     * @param {PIXI.RenderTarget} output - The target to output to.
     * @param {boolean} clear - Should the output be cleared before rendering to it
     */
    applyFilter(filter, input, output, clear)
    {
        const renderer = this.renderer;
        let shader = filter.glShaders[renderer.CONTEXT_UID];

        // cacheing..
        if (!shader)
        {
            if (filter.glShaderKey)
            {
                shader = this.shaderCache[filter.glShaderKey];

                if (!shader)
                {
                    shader = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);

                    filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = shader;
                }
            }
            else
            {
                shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc);
            }

            // TODO - this only needs to be done once?
            this.quad.initVao(shader);
        }

        renderer.bindRenderTarget(output);

        if (clear)
        {
            const gl = renderer.gl;

            gl.disable(gl.SCISSOR_TEST);
            renderer.clear();// [1, 1, 1, 1]);
            gl.enable(gl.SCISSOR_TEST);
        }

        // in case the render target is being masked using a scissor rect
        if (output === renderer.maskManager.scissorRenderTarget)
        {
            renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData);
        }

        renderer.bindShader(shader);

        // this syncs the pixi filters  uniforms with glsl uniforms
        this.syncUniforms(shader, filter);

        // bind the input texture..
        input.texture.bind(0);
        // when you manually bind a texture, please switch active texture location to it
        renderer._activeTextureLocation = 0;

        renderer.state.setBlendMode(filter.blendMode);

        this.quad.draw();
    }

    /**
     * Uploads the uniforms of the filter.
     *
     * @param {GLShader} shader - The underlying gl shader.
     * @param {PIXI.Filter} filter - The filter we are synchronizing.
     */
    syncUniforms(shader, filter)
    {
        const uniformData = filter.uniformData;
        const uniforms = filter.uniforms;

        // 0 is reserverd for the pixi texture so we start at 1!
        let textureCount = 1;
        let currentState;

        if (shader.uniforms.data.filterArea)
        {
            currentState = this.filterData.stack[this.filterData.index];
            const filterArea = shader.uniforms.filterArea;

            filterArea[0] = currentState.renderTarget.size.width;
            filterArea[1] = currentState.renderTarget.size.height;
            filterArea[2] = currentState.sourceFrame.x;
            filterArea[3] = currentState.sourceFrame.y;

            shader.uniforms.filterArea = filterArea;
        }

        // use this to clamp displaced texture coords so they belong to filterArea
        // see displacementFilter fragment shader for an example
        if (shader.uniforms.data.filterClamp)
        {
            currentState = this.filterData.stack[this.filterData.index];

            const filterClamp = shader.uniforms.filterClamp;

            filterClamp[0] = 0;
            filterClamp[1] = 0;
            filterClamp[2] = (currentState.sourceFrame.width - 1) / currentState.renderTarget.size.width;
            filterClamp[3] = (currentState.sourceFrame.height - 1) / currentState.renderTarget.size.height;

            shader.uniforms.filterClamp = filterClamp;
        }

        // TODO Cacheing layer..
        for (const i in uniformData)
        {
            if (uniformData[i].type === 'sampler2D')
            {
                shader.uniforms[i] = textureCount;

                if (uniforms[i].baseTexture)
                {
                    this.renderer.bindTexture(uniforms[i].baseTexture, textureCount);
                }
                else
                {
                    // this is helpful as renderTargets can also be set.
                    // Although thinking about it, we could probably
                    // make the filter texture cache return a RenderTexture
                    // rather than a renderTarget
                    const gl = this.renderer.gl;

                    this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount;

                    gl.activeTexture(gl.TEXTURE0 + textureCount);
                    uniforms[i].texture.bind();
                }

                textureCount++;
            }
            else if (uniformData[i].type === 'mat3')
            {
                // check if its pixi matrix..
                if (uniforms[i].a !== undefined)
                {
                    shader.uniforms[i] = uniforms[i].toArray(true);
                }
                else
                {
                    shader.uniforms[i] = uniforms[i];
                }
            }
            else if (uniformData[i].type === 'vec2')
            {
                // check if its a point..
                if (uniforms[i].x !== undefined)
               {
                    const val = shader.uniforms[i] || new Float32Array(2);

                    val[0] = uniforms[i].x;
                    val[1] = uniforms[i].y;
                    shader.uniforms[i] = val;
                }
                else
               {
                    shader.uniforms[i] = uniforms[i];
                }
            }
            else if (uniformData[i].type === 'float')
            {
                if (shader.uniforms.data[i].value !== uniformData[i])
                {
                    shader.uniforms[i] = uniforms[i];
                }
            }
            else
            {
                shader.uniforms[i] = uniforms[i];
            }
        }
    }

    /**
     * Gets a render target from the pool, or creates a new one.
     *
     * @param {boolean} clear - Should we clear the render texture when we get it?
     * @param {number} resolution - The resolution of the target.
     * @return {PIXI.RenderTarget} The new render target
     */
    getRenderTarget(clear, resolution)
    {
        const currentState = this.filterData.stack[this.filterData.index];
        const renderTarget = this.getPotRenderTarget(
            this.renderer.gl,
            currentState.sourceFrame.width,
            currentState.sourceFrame.height,
            resolution || currentState.resolution
        );

        renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame);

        return renderTarget;
    }

    /**
     * Returns a render target to the pool.
     *
     * @param {PIXI.RenderTarget} renderTarget - The render target to return.
     */
    returnRenderTarget(renderTarget)
    {
        this.freePotRenderTarget(renderTarget);
    }

    /**
     * Calculates the mapped matrix.
     *
     * TODO playing around here.. this is temporary - (will end up in the shader)
     * this returns a matrix that will normalise map filter cords in the filter to screen space
     *
     * @param {PIXI.Matrix} outputMatrix - the matrix to output to.
     * @return {PIXI.Matrix} The mapped matrix.
     */
    calculateScreenSpaceMatrix(outputMatrix)
    {
        const currentState = this.filterData.stack[this.filterData.index];

        return filterTransforms.calculateScreenSpaceMatrix(
            outputMatrix,
            currentState.sourceFrame,
            currentState.renderTarget.size
        );
    }

    /**
     * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea
     *
     * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
     * @return {PIXI.Matrix} The mapped matrix.
     */
    calculateNormalizedScreenSpaceMatrix(outputMatrix)
    {
        const currentState = this.filterData.stack[this.filterData.index];

        return filterTransforms.calculateNormalizedScreenSpaceMatrix(
            outputMatrix,
            currentState.sourceFrame,
            currentState.renderTarget.size,
            currentState.destinationFrame
        );
    }

    /**
     * This will map the filter coord so that a texture can be used based on the transform of a sprite
     *
     * @param {PIXI.Matrix} outputMatrix - The matrix to output to.
     * @param {PIXI.Sprite} sprite - The sprite to map to.
     * @return {PIXI.Matrix} The mapped matrix.
     */
    calculateSpriteMatrix(outputMatrix, sprite)
    {
        const currentState = this.filterData.stack[this.filterData.index];

        return filterTransforms.calculateSpriteMatrix(
            outputMatrix,
            currentState.sourceFrame,
            currentState.renderTarget.size,
            sprite
        );
    }

    /**
     * Destroys this Filter Manager.
     *
     */
    destroy()
    {
        this.shaderCache = [];
        this.emptyPool();
    }

    /**
     * Gets a Power-of-Two render texture.
     *
     * TODO move to a seperate class could be on renderer?
     * also - could cause issue with multiple contexts?
     *
     * @private
     * @param {WebGLRenderingContext} gl - The webgl rendering context
     * @param {number} minWidth - The minimum width of the render target.
     * @param {number} minHeight - The minimum height of the render target.
     * @param {number} resolution - The resolution of the render target.
     * @return {PIXI.RenderTarget} The new render target.
     */
    getPotRenderTarget(gl, minWidth, minHeight, resolution)
    {
        // TODO you coud return a bigger texture if there is not one in the pool?
        minWidth = bitTwiddle.nextPow2(minWidth * resolution);
        minHeight = bitTwiddle.nextPow2(minHeight * resolution);

        const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);

        if (!this.pool[key])
        {
            this.pool[key] = [];
        }

        const renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1);

        // manually tweak the resolution...
        // this will not modify the size of the frame buffer, just its resolution.
        renderTarget.resolution = resolution;
        renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution;
        renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution;

        return renderTarget;
    }

    /**
     * Empties the texture pool.
     *
     */
    emptyPool()
    {
        for (const i in this.pool)
        {
            const textures = this.pool[i];

            if (textures)
            {
                for (let j = 0; j < textures.length; j++)
                {
                    textures[j].destroy(true);
                }
            }
        }

        this.pool = {};
    }

    /**
     * Frees a render target back into the pool.
     *
     * @param {PIXI.RenderTarget} renderTarget - The renderTarget to free
     */
    freePotRenderTarget(renderTarget)
    {
        const minWidth = renderTarget.size.width * renderTarget.resolution;
        const minHeight = renderTarget.size.height * renderTarget.resolution;
        const key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF);

        this.pool[key].push(renderTarget);
    }
}
<|endoftext|>


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

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

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

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

import {randomString} from '../../../randomstring';
import {logger} from '../../../logger';
import {
    CANCEL_TYPE,
    PHASE_STARTED,
    PHASE_READY,
    REQUEST_TYPE,
    READY_TYPE,
    START_TYPE,
    VerificationRequest,
} from "./VerificationRequest";
import {errorFromEvent, newUnexpectedMessageError} from "../Error";
import {MatrixEvent} from "../../../models/event";

/**
 * A key verification channel that sends verification events over to_device messages.
 * Generates its own transaction ids.
 */
export class ToDeviceChannel {
    // userId and devices of user we're about to verify
    constructor(client, userId, devices, transactionId = null, deviceId = null) {
        this._client = client;
        this.userId = userId;
        this._devices = devices;
        this.transactionId = transactionId;
        this._deviceId = deviceId;
    }

    get deviceId() {
        return this._deviceId;
    }

    static getEventType(event) {
        return event.getType();
    }

    /**
     * Extract the transaction id used by a given key verification event, if any
     * @param {MatrixEvent} event the event
     * @returns {string} the transaction id
     */
    static getTransactionId(event) {
        const content = event.getContent();
        return content && content.transaction_id;
    }

    /**
     * Checks whether the given event type should be allowed to initiate a new VerificationRequest over this channel
     * @param {string} type the event type to check
     * @returns {bool} boolean flag
     */
    static canCreateRequest(type) {
        return type === REQUEST_TYPE || type === START_TYPE;
    }

    /**
     * Checks whether this event is a well-formed key verification event.
     * This only does checks that don't rely on the current state of a potentially already channel
     * so we can prevent channels being created by invalid events.
     * `handleEvent` can do more checks and choose to ignore invalid events.
     * @param {MatrixEvent} event the event to validate
     * @param {MatrixClient} client the client to get the current user and device id from
     * @returns {bool} whether the event is valid and should be passed to handleEvent
     */
    static validateEvent(event, client) {
        if (event.isCancelled()) {
            logger.warn("Ignoring flagged verification request from "
                 + event.getSender());
            return false;
        }
        const content = event.getContent();
        if (!content) {
            logger.warn("ToDeviceChannel.validateEvent: invalid: no content");
            return false;
        }

        if (!content.transaction_id) {
            logger.warn("ToDeviceChannel.validateEvent: invalid: no transaction_id");
            return false;
        }

        const type = event.getType();

        if (type === REQUEST_TYPE) {
            if (!Number.isFinite(content.timestamp)) {
                logger.warn("ToDeviceChannel.validateEvent: invalid: no timestamp");
                return false;
            }
            if (event.getSender() === client.getUserId() &&
                    content.from_device == client.getDeviceId()
            ) {
                // ignore requests from ourselves, because it doesn't make sense for a
                // device to verify itself
                logger.warn("ToDeviceChannel.validateEvent: invalid: from own device");
                return false;
            }
        }

        return VerificationRequest.validateEvent(type, event, client);
    }

    /**
     * @param {MatrixEvent} event the event to get the timestamp of
     * @return {number} the timestamp when the event was sent
     */
    getTimestamp(event) {
        const content = event.getContent();
        return content && content.timestamp;
    }

    /**
     * Changes the state of the channel, request, and verifier in response to a key verification event.
     * @param {MatrixEvent} event to handle
     * @param {VerificationRequest} request the request to forward handling to
     * @param {bool} isLiveEvent whether this is an even received through sync or not
     * @returns {Promise} a promise that resolves when any requests as an anwser to the passed-in event are sent.
     */
    async handleEvent(event, request, isLiveEvent) {
        const type = event.getType();
        const content = event.getContent();
        if (type === REQUEST_TYPE || type === START_TYPE) {
            if (!this.transactionId) {
                this.transactionId = content.transaction_id;
            }
            const deviceId = content.from_device;
            // adopt deviceId if not set before and valid
            if (!this._deviceId && this._devices.includes(deviceId)) {
                this._deviceId = deviceId;
            }
            // if no device id or different from addopted one, cancel with sender
            if (!this._deviceId || this._deviceId !== deviceId) {
                // also check that message came from the device we sent the request to earlier on
                // and do send a cancel message to that device
                // (but don't cancel the request for the device we should be talking to)
                const cancelContent =
                    this.completeContent(errorFromEvent(newUnexpectedMessageError()));
                return this._sendToDevices(CANCEL_TYPE, cancelContent, [deviceId]);
            }
        }
        const wasStarted = request.phase === PHASE_STARTED ||
                           request.phase === PHASE_READY;

        await request.handleEvent(event.getType(), event, isLiveEvent, false, false);

        const isStarted = request.phase === PHASE_STARTED ||
                          request.phase === PHASE_READY;

        const isAcceptingEvent = type === START_TYPE || type === READY_TYPE;
        // the request has picked a ready or start event, tell the other devices about it
        if (isAcceptingEvent && !wasStarted && isStarted && this._deviceId) {
            const nonChosenDevices = this._devices.filter(d => d !== this._deviceId);
            if (nonChosenDevices.length) {
                const message = this.completeContent({
                    code: "m.accepted",
                    reason: "Verification request accepted by another device",
                });
                await this._sendToDevices(CANCEL_TYPE, message, nonChosenDevices);
            }
        }
    }

    /**
     * See {InRoomChannel.completedContentFromEvent} why this is needed.
     * @param {MatrixEvent} event the received event
     * @returns {Object} the content object
     */
    completedContentFromEvent(event) {
        return event.getContent();
    }

    /**
     * Add all the fields to content needed for sending it over this channel.
     * This is public so verification methods (SAS uses this) can get the exact
     * content that will be sent independent of the used channel,
     * as they need to calculate the hash of it.
     * @param {string} type the event type
     * @param {object} content the (incomplete) content
     * @returns {object} the complete content, as it will be sent.
     */
    completeContent(type, content) {
        // make a copy
        content = Object.assign({}, content);
        if (this.transactionId) {
            content.transaction_id = this.transactionId;
        }
        if (type === REQUEST_TYPE || type === READY_TYPE || type === START_TYPE) {
            content.from_device = this._client.getDeviceId();
        }
        if (type === REQUEST_TYPE) {
            content.timestamp = Date.now();
        }
        return content;
    }

    /**
     * Send an event over the channel with the content not having gone through `completeContent`.
     * @param {string} type the event type
     * @param {object} uncompletedContent the (incomplete) content
     * @returns {Promise} the promise of the request
     */
    send(type, uncompletedContent = {}) {
        // create transaction id when sending request
        if ((type === REQUEST_TYPE || type === START_TYPE) && !this.transactionId) {
            this.transactionId = ToDeviceChannel.makeTransactionId();
        }
        const content = this.completeContent(type, uncompletedContent);
        return this.sendCompleted(type, content);
    }

    /**
     * Send an event over the channel with the content having gone through `completeContent` already.
     * @param {string} type the event type
     * @param {object} content
     * @returns {Promise} the promise of the request
     */
    async sendCompleted(type, content) {
        let result;
        if (type === REQUEST_TYPE) {
            result = await this._sendToDevices(type, content, this._devices);
        } else {
            result = await this._sendToDevices(type, content, [this._deviceId]);
        }
        // the VerificationRequest state machine requires remote echos of the event
        // the client sends itself, so we fake this for to_device messages
        const remoteEchoEvent = new MatrixEvent({
            sender: this._client.getUserId(),
            content,
            type,
        });
        await this._request.handleEvent(
            type,
            remoteEchoEvent,
            /*isLiveEvent=*/true,
            /*isRemoteEcho=*/true,
            /*isSentByUs=*/true,
        );
        return result;
    }

    _sendToDevices(type, content, devices) {
        if (devices.length) {
            const msgMap = {};
            for (const deviceId of devices) {
                msgMap[deviceId] = content;
            }

            return this._client.sendToDevice(type, {[this.userId]: msgMap});
        } else {
            return Promise.resolve();
        }
    }

    /**
     * Allow Crypto module to create and know the transaction id before the .start event gets sent.
     * @returns {string} the transaction id
     */
    static makeTransactionId() {
        return randomString(32);
    }
}

export class ToDeviceRequests {
    constructor() {
        this._requestsByUserId = new Map();
    }

    getRequest(event) {
        return this.getRequestBySenderAndTxnId(
            event.getSender(),
            ToDeviceChannel.getTransactionId(event),
        );
    }

    getRequestByChannel(channel) {
        return this.getRequestBySenderAndTxnId(channel.userId, channel.transactionId);
    }

    getRequestBySenderAndTxnId(sender, txnId) {
        const requestsByTxnId = this._requestsByUserId.get(sender);
        if (requestsByTxnId) {
            return requestsByTxnId.get(txnId);
        }
    }

    setRequest(event, request) {
        this.setRequestBySenderAndTxnId(
            event.getSender(),
            ToDeviceChannel.getTransactionId(event),
            request,
        );
    }

    setRequestByChannel(channel, request) {
        this.setRequestBySenderAndTxnId(channel.userId, channel.transactionId, request);
    }

    setRequestBySenderAndTxnId(sender, txnId, request) {
        let requestsByTxnId = this._requestsByUserId.get(sender);
        if (!requestsByTxnId) {
            requestsByTxnId = new Map();
            this._requestsByUserId.set(sender, requestsByTxnId);
        }
        requestsByTxnId.set(txnId, request);
    }

    removeRequest(event) {
        const userId = event.getSender();
        const requestsByTxnId = this._requestsByUserId.get(userId);
        if (requestsByTxnId) {
            requestsByTxnId.delete(ToDeviceChannel.getTransactionId(event));
            if (requestsByTxnId.size === 0) {
                this._requestsByUserId.delete(userId);
            }
        }
    }
}
<commit_msg>for the right panel to work, the verifier should send .done events<commit_after>/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

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

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

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

import {randomString} from '../../../randomstring';
import {logger} from '../../../logger';
import {
    CANCEL_TYPE,
    PHASE_STARTED,
    PHASE_READY,
    REQUEST_TYPE,
    READY_TYPE,
    START_TYPE,
    VerificationRequest,
} from "./VerificationRequest";
import {errorFromEvent, newUnexpectedMessageError} from "../Error";
import {MatrixEvent} from "../../../models/event";

/**
 * A key verification channel that sends verification events over to_device messages.
 * Generates its own transaction ids.
 */
export class ToDeviceChannel {
    // userId and devices of user we're about to verify
    constructor(client, userId, devices, transactionId = null, deviceId = null) {
        this._client = client;
        this.userId = userId;
        this._devices = devices;
        this.transactionId = transactionId;
        this._deviceId = deviceId;
    }

    get deviceId() {
        return this._deviceId;
    }

    get needsDoneMessage() {
        return true;
    }

    static getEventType(event) {
        return event.getType();
    }

    /**
     * Extract the transaction id used by a given key verification event, if any
     * @param {MatrixEvent} event the event
     * @returns {string} the transaction id
     */
    static getTransactionId(event) {
        const content = event.getContent();
        return content && content.transaction_id;
    }

    /**
     * Checks whether the given event type should be allowed to initiate a new VerificationRequest over this channel
     * @param {string} type the event type to check
     * @returns {bool} boolean flag
     */
    static canCreateRequest(type) {
        return type === REQUEST_TYPE || type === START_TYPE;
    }

    /**
     * Checks whether this event is a well-formed key verification event.
     * This only does checks that don't rely on the current state of a potentially already channel
     * so we can prevent channels being created by invalid events.
     * `handleEvent` can do more checks and choose to ignore invalid events.
     * @param {MatrixEvent} event the event to validate
     * @param {MatrixClient} client the client to get the current user and device id from
     * @returns {bool} whether the event is valid and should be passed to handleEvent
     */
    static validateEvent(event, client) {
        if (event.isCancelled()) {
            logger.warn("Ignoring flagged verification request from "
                 + event.getSender());
            return false;
        }
        const content = event.getContent();
        if (!content) {
            logger.warn("ToDeviceChannel.validateEvent: invalid: no content");
            return false;
        }

        if (!content.transaction_id) {
            logger.warn("ToDeviceChannel.validateEvent: invalid: no transaction_id");
            return false;
        }

        const type = event.getType();

        if (type === REQUEST_TYPE) {
            if (!Number.isFinite(content.timestamp)) {
                logger.warn("ToDeviceChannel.validateEvent: invalid: no timestamp");
                return false;
            }
            if (event.getSender() === client.getUserId() &&
                    content.from_device == client.getDeviceId()
            ) {
                // ignore requests from ourselves, because it doesn't make sense for a
                // device to verify itself
                logger.warn("ToDeviceChannel.validateEvent: invalid: from own device");
                return false;
            }
        }

        return VerificationRequest.validateEvent(type, event, client);
    }

    /**
     * @param {MatrixEvent} event the event to get the timestamp of
     * @return {number} the timestamp when the event was sent
     */
    getTimestamp(event) {
        const content = event.getContent();
        return content && content.timestamp;
    }

    /**
     * Changes the state of the channel, request, and verifier in response to a key verification event.
     * @param {MatrixEvent} event to handle
     * @param {VerificationRequest} request the request to forward handling to
     * @param {bool} isLiveEvent whether this is an even received through sync or not
     * @returns {Promise} a promise that resolves when any requests as an anwser to the passed-in event are sent.
     */
    async handleEvent(event, request, isLiveEvent) {
        const type = event.getType();
        const content = event.getContent();
        if (type === REQUEST_TYPE || type === START_TYPE) {
            if (!this.transactionId) {
                this.transactionId = content.transaction_id;
            }
            const deviceId = content.from_device;
            // adopt deviceId if not set before and valid
            if (!this._deviceId && this._devices.includes(deviceId)) {
                this._deviceId = deviceId;
            }
            // if no device id or different from addopted one, cancel with sender
            if (!this._deviceId || this._deviceId !== deviceId) {
                // also check that message came from the device we sent the request to earlier on
                // and do send a cancel message to that device
                // (but don't cancel the request for the device we should be talking to)
                const cancelContent =
                    this.completeContent(errorFromEvent(newUnexpectedMessageError()));
                return this._sendToDevices(CANCEL_TYPE, cancelContent, [deviceId]);
            }
        }
        const wasStarted = request.phase === PHASE_STARTED ||
                           request.phase === PHASE_READY;

        await request.handleEvent(event.getType(), event, isLiveEvent, false, false);

        const isStarted = request.phase === PHASE_STARTED ||
                          request.phase === PHASE_READY;

        const isAcceptingEvent = type === START_TYPE || type === READY_TYPE;
        // the request has picked a ready or start event, tell the other devices about it
        if (isAcceptingEvent && !wasStarted && isStarted && this._deviceId) {
            const nonChosenDevices = this._devices.filter(d => d !== this._deviceId);
            if (nonChosenDevices.length) {
                const message = this.completeContent({
                    code: "m.accepted",
                    reason: "Verification request accepted by another device",
                });
                await this._sendToDevices(CANCEL_TYPE, message, nonChosenDevices);
            }
        }
    }

    /**
     * See {InRoomChannel.completedContentFromEvent} why this is needed.
     * @param {MatrixEvent} event the received event
     * @returns {Object} the content object
     */
    completedContentFromEvent(event) {
        return event.getContent();
    }

    /**
     * Add all the fields to content needed for sending it over this channel.
     * This is public so verification methods (SAS uses this) can get the exact
     * content that will be sent independent of the used channel,
     * as they need to calculate the hash of it.
     * @param {string} type the event type
     * @param {object} content the (incomplete) content
     * @returns {object} the complete content, as it will be sent.
     */
    completeContent(type, content) {
        // make a copy
        content = Object.assign({}, content);
        if (this.transactionId) {
            content.transaction_id = this.transactionId;
        }
        if (type === REQUEST_TYPE || type === READY_TYPE || type === START_TYPE) {
            content.from_device = this._client.getDeviceId();
        }
        if (type === REQUEST_TYPE) {
            content.timestamp = Date.now();
        }
        return content;
    }

    /**
     * Send an event over the channel with the content not having gone through `completeContent`.
     * @param {string} type the event type
     * @param {object} uncompletedContent the (incomplete) content
     * @returns {Promise} the promise of the request
     */
    send(type, uncompletedContent = {}) {
        // create transaction id when sending request
        if ((type === REQUEST_TYPE || type === START_TYPE) && !this.transactionId) {
            this.transactionId = ToDeviceChannel.makeTransactionId();
        }
        const content = this.completeContent(type, uncompletedContent);
        return this.sendCompleted(type, content);
    }

    /**
     * Send an event over the channel with the content having gone through `completeContent` already.
     * @param {string} type the event type
     * @param {object} content
     * @returns {Promise} the promise of the request
     */
    async sendCompleted(type, content) {
        let result;
        if (type === REQUEST_TYPE) {
            result = await this._sendToDevices(type, content, this._devices);
        } else {
            result = await this._sendToDevices(type, content, [this._deviceId]);
        }
        // the VerificationRequest state machine requires remote echos of the event
        // the client sends itself, so we fake this for to_device messages
        const remoteEchoEvent = new MatrixEvent({
            sender: this._client.getUserId(),
            content,
            type,
        });
        await this._request.handleEvent(
            type,
            remoteEchoEvent,
            /*isLiveEvent=*/true,
            /*isRemoteEcho=*/true,
            /*isSentByUs=*/true,
        );
        return result;
    }

    _sendToDevices(type, content, devices) {
        if (devices.length) {
            const msgMap = {};
            for (const deviceId of devices) {
                msgMap[deviceId] = content;
            }

            return this._client.sendToDevice(type, {[this.userId]: msgMap});
        } else {
            return Promise.resolve();
        }
    }

    /**
     * Allow Crypto module to create and know the transaction id before the .start event gets sent.
     * @returns {string} the transaction id
     */
    static makeTransactionId() {
        return randomString(32);
    }
}

export class ToDeviceRequests {
    constructor() {
        this._requestsByUserId = new Map();
    }

    getRequest(event) {
        return this.getRequestBySenderAndTxnId(
            event.getSender(),
            ToDeviceChannel.getTransactionId(event),
        );
    }

    getRequestByChannel(channel) {
        return this.getRequestBySenderAndTxnId(channel.userId, channel.transactionId);
    }

    getRequestBySenderAndTxnId(sender, txnId) {
        const requestsByTxnId = this._requestsByUserId.get(sender);
        if (requestsByTxnId) {
            return requestsByTxnId.get(txnId);
        }
    }

    setRequest(event, request) {
        this.setRequestBySenderAndTxnId(
            event.getSender(),
            ToDeviceChannel.getTransactionId(event),
            request,
        );
    }

    setRequestByChannel(channel, request) {
        this.setRequestBySenderAndTxnId(channel.userId, channel.transactionId, request);
    }

    setRequestBySenderAndTxnId(sender, txnId, request) {
        let requestsByTxnId = this._requestsByUserId.get(sender);
        if (!requestsByTxnId) {
            requestsByTxnId = new Map();
            this._requestsByUserId.set(sender, requestsByTxnId);
        }
        requestsByTxnId.set(txnId, request);
    }

    removeRequest(event) {
        const userId = event.getSender();
        const requestsByTxnId = this._requestsByUserId.get(userId);
        if (requestsByTxnId) {
            requestsByTxnId.delete(ToDeviceChannel.getTransactionId(event));
            if (requestsByTxnId.size === 0) {
                this._requestsByUserId.delete(userId);
            }
        }
    }
}
<|endoftext|>


<commit_before>const lasso = require('lasso-string');

function isSpecialComment(element) {
  return /(\s+|)!/.test(element.value[0]);
}

function isSectionTitle(element) {
  /*------------------------------------*\
    #SECTION-TITLE
  \*------------------------------------*/
  return (
    /^[\-]+(\s+|)\*\\$/.test(element.value[0])
    && /^(\s+|)\\\*(\s+|)[\-]+$/.test(element.value.slice(-1)[0])
  );
}

function isEmptyLine(wordList) {
  return (
    wordList
    && wordList.length === 1
    && wordList[0].length === 0
  );
}

function splitByLineBreak(that, element) {
  let lines = [];
  let lineIndex = 0;
  let tabLength = that.getTab(element.depth + 2).length;

  let raw = element.value
    .map(a => a
      .replace(/^(\s+|)\*/g, '')
      .replace(/^(\s+)([^\s])/, '$2')
      .replace(/([^\s])(\s+)$/, '$1')
    )
    .reduce(function (a, b) {
      var o = [a];

      if (Array.isArray(a)) {
        o = a;
      } else if (!a.length) {
        o = [];
      }

      if ('- ' === b.substr(0, 2)) {
        o.push('');
      }

      o.push(b);
      return o;
    });

  // Single line
  if (typeof raw === 'string') {
    raw = [raw];
  }

  raw = raw
    .join(' ')
    .trim()
    .split(' ');

  raw.forEach(function (word, i) {
    let nextWord = raw[i + 1];

    if (!lines[lineIndex]) {
      lines[lineIndex] = [];
    } else if (
      tabLength + lines[lineIndex].join(' ').length + word.length
      > that.lineBreak
    ) {
      lineIndex++;
      lines[lineIndex] = [];
    } else if (!word.length && !isEmptyLine(lines[lineIndex])) {
      // is a list
      if (nextWord !== '-') {
        lineIndex++;
        lines[lineIndex] = [];
      }
      lineIndex++;
      lines[lineIndex] = [];
    }
    lines[lineIndex].push(word);
  });

  element.value = lines.map(a => a.join(' ').replace(/\s+/g, ' '));
}

function formatSectionTitle(that, element) {
  const tab = that.getTab(element.depth);
  let value = element.value.map(function (line, i) {
    let $tab = '';

    if (i > 0) {
      $tab = tab + that.getTab(1);
    }

    line = line.trim();

    return i < element.value.length - 1
      ? $tab + line + '\n'
      : tab + line;
  });
  return '/*' + value.join('') + '*/';
}

function formatSpecialComment(that, element) {
  const tab = that.getTab(element.depth);

  if (element.value.length === 1) {
    return '/*' + element.value.join('\n') + ' */';
  }

  return '/*' + element.value.map(function (line, i) {
    let $tab = '';
    if (i > 0) {
      $tab = tab + that.getTab(1);
    }
    return $tab + line.trim();
  }).join('\n') + '\n' + tab + ' */';
}

function formatDefault(that, element) {
  const tab = that.getTab(element.depth);

  splitByLineBreak(that, element);

  return element.value.length > 1
    ? '/**\n' +

    element.value.map(function (line, i) {
      let $tab = that.getTab(element.depth);
      line = ' * ' + line.trim();
      return $tab + line;
    }).join('\n') +

    '\n' + tab + ' */'
  : '/* ' + element.value.join('') + ' */';
}

function commentBlock(that, element) {
  /*
  Titling support
  http://cssguidelin.es/#titling
  */

  if (isSectionTitle(element)) {
    return formatSectionTitle(that, element);
  } else if (isSpecialComment(element)) {
    return formatSpecialComment(that, element);
  }

  return formatDefault(that, element);
}

module.exports = commentBlock;
<commit_msg>Added example comment<commit_after>const lasso = require('lasso-string');

function isSpecialComment(element) {
  /*!
    Theme Name: casino
    Version: 1.0
    Author: HannesDev
   */
  return /(\s+|)!/.test(element.value[0]);
}

function isSectionTitle(element) {
  /*------------------------------------*\
    #SECTION-TITLE
  \*------------------------------------*/
  return (
    /^[\-]+(\s+|)\*\\$/.test(element.value[0])
    && /^(\s+|)\\\*(\s+|)[\-]+$/.test(element.value.slice(-1)[0])
  );
}

function isEmptyLine(wordList) {
  return (
    wordList
    && wordList.length === 1
    && wordList[0].length === 0
  );
}

function splitByLineBreak(that, element) {
  let lines = [];
  let lineIndex = 0;
  let tabLength = that.getTab(element.depth + 2).length;

  let raw = element.value
    .map(a => a
      .replace(/^(\s+|)\*/g, '')
      .replace(/^(\s+)([^\s])/, '$2')
      .replace(/([^\s])(\s+)$/, '$1')
    )
    .reduce(function (a, b) {
      var o = [a];

      if (Array.isArray(a)) {
        o = a;
      } else if (!a.length) {
        o = [];
      }

      if ('- ' === b.substr(0, 2)) {
        o.push('');
      }

      o.push(b);
      return o;
    });

  // Single line
  if (typeof raw === 'string') {
    raw = [raw];
  }

  raw = raw
    .join(' ')
    .trim()
    .split(' ');

  raw.forEach(function (word, i) {
    let nextWord = raw[i + 1];

    if (!lines[lineIndex]) {
      lines[lineIndex] = [];
    } else if (
      tabLength + lines[lineIndex].join(' ').length + word.length
      > that.lineBreak
    ) {
      lineIndex++;
      lines[lineIndex] = [];
    } else if (!word.length && !isEmptyLine(lines[lineIndex])) {
      // is a list
      if (nextWord !== '-') {
        lineIndex++;
        lines[lineIndex] = [];
      }
      lineIndex++;
      lines[lineIndex] = [];
    }
    lines[lineIndex].push(word);
  });

  element.value = lines.map(a => a.join(' ').replace(/\s+/g, ' '));
}

function formatSectionTitle(that, element) {
  const tab = that.getTab(element.depth);
  let value = element.value.map(function (line, i) {
    let $tab = '';

    if (i > 0) {
      $tab = tab + that.getTab(1);
    }

    line = line.trim();

    return i < element.value.length - 1
      ? $tab + line + '\n'
      : tab + line;
  });
  return '/*' + value.join('') + '*/';
}

function formatSpecialComment(that, element) {
  const tab = that.getTab(element.depth);

  if (element.value.length === 1) {
    return '/*' + element.value.join('\n') + ' */';
  }

  return '/*' + element.value.map(function (line, i) {
    let $tab = '';
    if (i > 0) {
      $tab = tab + that.getTab(1);
    }
    return $tab + line.trim();
  }).join('\n') + '\n' + tab + ' */';
}

function formatDefault(that, element) {
  const tab = that.getTab(element.depth);

  splitByLineBreak(that, element);

  return element.value.length > 1
    ? '/**\n' +

    element.value.map(function (line, i) {
      let $tab = that.getTab(element.depth);
      line = ' * ' + line.trim();
      return $tab + line;
    }).join('\n') +

    '\n' + tab + ' */'
  : '/* ' + element.value.join('') + ' */';
}

function commentBlock(that, element) {
  /*
  Titling support
  http://cssguidelin.es/#titling
  */

  if (isSectionTitle(element)) {
    return formatSectionTitle(that, element);
  } else if (isSpecialComment(element)) {
    return formatSpecialComment(that, element);
  }

  return formatDefault(that, element);
}

module.exports = commentBlock;
<|endoftext|>