<commit_before>'use strict';

import React from 'react';
import PropTypes from 'prop-types';
import _ from 'underscore';
import url from 'url';
import { content_views } from './globals';
import { console, object, Schemas, JWT, layout, DateUtility } from './util';
import { windowHref } from './globals';
import QuickInfoBar from './viz/QuickInfoBar';

var TITLE_PATHNAME_MAP = {
    '/' : {
        'title' : "Welcome"
    },
    '/browse/' : {
        'title' : "Data Browser",
        'subtitle' : "Filter & browse experiments"
    },
    '/search/' : {
        'title' : 'Search',
        'calloutTitle' : function(pathName, context){
            var thisTypeTitle = getSchemaTypeFromSearchContext(context);
            return thisTypeTitle ? for { thisTypeTitle }: null;
        }
    },
    '/health' : {
        'title' : "Health"
    },
    '/indexing_status' : {
        'title' : "Indexing Status"
    },
    '/users/\*' : {
        'title' : function(pathName, context){
            var myDetails = JWT.getUserDetails();
            var myEmail = myDetails && myDetails.email;
            if (myEmail && context && context.email && myEmail === context.email){
                return "My Profile";
            }
            return object.itemUtil.getTitleStringFromContext(context);
        }
    },
    '/planned-submissions' : {
        'title' : function(pathName, context){
            if (context.status === 'error' && context.code && (context.code === 404 || context.code === 403)){
                return 'Forbidden';
            }
            return object.itemUtil.getTitleStringFromContext(context);
        }
    }
};

// Duplicates
TITLE_PATHNAME_MAP['/home'] = TITLE_PATHNAME_MAP['/home/'] = TITLE_PATHNAME_MAP['/'];

export function getSchemaTypeFromSearchContext(context){
    var thisType = _.pluck(_.filter(context.filters || [], function(o){
        if (o.field === 'type' && o.term !== 'Item') return true;
        return false;
    }), 'term')[0] || null;
    if (thisType){
        return Schemas.getTitleForType(thisType);
    }
    return null;
}

export default class PageTitle extends React.PureComponent {

    static isStaticPage(context){
        if (Array.isArray(context['@type'])){
            if (context['@type'][context['@type'].length - 1] === 'Portal' && context['@type'][context['@type'].length - 2] === 'StaticPage'){
                if (context['@type'].indexOf('HomePage') > -1) return false; // Exclude home page
                return true;
            }
        }
        return false;
    }

    static isHomePage(href){
        var currentHrefParts = url.parse(href, false);
        var pathName = currentHrefParts.pathname;
        if (pathName === '/' || pathName === '/home'){
            return true;
        }
        return false;
    }

    static calculateTitles(context, href, schemas = Schemas.get(), isMounted = false){
        var currentPathName = null, currentPathRoot;
        var title;
        var atId = object.atIdFromObject(context);
        var currentHref = isMounted ? windowHref(href) : href;
        var currentHrefParts = url.parse(currentHref);

        if (typeof atId === 'string'){
            currentPathName = url.parse(atId).pathname;
        }
        if (!currentPathName && typeof currentHref === 'string'){
            currentPathName = currentHrefParts.pathname;
        }

        /**** Pre-mapping overrides ****/

        // For Edit, Create, Add titles:
        if (currentHrefParts.hash && currentHrefParts.hash.length > 1 && (object.isAnItem(context) || currentPathName.indexOf('/search/') > -1)){
            if (currentHrefParts.hash === '#!edit'){
                return {
                    'title' : "Editing",
                    'calloutTitle' : object.itemUtil.getTitleStringFromContext(context) // We can only edit on current context, so this should always be correct/relevant context.
                };
            }

            if (currentHrefParts.hash === '#!create') {
                return {
                    'title' : "Creating",
                    'calloutTitle' : Schemas.getItemTypeTitle(context, schemas) // Create is called from current item view.
                };
            }

            if (currentHrefParts.hash === '#!add') {
                return {
                    'title' : "Creating",
                    'calloutTitle' : (currentPathName.indexOf('/search/') > -1 ? getSchemaTypeFromSearchContext(context) : Schemas.getItemTypeTitle(context, schemas) )
                };
            }

        }

        /**** Titles from mapping ****/
        title = TITLE_PATHNAME_MAP[currentPathName] && TITLE_PATHNAME_MAP[currentPathName].title;

        if (!title) {

            var pathRoot = currentPathName.split('/')[1] || null;
            if (typeof pathRoot === 'string' && pathRoot.length > 0){
                currentPathName = '/' + pathRoot + '/*';
                title = TITLE_PATHNAME_MAP[currentPathName] && TITLE_PATHNAME_MAP[currentPathName].title;
            }
        }

        function getProp(prop){
            if (typeof prop === 'string') return prop;
            if (typeof prop === 'function') return prop(currentPathName, context, href);
            return prop;
        }

        if (title){
            return {
                'title' : getProp(title),
                'subtitle' : getProp(TITLE_PATHNAME_MAP[currentPathName].subtitle),
                'calloutTitle' : getProp(TITLE_PATHNAME_MAP[currentPathName].calloutTitle)
            };
        }

        if (PageTitle.isStaticPage(context)){
            return { 'title' : object.itemUtil.getTitleStringFromContext(context) };
        }

        /**** Post-mapping overrides ****/
        if (object.isAnItem(context)){ // If Item

            title = object.itemUtil.getTitleStringFromContext(context);
            var itemTypeTitle = Schemas.getItemTypeTitle(context, schemas);

            // Handle long title strings by Item type
            if (itemTypeTitle === 'Publication'){
                if (context.title && context.short_attribution){
                    return {'title' : itemTypeTitle, 'subtitle' : context.title, 'subtitlePrepend' : { context.short_attribution }, 'subtitleEllipsis' : true };
                }
                return { 'title' : itemTypeTitle, 'subtitle' : title, 'subtitleEllipsis' : true };
            }

            // Don't show Accessions in titles.
            if (object.itemUtil.isDisplayTitleAccession(context, title, true)){

                // But show rest of title if it is in form 'Something - ACCESSION'
                if (typeof context.accession === 'string' && context.accession.length >= 12 && title.indexOf(' - ' + context.accession) > -1){
                    title = title.replace(' - ' + context.accession, '');
                    if (title.length > 0){
                        return { 'title' : itemTypeTitle, 'calloutTitle' : title };
                    }
                }

                return { 'title' : itemTypeTitle };
                // Re-Enable below if want Accessions as Page Subtitles.
                // return { 'title' : itemTypeTitle, 'subtitle' : title };
            } else {
                if (title.indexOf(context['@type'][0] + ' from ') === 0){ // Our title is in form of 'CellCultureDetails from 2018-01-01' or something, lets make it prettier.
                    title = (context.date_created && from <DateUtility.LocalizedTime timestamp={context.date_created} />) || title.replace(context['@type'][0] + ' ', '');
                }
                // Check if long title & no 'typeInfo' text right under it from Item page -- if so: render it _under_ Type title instead of to the right of it.
                var viewForItem = content_views.lookup(context, null);
                var viewReturnsTypeInfo = false;
                try {
                    viewReturnsTypeInfo = !!(viewForItem.prototype && viewForItem.prototype.typeInfo && viewForItem.prototype.typeInfo.call({ 'props' : { context, href, schemas } }).title ) || false;
                } catch (e){
                    viewReturnsTypeInfo = true; // Assume it failed because trying to access "this", which means typeInfo() most likely does & returns something.
                    console.warn(e);
                }
                if (!context.accession && !Schemas.itemTypeHierarchy[context['@type'][0]] && !viewReturnsTypeInfo && typeof title === 'string' && title.length > 20) {
                    return { 'title' : itemTypeTitle, 'subtitle' : title };
                }
                return { 'title' : itemTypeTitle, 'calloutTitle' : title };
            }

        }

        // Fallback-ish stuff.
        title = object.itemUtil.getTitleStringFromContext(context);
        if (!title) title = currentHrefParts.path;

        return { title };
    }

    static getStyles(context, href, mounted, hasToc){
        var style = { 'marginTop' : 0 };
        var gridSize = mounted && layout.responsiveGridState();
        if (!QuickInfoBar.isInvisibleForHref(href)){
            // We're showing QuickInfoBar, lets extend margin top by height of QuickInfoBar (hardcoded in CSS 38px).
            if (mounted && (gridSize === 'xs' || gridSize === 'sm')) {
                // don't do it; but do by default if not mounted (aka serverside) since desktop is more common than mobile for us
            } else {
                style.marginTop += 38;
            }
        }

        if (hasToc && (gridSize === 'md' || gridSize === 'lg' || !mounted)) style.width = '75%';

        return style;
    }

    constructor(props){
        super(props);
        this.componentDidMount = this.componentDidMount.bind(this);
        this.state = { 'mounted' : false };
    }

    componentDidMount(){
        this.setState({ 'mounted' : true });
    }

    render(){
        var { context, href, session } = this.props;

        if (PageTitle.isHomePage(href)){
            return (
                <layout.WindowResizeUpdateTrigger>

                        <HomePageTitleElement {..._.pick(this.props, 'context', 'href')} mounted={this.state.mounted} />

                </layout.WindowResizeUpdateTrigger>
            );
        }

        var { title, subtitle, calloutTitle, subtitlePrepend, subtitleAppend, subtitleEllipsis } = PageTitle.calculateTitles(context, href, (this.props.shemas || Schemas.get()), this.state.mounted);

        if (title) {
            title = { title };
        }

        if (calloutTitle){
            calloutTitle = { calloutTitle };
        }

        if (subtitle){
            subtitle =
{ subtitlePrepend }{ subtitle }{ subtitleAppend }
;
        }

        var hasToc = (
            context && Array.isArray(context['@type'])
            && context['@type'].indexOf('StaticPage') > -1
            && context['table-of-contents']
            && context['table-of-contents'].enabled
        );

        return (
            <layout.WindowResizeUpdateTrigger>

                    <StaticPageBreadcrumbs {...{ context, session, hasToc }} key="breadcrumbs" />
                    <PageTitleElement {... { title, subtitle, context, href, calloutTitle, hasToc } } mounted={this.state.mounted} />

            </layout.WindowResizeUpdateTrigger>
        );
    }

}

/**
 * Used for most page titles.
 *
 * @prop {JSX.Element|string} title - Shown @ top left, 300 font weight.
 * @prop {JSX.Element|string} calloutTitle - Shown @ right of title in similar size, 400 font weight.
 * @prop {JSX.Element|string} subtitle - Shown @ bottom title in small size, 400 font weight.
 * @returns {JSX.Element} br if no title to display or h1 element with appropriate className, style, content.
 */
class PageTitleElement extends React.Component {

    render(){
        var { title, calloutTitle, subtitle, context, href, mounted, hasToc } = this.props;

        return ((title || subtitle) && (

                { title }{ calloutTitle }{ subtitle }

        )) ||
;
    }
}

class HomePageTitleElement extends React.Component {
    render(){
        var { title, calloutTitle, subtitle, context, href, mounted, hasToc } = this.props;

        var style = PageTitle.getStyles(context, href, mounted, hasToc);
        style.marginTop ? style.marginTop -= 3 : null;

        return (

                4D Nucleome Data Portal

A platform to search, visualize, and download nucleomics data.

        );
    }
}

export class StaticPageBreadcrumbs extends React.Component {

    static getAncestors(context){
        if (!context.parent || !context.parent.display_title) return null;
        var list = [];
        var node = context.parent;
        while (node){
            list.push(node);
            node = node.parent;
        }
        list.reverse();
        list.push(context);
        return list;
    }

    constructor(props){
        super(props);
        this.renderCrumb = this.renderCrumb.bind(this);
    }

    renderCrumb(ancestor, index, all){
        var inner;
        if (ancestor['@id'] === this.props.context['@id']){
            inner = null;//ancestor.display_title;
        } else {
            inner = { ancestor.display_title };
        }
        return (

                { index > 0 ?  : null }
                { inner }

        );
    }

    editButton(){
        var { session, context } = this.props;
        if (session && context && Array.isArray(context['@type']) && context['@type'].indexOf('StaticPage') > -1){

            if (Array.isArray(context.actions)){
                var editAction = _.findWhere(context.actions, { 'name' : 'edit' });
                if (editAction && editAction.href){
                    return (

                             Edit

                    );
                }
            }
        }
        return null;
    }

    render(){
        var { context, hasToc } = this.props;
        var ancestors = StaticPageBreadcrumbs.getAncestors(context);
        var crumbs = ancestors && _.map(ancestors, this.renderCrumb);
        return  (

                { crumbs }
                { this.editButton() }

        );
    }
}

<commit_msg>PageTitle resize fix<commit_after>'use strict';

import React from 'react';
import PropTypes from 'prop-types';
import _ from 'underscore';
import url from 'url';
import { content_views } from './globals';
import { console, object, Schemas, JWT, layout, DateUtility } from './util';
import { windowHref } from './globals';
import QuickInfoBar from './viz/QuickInfoBar';

var TITLE_PATHNAME_MAP = {
    '/' : {
        'title' : "Welcome"
    },
    '/browse/' : {
        'title' : "Data Browser",
        'subtitle' : "Filter & browse experiments"
    },
    '/search/' : {
        'title' : 'Search',
        'calloutTitle' : function(pathName, context){
            var thisTypeTitle = getSchemaTypeFromSearchContext(context);
            return thisTypeTitle ? for { thisTypeTitle }: null;
        }
    },
    '/health' : {
        'title' : "Health"
    },
    '/indexing_status' : {
        'title' : "Indexing Status"
    },
    '/users/\*' : {
        'title' : function(pathName, context){
            var myDetails = JWT.getUserDetails();
            var myEmail = myDetails && myDetails.email;
            if (myEmail && context && context.email && myEmail === context.email){
                return "My Profile";
            }
            return object.itemUtil.getTitleStringFromContext(context);
        }
    },
    '/planned-submissions' : {
        'title' : function(pathName, context){
            if (context.status === 'error' && context.code && (context.code === 404 || context.code === 403)){
                return 'Forbidden';
            }
            return object.itemUtil.getTitleStringFromContext(context);
        }
    }
};

// Duplicates
TITLE_PATHNAME_MAP['/home'] = TITLE_PATHNAME_MAP['/home/'] = TITLE_PATHNAME_MAP['/'];

export function getSchemaTypeFromSearchContext(context){
    var thisType = _.pluck(_.filter(context.filters || [], function(o){
        if (o.field === 'type' && o.term !== 'Item') return true;
        return false;
    }), 'term')[0] || null;
    if (thisType){
        return Schemas.getTitleForType(thisType);
    }
    return null;
}

export default class PageTitle extends React.PureComponent {

    static isStaticPage(context){
        if (Array.isArray(context['@type'])){
            if (context['@type'][context['@type'].length - 1] === 'Portal' && context['@type'][context['@type'].length - 2] === 'StaticPage'){
                if (context['@type'].indexOf('HomePage') > -1) return false; // Exclude home page
                return true;
            }
        }
        return false;
    }

    static isHomePage(href){
        var currentHrefParts = url.parse(href, false);
        var pathName = currentHrefParts.pathname;
        if (pathName === '/' || pathName === '/home'){
            return true;
        }
        return false;
    }

    static calculateTitles(context, href, schemas = Schemas.get(), isMounted = false){
        var currentPathName = null, currentPathRoot;
        var title;
        var atId = object.atIdFromObject(context);
        var currentHref = isMounted ? windowHref(href) : href;
        var currentHrefParts = url.parse(currentHref);

        if (typeof atId === 'string'){
            currentPathName = url.parse(atId).pathname;
        }
        if (!currentPathName && typeof currentHref === 'string'){
            currentPathName = currentHrefParts.pathname;
        }

        /**** Pre-mapping overrides ****/

        // For Edit, Create, Add titles:
        if (currentHrefParts.hash && currentHrefParts.hash.length > 1 && (object.isAnItem(context) || currentPathName.indexOf('/search/') > -1)){
            if (currentHrefParts.hash === '#!edit'){
                return {
                    'title' : "Editing",
                    'calloutTitle' : object.itemUtil.getTitleStringFromContext(context) // We can only edit on current context, so this should always be correct/relevant context.
                };
            }

            if (currentHrefParts.hash === '#!create') {
                return {
                    'title' : "Creating",
                    'calloutTitle' : Schemas.getItemTypeTitle(context, schemas) // Create is called from current item view.
                };
            }

            if (currentHrefParts.hash === '#!add') {
                return {
                    'title' : "Creating",
                    'calloutTitle' : (currentPathName.indexOf('/search/') > -1 ? getSchemaTypeFromSearchContext(context) : Schemas.getItemTypeTitle(context, schemas) )
                };
            }

        }

        /**** Titles from mapping ****/
        title = TITLE_PATHNAME_MAP[currentPathName] && TITLE_PATHNAME_MAP[currentPathName].title;

        if (!title) {

            var pathRoot = currentPathName.split('/')[1] || null;
            if (typeof pathRoot === 'string' && pathRoot.length > 0){
                currentPathName = '/' + pathRoot + '/*';
                title = TITLE_PATHNAME_MAP[currentPathName] && TITLE_PATHNAME_MAP[currentPathName].title;
            }
        }

        function getProp(prop){
            if (typeof prop === 'string') return prop;
            if (typeof prop === 'function') return prop(currentPathName, context, href);
            return prop;
        }

        if (title){
            return {
                'title' : getProp(title),
                'subtitle' : getProp(TITLE_PATHNAME_MAP[currentPathName].subtitle),
                'calloutTitle' : getProp(TITLE_PATHNAME_MAP[currentPathName].calloutTitle)
            };
        }

        if (PageTitle.isStaticPage(context)){
            return { 'title' : object.itemUtil.getTitleStringFromContext(context) };
        }

        /**** Post-mapping overrides ****/
        if (object.isAnItem(context)){ // If Item

            title = object.itemUtil.getTitleStringFromContext(context);
            var itemTypeTitle = Schemas.getItemTypeTitle(context, schemas);

            // Handle long title strings by Item type
            if (itemTypeTitle === 'Publication'){
                if (context.title && context.short_attribution){
                    return {'title' : itemTypeTitle, 'subtitle' : context.title, 'subtitlePrepend' : { context.short_attribution }, 'subtitleEllipsis' : true };
                }
                return { 'title' : itemTypeTitle, 'subtitle' : title, 'subtitleEllipsis' : true };
            }

            // Don't show Accessions in titles.
            if (object.itemUtil.isDisplayTitleAccession(context, title, true)){

                // But show rest of title if it is in form 'Something - ACCESSION'
                if (typeof context.accession === 'string' && context.accession.length >= 12 && title.indexOf(' - ' + context.accession) > -1){
                    title = title.replace(' - ' + context.accession, '');
                    if (title.length > 0){
                        return { 'title' : itemTypeTitle, 'calloutTitle' : title };
                    }
                }

                return { 'title' : itemTypeTitle };
                // Re-Enable below if want Accessions as Page Subtitles.
                // return { 'title' : itemTypeTitle, 'subtitle' : title };
            } else {
                if (title.indexOf(context['@type'][0] + ' from ') === 0){ // Our title is in form of 'CellCultureDetails from 2018-01-01' or something, lets make it prettier.
                    title = (context.date_created && from <DateUtility.LocalizedTime timestamp={context.date_created} />) || title.replace(context['@type'][0] + ' ', '');
                }
                // Check if long title & no 'typeInfo' text right under it from Item page -- if so: render it _under_ Type title instead of to the right of it.
                var viewForItem = content_views.lookup(context, null);
                var viewReturnsTypeInfo = false;
                try {
                    viewReturnsTypeInfo = !!(viewForItem.prototype && viewForItem.prototype.typeInfo && viewForItem.prototype.typeInfo.call({ 'props' : { context, href, schemas } }).title ) || false;
                } catch (e){
                    viewReturnsTypeInfo = true; // Assume it failed because trying to access "this", which means typeInfo() most likely does & returns something.
                    console.warn(e);
                }
                if (!context.accession && !Schemas.itemTypeHierarchy[context['@type'][0]] && !viewReturnsTypeInfo && typeof title === 'string' && title.length > 20) {
                    return { 'title' : itemTypeTitle, 'subtitle' : title };
                }
                return { 'title' : itemTypeTitle, 'calloutTitle' : title };
            }

        }

        // Fallback-ish stuff.
        title = object.itemUtil.getTitleStringFromContext(context);
        if (!title) title = currentHrefParts.path;

        return { title };
    }

    static getStyles(context, href, mounted, hasToc){
        var style = { 'marginTop' : 0 };
        var gridSize = mounted && layout.responsiveGridState();
        if (!QuickInfoBar.isInvisibleForHref(href)){
            // We're showing QuickInfoBar, lets extend margin top by height of QuickInfoBar (hardcoded in CSS 38px).
            if (mounted && (gridSize === 'xs' || gridSize === 'sm')) {
                // don't do it; but do by default if not mounted (aka serverside) since desktop is more common than mobile for us
            } else {
                style.marginTop += 38;
            }
        }

        if (hasToc && (gridSize === 'md' || gridSize === 'lg' || !mounted)) style.width = '75%';

        return style;
    }

    constructor(props){
        super(props);
        this.componentDidMount = this.componentDidMount.bind(this);
        this.state = { 'mounted' : false };
    }

    componentDidMount(){
        this.setState({ 'mounted' : true });
    }

    render(){
        var { context, href, session } = this.props;

        if (PageTitle.isHomePage(href)){
            return (

                    <layout.WindowResizeUpdateTrigger>
                        <HomePageTitleElement {..._.pick(this.props, 'context', 'href')} mounted={this.state.mounted} />
                    </layout.WindowResizeUpdateTrigger>

            );
        }

        var { title, subtitle, calloutTitle, subtitlePrepend, subtitleAppend, subtitleEllipsis } = PageTitle.calculateTitles(context, href, (this.props.shemas || Schemas.get()), this.state.mounted);

        if (title) {
            title = { title };
        }

        if (calloutTitle){
            calloutTitle = { calloutTitle };
        }

        if (subtitle){
            subtitle =
{ subtitlePrepend }{ subtitle }{ subtitleAppend }
;
        }

        var hasToc = (
            context && Array.isArray(context['@type'])
            && context['@type'].indexOf('StaticPage') > -1
            && context['table-of-contents']
            && context['table-of-contents'].enabled
        );

        return (

                <StaticPageBreadcrumbs {...{ context, session, hasToc }} key="breadcrumbs" />
                <layout.WindowResizeUpdateTrigger>
                    <PageTitleElement {... { title, subtitle, context, href, calloutTitle, hasToc } } mounted={this.state.mounted} />
                </layout.WindowResizeUpdateTrigger>

        );
    }

}

/**
 * Used for most page titles.
 *
 * @prop {JSX.Element|string} title - Shown @ top left, 300 font weight.
 * @prop {JSX.Element|string} calloutTitle - Shown @ right of title in similar size, 400 font weight.
 * @prop {JSX.Element|string} subtitle - Shown @ bottom title in small size, 400 font weight.
 * @returns {JSX.Element} br if no title to display or h1 element with appropriate className, style, content.
 */
class PageTitleElement extends React.Component {

    render(){
        var { title, calloutTitle, subtitle, context, href, mounted, hasToc } = this.props;

        return ((title || subtitle) && (

                { title }{ calloutTitle }{ subtitle }

        )) ||
;
    }
}

class HomePageTitleElement extends React.Component {
    render(){
        var { title, calloutTitle, subtitle, context, href, mounted, hasToc } = this.props;

        var style = PageTitle.getStyles(context, href, mounted, hasToc);
        style.marginTop ? style.marginTop -= 3 : null;

        return (

                4D Nucleome Data Portal

A platform to search, visualize, and download nucleomics data.

        );
    }
}

export class StaticPageBreadcrumbs extends React.Component {

    static getAncestors(context){
        if (!context.parent || !context.parent.display_title) return null;
        var list = [];
        var node = context.parent;
        while (node){
            list.push(node);
            node = node.parent;
        }
        list.reverse();
        list.push(context);
        return list;
    }

    constructor(props){
        super(props);
        this.renderCrumb = this.renderCrumb.bind(this);
    }

    renderCrumb(ancestor, index, all){
        var inner;
        if (ancestor['@id'] === this.props.context['@id']){
            inner = null;//ancestor.display_title;
        } else {
            inner = { ancestor.display_title };
        }
        return (

                { index > 0 ?  : null }
                { inner }

        );
    }

    editButton(){
        var { session, context } = this.props;
        if (session && context && Array.isArray(context['@type']) && context['@type'].indexOf('StaticPage') > -1){

            if (Array.isArray(context.actions)){
                var editAction = _.findWhere(context.actions, { 'name' : 'edit' });
                if (editAction && editAction.href){
                    return (

                             Edit

                    );
                }
            }
        }
        return null;
    }

    render(){
        var { context, hasToc } = this.props;
        var ancestors = StaticPageBreadcrumbs.getAncestors(context);
        var crumbs = ancestors && _.map(ancestors, this.renderCrumb);
        return  (

                { crumbs }
                { this.editButton() }

        );
    }
}

<|endoftext|>


<commit_before>'use strict';

angular.module('fusioApp.routes', ['ngRoute', 'ui.bootstrap'])

.config(['$routeProvider', function($routeProvider) {
	$routeProvider.when('/routes', {
		templateUrl: 'app/routes/index.html',
		controller: 'RoutesCtrl'
	});
}])

.controller('RoutesCtrl', ['$scope', '$http', '$modal', '$routeParams', function($scope, $http, $modal){

	$scope.response = null;
	$scope.search = '';

	$scope.load = function(){
		var search = encodeURIComponent($scope.search);

		$http.get(fusio_url + 'backend/routes?search=' + search).success(function(data){
			$scope.totalResults = data.totalResults;
			$scope.startIndex = 0;
			$scope.routes = data.entry;
		});
	};

	$scope.pageChanged = function(){
		var startIndex = ($scope.startIndex - 1) * 16;
		var search = encodeURIComponent($scope.search);

		$http.get(fusio_url + 'backend/routes?startIndex=' + startIndex + '&search=' + search).success(function(data){
			$scope.totalResults = data.totalResults;
			$scope.routes = data.entry;
		});
	};

	$scope.doSearch = function(search){
		var search = encodeURIComponent(search);
		$http.get(fusio_url + 'backend/routes?search=' + search).success(function(data){
			$scope.totalResults = data.totalResults;
			$scope.startIndex = 0;
			$scope.routes = data.entry;
		});
	};

	$scope.openCreateDialog = function(){
		var modalInstance = $modal.open({
			size: 'lg',
			backdrop: 'static',
			templateUrl: 'app/routes/create.html',
			controller: 'RoutesCreateCtrl'
		});

		modalInstance.result.then(function(response){
			$scope.response = response;
			$scope.load();
		}, function(){
		});
	};

	$scope.openUpdateDialog = function(route){
		var modalInstance = $modal.open({
			size: 'lg',
			backdrop: 'static',
			templateUrl: 'app/routes/update.html',
			controller: 'RoutesUpdateCtrl',
			resolve: {
				route: function(){
					return route;
				}
			}
		});

		modalInstance.result.then(function(response){
			$scope.response = response;
			$scope.load();
		}, function(){
		});
	};

	$scope.openDeleteDialog = function(route){
		var modalInstance = $modal.open({
			size: 'lg',
			backdrop: 'static',
			templateUrl: 'app/routes/delete.html',
			controller: 'RoutesDeleteCtrl',
			resolve: {
				route: function(){
					return route;
				}
			}
		});

		modalInstance.result.then(function(response){
			$scope.response = response;
			$scope.load();
		}, function(){
		});
	};

	$scope.closeResponse = function(){
		$scope.response = null;
	};

	$scope.load();

}])

.controller('RoutesCreateCtrl', ['$scope', '$http', '$modalInstance', function($scope, $http, $modalInstance){

	$scope.route = {
		methods: 'GET|POST|PUT|DELETE',
		path: '',
		config: []
	};

	$scope.methods = ['GET', 'POST', 'PUT', 'DELETE'];
	$scope.schemas = [];
	$scope.actions = [];

	$scope.statuuus = [{
		key: 4,
		value: "Development"
	},{
		key: 1,
		value: "Production"
	},{
		key: 2,
		value: "Deprecated"
	},{
		key: 3,
		value: "Closed"
	}];

	$scope.create = function(route){
		$http.post(fusio_url + 'backend/routes', route)
			.success(function(data){
				$scope.response = data;
				if (data.success === true) {
					$modalInstance.close(data);
				}
			})
			.error(function(data){
				$scope.response = data;
			});
	};

	$http.get(fusio_url + 'backend/action')
		.success(function(data){
			$scope.actions = data.entry;
		});

	$http.get(fusio_url + 'backend/schema')
		.success(function(data){
			$scope.schemas = data.entry;
		});

	$scope.close = function(){
		$modalInstance.dismiss('cancel');
	};

	$scope.closeResponse = function(){
		$scope.response = null;
	};

	$scope.addVersion = function(){
		var versions = [];
		for (var i = 0; i < $scope.route.config.length; i++) {
			var version = $scope.route.config[i];
			version.active = false;

			versions.push(version);
		}

		versions.push($scope.newVersion());

		$scope.route.config = versions;
	};

	$scope.removeVersion = function(version){
		var versions = [];
		for (var i = 0; i < $scope.route.config.length; i++) {
			if ($scope.route.config[i].name != version.name) {
				versions.push($scope.route.config[i]);
			}
		}
		$scope.route.config = versions;
	};

	$scope.newVersion = function(){
		var version = {
			name: "" + ($scope.getLatestVersion() + 1),
			active: true,
			status: 4,
			methods: []
		};

		for(var i = 0; i < $scope.methods.length; i++) {
			version.methods.push({
				name: $scope.methods[i]
			});
		}

		return version;
	};

	$scope.getLatestVersion = function(){
		var version = 0;
		for (var i = 0; i < $scope.route.config.length; i++) {
			var ver = parseInt($scope.route.config[i].name);
			if (ver > version) {
				version = ver;
			}
		}
		return version;
	};

	$scope.addVersion();

}])

.controller('RoutesUpdateCtrl', ['$scope', '$http', '$modalInstance', 'route', function($scope, $http, $modalInstance, route){

	$scope.route = route;

	$scope.methods = ['GET', 'POST', 'PUT', 'DELETE'];
	$scope.schemas = [];
	$scope.actions = [];

	$scope.statuuus = [{
		key: 4,
		value: "Development"
	},{
		key: 1,
		value: "Production"
	},{
		key: 2,
		value: "Deprecated"
	},{
		key: 3,
		value: "Closed"
	}];

	$scope.update = function(route){
		$http.put(fusio_url + 'backend/routes/' + route.id, route)
			.success(function(data){
				$scope.response = data;
				if (data.success === true) {
					$modalInstance.close(data);
				}
			})
			.error(function(data){
				$scope.response = data;
			});
	};

	$http.get(fusio_url + 'backend/routes/' + route.id)
		.success(function(data){
			$scope.route = data;
		});

	$http.get(fusio_url + 'backend/action')
		.success(function(data){
			$scope.actions = data.entry;
		});

	$http.get(fusio_url + 'backend/schema')
		.success(function(data){
			$scope.schemas = data.entry;
		});

	$scope.close = function(){
		$modalInstance.dismiss('cancel');
	};

	$scope.closeResponse = function(){
		$scope.response = null;
	};

	$scope.addVersion = function(){
		var versions = [];
		for (var i = 0; i < $scope.route.config.length; i++) {
			var version = $scope.route.config[i];
			version.active = false;

			versions.push(version);
		}

		versions.push($scope.newVersion());

		$scope.route.config = versions;
	};

	$scope.removeVersion = function(version){
		var versions = [];
		for (var i = 0; i < $scope.route.config.length; i++) {
			if ($scope.route.config[i].name != version.name) {
				versions.push($scope.route.config[i]);
			}
		}
		$scope.route.config = versions;
	};

	$scope.newVersion = function(){
		var version = {
			name: "" + ($scope.getLatestVersion() + 1),
			active: true,
			status: 4,
			methods: []
		};

		for(var i = 0; i < $scope.methods.length; i++) {
			version.methods.push({
				name: $scope.methods[i]
			});
		}

		return version;
	};

	$scope.getLatestVersion = function(){
		var version = 0;
		for (var i = 0; i < $scope.route.config.length; i++) {
			var ver = parseInt($scope.route.config[i].name);
			if (ver > version) {
				version = ver;
			}
		}
		return version;
	};

}])

.controller('RoutesDeleteCtrl', ['$scope', '$http', '$modalInstance', 'route', function($scope, $http, $modalInstance, route){

	$scope.route = route;

	$scope.delete = function(route){
		$http.delete(fusio_url + 'backend/routes/' + route.id)
			.success(function(data){
				$scope.response = data;
				if (data.success === true) {
					$modalInstance.close(data);
				}
			})
			.error(function(data){
				$scope.response = data;
			});
	};

	$scope.close = function(){
		$modalInstance.dismiss('cancel');
	};

	$scope.closeResponse = function(){
		$scope.response = null;
	};

}]);

<commit_msg>check whether version can be deleted<commit_after>'use strict';

angular.module('fusioApp.routes', ['ngRoute', 'ui.bootstrap'])

.config(['$routeProvider', function($routeProvider) {
	$routeProvider.when('/routes', {
		templateUrl: 'app/routes/index.html',
		controller: 'RoutesCtrl'
	});
}])

.controller('RoutesCtrl', ['$scope', '$http', '$modal', '$routeParams', function($scope, $http, $modal){

	$scope.response = null;
	$scope.search = '';

	$scope.load = function(){
		var search = encodeURIComponent($scope.search);

		$http.get(fusio_url + 'backend/routes?search=' + search).success(function(data){
			$scope.totalResults = data.totalResults;
			$scope.startIndex = 0;
			$scope.routes = data.entry;
		});
	};

	$scope.pageChanged = function(){
		var startIndex = ($scope.startIndex - 1) * 16;
		var search = encodeURIComponent($scope.search);

		$http.get(fusio_url + 'backend/routes?startIndex=' + startIndex + '&search=' + search).success(function(data){
			$scope.totalResults = data.totalResults;
			$scope.routes = data.entry;
		});
	};

	$scope.doSearch = function(search){
		var search = encodeURIComponent(search);
		$http.get(fusio_url + 'backend/routes?search=' + search).success(function(data){
			$scope.totalResults = data.totalResults;
			$scope.startIndex = 0;
			$scope.routes = data.entry;
		});
	};

	$scope.openCreateDialog = function(){
		var modalInstance = $modal.open({
			size: 'lg',
			backdrop: 'static',
			templateUrl: 'app/routes/create.html',
			controller: 'RoutesCreateCtrl'
		});

		modalInstance.result.then(function(response){
			$scope.response = response;
			$scope.load();
		}, function(){
		});
	};

	$scope.openUpdateDialog = function(route){
		var modalInstance = $modal.open({
			size: 'lg',
			backdrop: 'static',
			templateUrl: 'app/routes/update.html',
			controller: 'RoutesUpdateCtrl',
			resolve: {
				route: function(){
					return route;
				}
			}
		});

		modalInstance.result.then(function(response){
			$scope.response = response;
			$scope.load();
		}, function(){
		});
	};

	$scope.openDeleteDialog = function(route){
		var modalInstance = $modal.open({
			size: 'lg',
			backdrop: 'static',
			templateUrl: 'app/routes/delete.html',
			controller: 'RoutesDeleteCtrl',
			resolve: {
				route: function(){
					return route;
				}
			}
		});

		modalInstance.result.then(function(response){
			$scope.response = response;
			$scope.load();
		}, function(){
		});
	};

	$scope.closeResponse = function(){
		$scope.response = null;
	};

	$scope.load();

}])

.controller('RoutesCreateCtrl', ['$scope', '$http', '$modalInstance', function($scope, $http, $modalInstance){

	$scope.route = {
		methods: 'GET|POST|PUT|DELETE',
		path: '',
		config: []
	};

	$scope.methods = ['GET', 'POST', 'PUT', 'DELETE'];
	$scope.schemas = [];
	$scope.actions = [];

	$scope.statuuus = [{
		key: 4,
		value: "Development"
	},{
		key: 1,
		value: "Production"
	},{
		key: 2,
		value: "Deprecated"
	},{
		key: 3,
		value: "Closed"
	}];

	$scope.create = function(route){
		$http.post(fusio_url + 'backend/routes', route)
			.success(function(data){
				$scope.response = data;
				if (data.success === true) {
					$modalInstance.close(data);
				}
			})
			.error(function(data){
				$scope.response = data;
			});
	};

	$http.get(fusio_url + 'backend/action')
		.success(function(data){
			$scope.actions = data.entry;
		});

	$http.get(fusio_url + 'backend/schema')
		.success(function(data){
			$scope.schemas = data.entry;
		});

	$scope.close = function(){
		$modalInstance.dismiss('cancel');
	};

	$scope.closeResponse = function(){
		$scope.response = null;
	};

	$scope.addVersion = function(){
		var versions = [];
		for (var i = 0; i < $scope.route.config.length; i++) {
			var version = $scope.route.config[i];
			version.active = false;

			versions.push(version);
		}

		versions.push($scope.newVersion());

		$scope.route.config = versions;
	};

	$scope.removeVersion = function(version){
		if (version.status != 4 || $scope.route.config.length == 1) {
			return;
		}

		var versions = [];
		for (var i = 0; i < $scope.route.config.length; i++) {
			if ($scope.route.config[i].name != version.name) {
				versions.push($scope.route.config[i]);
			}
		}
		$scope.route.config = versions;
	};

	$scope.newVersion = function(){
		var version = {
			name: "" + ($scope.getLatestVersion() + 1),
			active: true,
			status: 4,
			methods: []
		};

		for(var i = 0; i < $scope.methods.length; i++) {
			version.methods.push({
				name: $scope.methods[i]
			});
		}

		return version;
	};

	$scope.getLatestVersion = function(){
		var version = 0;
		for (var i = 0; i < $scope.route.config.length; i++) {
			var ver = parseInt($scope.route.config[i].name);
			if (ver > version) {
				version = ver;
			}
		}
		return version;
	};

	$scope.addVersion();

}])

.controller('RoutesUpdateCtrl', ['$scope', '$http', '$modalInstance', 'route', function($scope, $http, $modalInstance, route){

	$scope.route = route;

	$scope.methods = ['GET', 'POST', 'PUT', 'DELETE'];
	$scope.schemas = [];
	$scope.actions = [];

	$scope.statuuus = [{
		key: 4,
		value: "Development"
	},{
		key: 1,
		value: "Production"
	},{
		key: 2,
		value: "Deprecated"
	},{
		key: 3,
		value: "Closed"
	}];

	$scope.update = function(route){
		$http.put(fusio_url + 'backend/routes/' + route.id, route)
			.success(function(data){
				$scope.response = data;
				if (data.success === true) {
					$modalInstance.close(data);
				}
			})
			.error(function(data){
				$scope.response = data;
			});
	};

	$http.get(fusio_url + 'backend/routes/' + route.id)
		.success(function(data){
			$scope.route = data;
		});

	$http.get(fusio_url + 'backend/action')
		.success(function(data){
			$scope.actions = data.entry;
		});

	$http.get(fusio_url + 'backend/schema')
		.success(function(data){
			$scope.schemas = data.entry;
		});

	$scope.close = function(){
		$modalInstance.dismiss('cancel');
	};

	$scope.closeResponse = function(){
		$scope.response = null;
	};

	$scope.addVersion = function(){
		var versions = [];
		for (var i = 0; i < $scope.route.config.length; i++) {
			var version = $scope.route.config[i];
			version.active = false;

			versions.push(version);
		}

		versions.push($scope.newVersion());

		$scope.route.config = versions;
	};

	$scope.removeVersion = function(version){
		if (version.status != 4 || $scope.route.config.length == 1) {
			return;
		}

		var versions = [];
		for (var i = 0; i < $scope.route.config.length; i++) {
			if ($scope.route.config[i].name != version.name) {
				versions.push($scope.route.config[i]);
			}
		}
		$scope.route.config = versions;
	};

	$scope.newVersion = function(){
		var version = {
			name: "" + ($scope.getLatestVersion() + 1),
			active: true,
			status: 4,
			methods: []
		};

		for(var i = 0; i < $scope.methods.length; i++) {
			version.methods.push({
				name: $scope.methods[i]
			});
		}

		return version;
	};

	$scope.getLatestVersion = function(){
		var version = 0;
		for (var i = 0; i < $scope.route.config.length; i++) {
			var ver = parseInt($scope.route.config[i].name);
			if (ver > version) {
				version = ver;
			}
		}
		return version;
	};

}])

.controller('RoutesDeleteCtrl', ['$scope', '$http', '$modalInstance', 'route', function($scope, $http, $modalInstance, route){

	$scope.route = route;

	$scope.delete = function(route){
		$http.delete(fusio_url + 'backend/routes/' + route.id)
			.success(function(data){
				$scope.response = data;
				if (data.success === true) {
					$modalInstance.close(data);
				}
			})
			.error(function(data){
				$scope.response = data;
			});
	};

	$scope.close = function(){
		$modalInstance.dismiss('cancel');
	};

	$scope.closeResponse = function(){
		$scope.response = null;
	};

}]);

<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import color from 'color';
import _ from 'underscore';
import url from 'url';
import { svgIcon } from '../libs/svg-icons';
import * as globals from './globals';
import { BrowserSelector } from './objectutils';
import { BatchDownload, FacetList, TextFilter } from './search';

class GroupMoreButton extends React.Component {
    constructor() {
        super();

        // Bind this to non-React components.
        this.localHandleClick = this.localHandleClick.bind(this);
    }

    localHandleClick() {
        this.props.handleClick(this.props.id);
    }

    render() {
        return <button className="group-more-cell__button" onClick={this.localHandleClick}>{this.props.displayText}</button>;
    }
}

GroupMoreButton.propTypes = {
    id: PropTypes.string, // ID for the handleClick function to know which control was clicked
    handleClick: PropTypes.func.isRequired, // Call this parent function to handle the click in the button
    displayText: PropTypes.string, // Text to display in the button while closed
};

GroupMoreButton.defaultProps = {
    id: '',
    displayText: '',
};

class AuditMatrix extends React.Component {
    static generateYGroupOpen(matrix) {
        // Make a state for each of the Y groups (each Y group currently shows a biosample type).
        // To do that, we have to get each of the bucket keys, which will be the keys into the
        // object that keeps track of whether the group shows all or not. If a group has fewer than
        // the maximum number of items to show a See More button, it doesn't get included in the
        // state.
        const primaryYGrouping = matrix.y.group_by[0];
        const secondaryYGrouping = matrix.y.group_by[1];
        const yLimit = matrix.y.limit;
        const yGroups = matrix.y[primaryYGrouping].buckets;
        const yGroupOpen = {};
        yGroups.forEach((group) => {
            if (group[secondaryYGrouping].buckets.length > yLimit) {
                yGroupOpen[group.key] = false;
            }
        });
        return yGroupOpen;
    }

    constructor(props) {
        super(props);

        // Set initial React state.
        const yGroupOpen = AuditMatrix.generateYGroupOpen(this.props.context.matrix);
        this.state = {
            yGroupOpen,
            allYGroupsOpen: false,
        };

        // Bind this to non-React methods.
        this.onChange = this.onChange.bind(this);
        this.onFilter = this.onFilter.bind(this);
        this.updateElement = this.updateElement.bind(this);
        this.handleClick = this.handleClick.bind(this);
        this.handleSeeAllClick = this.handleSeeAllClick.bind(this);
    }

    componentWillReceiveProps(nextProps) {
        // This callback makes possible updating the See More buttons when the user clicks a facet,
        // which could cause these buttons to not be needed. This resets all the buttons to the See
        // More state.
        const yGroupOpen = AuditMatrix.generateYGroupOpen(nextProps.context.matrix);
        this.setState({
            yGroupOpen,
            allYGroupsOpen: false,
        });
    }

    onChange(href) {
        this.context.navigate(href);
    }

    onFilter(e) {
        const search = e.currentTarget.getAttribute('href');
        this.context.navigate(search);
        e.stopPropagation();
        e.preventDefault();
    }

    // Called when the Visualize button dropdown menu gets opened or closed. `dropdownEl` is the
    // DOM node for the dropdown menu. This sets inline CSS to set the height of the wrapper

    // to make room for the dropdown.
    updateElement(dropdownEl) {
        const wrapperEl = this.hubscontrols;
        const dropdownHeight = dropdownEl.clientHeight;
        if (dropdownHeight === 0) {
            // The dropdown menu has closed
            wrapperEl.style.height = 'auto';
        } else {
            // The menu has dropped down
            wrapperEl.style.height = `${wrapperEl.clientHeight}${dropdownHeight}px`;
        }
    }

    // Handle a click in a See More link within the matrix (not for the facets)
    handleClick(groupKey) {
        const groupOpen = _.clone(this.state.yGroupOpen);
        groupOpen[groupKey] = !groupOpen[groupKey];
        this.setState({ yGroupOpen: groupOpen });
    }

    handleSeeAllClick() {
        this.setState((prevState) => {
            const newState = {};

            // If the See All button wasn't open (meaning this function was called because the user
            // wanted to see all), forget all the individual ones the user had opened so that
            // they're all closed when the user clicks See Fewer.
            if (!prevState.allYGroupsOpen) {
                const groupOpen = {};
                Object.keys(prevState.yGroupOpen).forEach((key) => {
                    groupOpen[key] = false;
                });
                newState.yGroupOpen = groupOpen;
            }

            // Toggle the state of allYGroupsOpen.
            newState.allYGroupsOpen = !prevState.allYGroupsOpen;
            return newState;
        });
    }

    render() {
        const context = this.props.context;
        const matrix = context.matrix;
        const parsedUrl = url.parse(this.context.location_href);
        const matrixBase = parsedUrl.search || '';
        const matrixSearch = matrixBase + (matrixBase ? '&' : '?');
        const notification = context.notification;
        const visualizeLimit = 500;
        if (notification === 'Success' || notification === 'No results found') {
            const xFacets = matrix.x.facets.map(f => _.findWhere(context.facets, { field: f })).filter(f => f);
            let yFacets = matrix.y.facets.map(f => _.findWhere(context.facets, { field: f })).filter(f => f);
            yFacets = yFacets.concat(_.reject(context.facets, f => _.contains(matrix.x.facets, f.field) || _.contains(matrix.y.facets, f.field)));
            const xGrouping = matrix.x.group_by;
            const primaryYGrouping = matrix.y.group_by[0];
            const secondaryYGrouping = matrix.y.group_by[1];
            const xBuckets = matrix.x.buckets;
            const xLimit = matrix.x.limit || xBuckets.length;
            var yGroups = matrix.y[primaryYGrouping].buckets;
            const orderKey = ['audit-ERROR-category', 'audit-NOT_COMPLIANT-category', 'audit-WARNING-category', 'audit-INTERNAL_ACTION-category'];
            var orderIndex = 0;
            var tempYGroups = [];
            while(orderIndex < orderKey.length){
                yGroups.forEach((group) => {
                    if(group.key === orderKey[orderIndex]){
                        tempYGroups.push(group);
                    }
                });
                orderIndex++;
            }
            yGroups = tempYGroups;
            const yGroupFacet = _.findWhere(context.facets, { field: primaryYGrouping });
            const yGroupOptions = yGroupFacet ? yGroupFacet.terms.map(term => term.key) : [];
            yGroupOptions.sort();
            const searchBase = context.matrix.search_base;
            const visualizeDisabled = matrix.doc_count > visualizeLimit;
            const colCount = Math.min(xBuckets.length, xLimit + 1);

            // Get a sorted list of batch hubs keys with case-insensitive sort
            // NOTE: Tim thinks this is overkill as opposed to simple sort()
            let visualizeKeys = [];
            if (context.visualize_batch && Object.keys(context.visualize_batch).length) {
                visualizeKeys = Object.keys(context.visualize_batch).sort((a, b) => {
                    const aLower = a.toLowerCase();
                    const bLower = b.toLowerCase();
                    return (aLower > bLower) ? 1 : ((aLower < bLower) ? -1 : 0);
                });
            }

            // Map view icons to svg icons
            const view2svg = {
                'list-alt': 'search',
                table: 'table',
            };

            // Make an array of colors corresponding to the ordering of biosample_type
            const biosampleTypeColors = ["#cc0700", "#ff8000", "#e0e000"];

            return (

{context.title}

Click or enter search terms to filter the experiments included in the matrix.

                                                <TextFilter filters={context.filters} searchBase={matrixSearch} onChange={this.onChange} />

                                <FacetList
                                    facets={xFacets}
                                    filters={context.filters}
                                    orientation="horizontal"
                                    searchBase={matrixSearch}
                                    onFilter={this.onFilter}
                                />

                                <FacetList facets={yFacets} filters={context.filters} searchBase={matrixSearch} onFilter={this.onFilter} />

                                            {matrix.y.label.toUpperCase()}

                                            {matrix.doc_count ?

                                                    {matrix.x.label.toUpperCase()}

                                            : null}

                                                      {matrix.doc_count} results

                                                        {matrix.doc_count && context.views ? context.views.map(view => {svgIcon(view2svg[view.icon])}) : ''}

                                                    {context.filters.length ?

                                                            Clear Filters

                                                    : null}

                                                {xBuckets.map((xb, i) => {
                                                    if (i < xLimit) {
                                                        const href = `${searchBase}&${xGrouping}=${globals.encodedURIComponent(xb.key)}`;
                                                        return
{xb.key}
	;
                                                    } else if (i === xLimit) {
                                                        const parsed = url.parse(matrixBase, true);
                                                        parsed.query['x.limit'] = null;
                                                        delete parsed.search; // this makes format compose the search string out of the query object
                                                        const unlimitedHref = url.format(parsed);
                                                        return
...and {xBuckets.length - xLimit} more
	;
                                                    }
                                                    return null;
                                                })}

                                            {yGroups.map((group, i) => {
                                                const groupColor = biosampleTypeColors[i];
                                                const seriesColor = color(groupColor);
                                                const parsed = url.parse(matrixBase, true);
                                                parsed.query[primaryYGrouping] = group.key;
                                                parsed.query['y.limit'] = null;
                                                delete parsed.search; // this makes format compose the search string out of the query object
                                                const groupHref = url.format(parsed);
                                                const rows = [

                                                        {group.key}

];
                                                const groupBuckets = group[secondaryYGrouping].buckets;
                                                const yLimit = matrix.y.limit || groupBuckets.length;

                                                // If this group isn't open (noted by
                                                // this.state.yGroupOpen[key]), extract just the
                                                // group rows that are under the display limit.
                                                const groupRows = (this.state.yGroupOpen[group.key] || this.state.allYGroupsOpen) ? groupBuckets : groupBuckets.slice(0, yLimit);
                                                rows.push(...groupRows.map((yb) => {
                                                    const href = `${searchBase}&${secondaryYGrouping}=${globals.encodedURIComponent(yb.key)}`;
                                                    return (

                                                            {yb.key}
                                                            {xBuckets.map((xb, k) => {
                                                                if (k < xLimit) {
                                                                    const value = yb[xGrouping][k];
                                                                    const cellColor = seriesColor.clone();
                                                                    // scale color between white and the series color
                                                                    cellColor.lightness(cellColor.lightness() + ((1 - (value / matrix.max_cell_doc_count)) * (100 - cellColor.lightness())));
                                                                    const textColor = cellColor.luminosity() > 0.5 ? '#000' : '#fff';
                                                                    const cellHref = `${searchBase}&${secondaryYGrouping}=${globals.encodedURIComponent(yb.key)}&${xGrouping}=${globals.encodedURIComponent(xb.key)}`;
                                                                    const title = `${yb.key} / ${xb.key}: ${value}`;
                                                                    return (

                                                                            {value ? {value} : null}

                                                                    );
                                                                }
                                                                return null;
                                                            })}
                                                            {xBuckets.length > xLimit && }

                                                    );
                                                }));
                                                if (groupBuckets.length > yLimit && !this.state.allYGroupsOpen) {
                                                    rows.push(

                                                                <GroupMoreButton
                                                                    id={group.key}
                                                                    handleClick={this.handleClick}
                                                                    displayText={this.state.yGroupOpen[group.key] ? '- See fewer' : `+ See ${groupBuckets.length - yLimit} more…`}
                                                                />

                                                            {_.range(colCount - 1).map(n => )}

                                                    );
                                                }
                                                return rows;
                                            })}

                                            {/* Display the See Fewer/See All button controlling
                                                the whole table if at least one biosample_type has
                                                more than the limit. We know this is the case if at
                                                least one yGroupOpen state member exists. */}
                                            {Object.keys(this.state.yGroupOpen).length ?

                                                        <button className="group-all-groups-cell__button" onClick={this.handleSeeAllClick}>
                                                            {this.state.allYGroupsOpen ? 'See fewer biosamples' : 'See all biosamples'}
                                                        </button>

                                            : null}

                                    {context.batch_download ?
                                        <BatchDownload context={context} />
                                    : null}
                                    {' '}
                                    {visualizeKeys.length ?
                                        <BrowserSelector
                                            visualizeCfg={context.visualize_batch}
                                            disabled={visualizeDisabled}
                                            title={visualizeDisabled ? `Filter to ${visualizeLimit} to visualize` : 'Visualize'}
                                        />
                                    : null}

            );
        }
        return
{notification}
;
    }
}

AuditMatrix.propTypes = {
    context: React.PropTypes.object.isRequired,
};

AuditMatrix.contextTypes = {
    location_href: PropTypes.string,
    navigate: PropTypes.func,
    biosampleTypeColors: PropTypes.object, // DataColor instance for experiment project
    auditCategoryColors: PropTypes.object,
};

globals.contentViews.register(AuditMatrix, 'AuditMatrix');
<commit_msg>changed see all to audits<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import color from 'color';
import _ from 'underscore';
import url from 'url';
import { svgIcon } from '../libs/svg-icons';
import * as globals from './globals';
import { BrowserSelector } from './objectutils';
import { BatchDownload, FacetList, TextFilter } from './search';

class GroupMoreButton extends React.Component {
    constructor() {
        super();

        // Bind this to non-React components.
        this.localHandleClick = this.localHandleClick.bind(this);
    }

    localHandleClick() {
        this.props.handleClick(this.props.id);
    }

    render() {
        return <button className="group-more-cell__button" onClick={this.localHandleClick}>{this.props.displayText}</button>;
    }
}

GroupMoreButton.propTypes = {
    id: PropTypes.string, // ID for the handleClick function to know which control was clicked
    handleClick: PropTypes.func.isRequired, // Call this parent function to handle the click in the button
    displayText: PropTypes.string, // Text to display in the button while closed
};

GroupMoreButton.defaultProps = {
    id: '',
    displayText: '',
};

class AuditMatrix extends React.Component {
    static generateYGroupOpen(matrix) {
        // Make a state for each of the Y groups (each Y group currently shows a biosample type).
        // To do that, we have to get each of the bucket keys, which will be the keys into the
        // object that keeps track of whether the group shows all or not. If a group has fewer than
        // the maximum number of items to show a See More button, it doesn't get included in the
        // state.
        const primaryYGrouping = matrix.y.group_by[0];
        const secondaryYGrouping = matrix.y.group_by[1];
        const yLimit = matrix.y.limit;
        const yGroups = matrix.y[primaryYGrouping].buckets;
        const yGroupOpen = {};
        yGroups.forEach((group) => {
            if (group[secondaryYGrouping].buckets.length > yLimit) {
                yGroupOpen[group.key] = false;
            }
        });
        return yGroupOpen;
    }

    constructor(props) {
        super(props);

        // Set initial React state.
        const yGroupOpen = AuditMatrix.generateYGroupOpen(this.props.context.matrix);
        this.state = {
            yGroupOpen,
            allYGroupsOpen: false,
        };

        // Bind this to non-React methods.
        this.onChange = this.onChange.bind(this);
        this.onFilter = this.onFilter.bind(this);
        this.updateElement = this.updateElement.bind(this);
        this.handleClick = this.handleClick.bind(this);
        this.handleSeeAllClick = this.handleSeeAllClick.bind(this);
    }

    componentWillReceiveProps(nextProps) {
        // This callback makes possible updating the See More buttons when the user clicks a facet,
        // which could cause these buttons to not be needed. This resets all the buttons to the See
        // More state.
        const yGroupOpen = AuditMatrix.generateYGroupOpen(nextProps.context.matrix);
        this.setState({
            yGroupOpen,
            allYGroupsOpen: false,
        });
    }

    onChange(href) {
        this.context.navigate(href);
    }

    onFilter(e) {
        const search = e.currentTarget.getAttribute('href');
        this.context.navigate(search);
        e.stopPropagation();
        e.preventDefault();
    }

    // Called when the Visualize button dropdown menu gets opened or closed. `dropdownEl` is the
    // DOM node for the dropdown menu. This sets inline CSS to set the height of the wrapper

    // to make room for the dropdown.
    updateElement(dropdownEl) {
        const wrapperEl = this.hubscontrols;
        const dropdownHeight = dropdownEl.clientHeight;
        if (dropdownHeight === 0) {
            // The dropdown menu has closed
            wrapperEl.style.height = 'auto';
        } else {
            // The menu has dropped down
            wrapperEl.style.height = `${wrapperEl.clientHeight}${dropdownHeight}px`;
        }
    }

    // Handle a click in a See More link within the matrix (not for the facets)
    handleClick(groupKey) {
        const groupOpen = _.clone(this.state.yGroupOpen);
        groupOpen[groupKey] = !groupOpen[groupKey];
        this.setState({ yGroupOpen: groupOpen });
    }

    handleSeeAllClick() {
        this.setState((prevState) => {
            const newState = {};

            // If the See All button wasn't open (meaning this function was called because the user
            // wanted to see all), forget all the individual ones the user had opened so that
            // they're all closed when the user clicks See Fewer.
            if (!prevState.allYGroupsOpen) {
                const groupOpen = {};
                Object.keys(prevState.yGroupOpen).forEach((key) => {
                    groupOpen[key] = false;
                });
                newState.yGroupOpen = groupOpen;
            }

            // Toggle the state of allYGroupsOpen.
            newState.allYGroupsOpen = !prevState.allYGroupsOpen;
            return newState;
        });
    }

    render() {
        const context = this.props.context;
        const matrix = context.matrix;
        const parsedUrl = url.parse(this.context.location_href);
        const matrixBase = parsedUrl.search || '';
        const matrixSearch = matrixBase + (matrixBase ? '&' : '?');
        const notification = context.notification;
        const visualizeLimit = 500;
        if (notification === 'Success' || notification === 'No results found') {
            const xFacets = matrix.x.facets.map(f => _.findWhere(context.facets, { field: f })).filter(f => f);
            let yFacets = matrix.y.facets.map(f => _.findWhere(context.facets, { field: f })).filter(f => f);
            yFacets = yFacets.concat(_.reject(context.facets, f => _.contains(matrix.x.facets, f.field) || _.contains(matrix.y.facets, f.field)));
            const xGrouping = matrix.x.group_by;
            const primaryYGrouping = matrix.y.group_by[0];
            const secondaryYGrouping = matrix.y.group_by[1];
            const xBuckets = matrix.x.buckets;
            const xLimit = matrix.x.limit || xBuckets.length;
            var yGroups = matrix.y[primaryYGrouping].buckets;
            const orderKey = ['audit-ERROR-category', 'audit-NOT_COMPLIANT-category', 'audit-WARNING-category', 'audit-INTERNAL_ACTION-category'];
            var orderIndex = 0;
            var tempYGroups = [];
            while(orderIndex < orderKey.length){
                yGroups.forEach((group) => {
                    if(group.key === orderKey[orderIndex]){
                        tempYGroups.push(group);
                    }
                });
                orderIndex++;
            }
            yGroups = tempYGroups;
            const yGroupFacet = _.findWhere(context.facets, { field: primaryYGrouping });
            const yGroupOptions = yGroupFacet ? yGroupFacet.terms.map(term => term.key) : [];
            yGroupOptions.sort();
            const searchBase = context.matrix.search_base;
            const visualizeDisabled = matrix.doc_count > visualizeLimit;
            const colCount = Math.min(xBuckets.length, xLimit + 1);

            // Get a sorted list of batch hubs keys with case-insensitive sort
            // NOTE: Tim thinks this is overkill as opposed to simple sort()
            let visualizeKeys = [];
            if (context.visualize_batch && Object.keys(context.visualize_batch).length) {
                visualizeKeys = Object.keys(context.visualize_batch).sort((a, b) => {
                    const aLower = a.toLowerCase();
                    const bLower = b.toLowerCase();
                    return (aLower > bLower) ? 1 : ((aLower < bLower) ? -1 : 0);
                });
            }

            // Map view icons to svg icons
            const view2svg = {
                'list-alt': 'search',
                table: 'table',
            };

            // Make an array of colors corresponding to the ordering of biosample_type
            const biosampleTypeColors = ["#cc0700", "#ff8000", "#e0e000"];

            return (

{context.title}

Click or enter search terms to filter the experiments included in the matrix.

                                                <TextFilter filters={context.filters} searchBase={matrixSearch} onChange={this.onChange} />

                                <FacetList
                                    facets={xFacets}
                                    filters={context.filters}
                                    orientation="horizontal"
                                    searchBase={matrixSearch}
                                    onFilter={this.onFilter}
                                />

                                <FacetList facets={yFacets} filters={context.filters} searchBase={matrixSearch} onFilter={this.onFilter} />

                                            {matrix.y.label.toUpperCase()}

                                            {matrix.doc_count ?

                                                    {matrix.x.label.toUpperCase()}

                                            : null}

                                                      {matrix.doc_count} results

                                                        {matrix.doc_count && context.views ? context.views.map(view => {svgIcon(view2svg[view.icon])}) : ''}

                                                    {context.filters.length ?

                                                            Clear Filters

                                                    : null}

                                                {xBuckets.map((xb, i) => {
                                                    if (i < xLimit) {
                                                        const href = `${searchBase}&${xGrouping}=${globals.encodedURIComponent(xb.key)}`;
                                                        return
{xb.key}
	;
                                                    } else if (i === xLimit) {
                                                        const parsed = url.parse(matrixBase, true);
                                                        parsed.query['x.limit'] = null;
                                                        delete parsed.search; // this makes format compose the search string out of the query object
                                                        const unlimitedHref = url.format(parsed);
                                                        return
...and {xBuckets.length - xLimit} more
	;
                                                    }
                                                    return null;
                                                })}

                                            {yGroups.map((group, i) => {
                                                const groupColor = biosampleTypeColors[i];
                                                const seriesColor = color(groupColor);
                                                const parsed = url.parse(matrixBase, true);
                                                parsed.query[primaryYGrouping] = group.key;
                                                parsed.query['y.limit'] = null;
                                                delete parsed.search; // this makes format compose the search string out of the query object
                                                const groupHref = url.format(parsed);
                                                const rows = [

                                                        {group.key}

];
                                                const groupBuckets = group[secondaryYGrouping].buckets;
                                                const yLimit = matrix.y.limit || groupBuckets.length;

                                                // If this group isn't open (noted by
                                                // this.state.yGroupOpen[key]), extract just the
                                                // group rows that are under the display limit.
                                                const groupRows = (this.state.yGroupOpen[group.key] || this.state.allYGroupsOpen) ? groupBuckets : groupBuckets.slice(0, yLimit);
                                                rows.push(...groupRows.map((yb) => {
                                                    const href = `${searchBase}&${secondaryYGrouping}=${globals.encodedURIComponent(yb.key)}`;
                                                    return (

                                                            {yb.key}
                                                            {xBuckets.map((xb, k) => {
                                                                if (k < xLimit) {
                                                                    const value = yb[xGrouping][k];
                                                                    const cellColor = seriesColor.clone();
                                                                    // scale color between white and the series color
                                                                    cellColor.lightness(cellColor.lightness() + ((1 - (value / matrix.max_cell_doc_count)) * (100 - cellColor.lightness())));
                                                                    const textColor = cellColor.luminosity() > 0.5 ? '#000' : '#fff';
                                                                    const cellHref = `${searchBase}&${secondaryYGrouping}=${globals.encodedURIComponent(yb.key)}&${xGrouping}=${globals.encodedURIComponent(xb.key)}`;
                                                                    const title = `${yb.key} / ${xb.key}: ${value}`;
                                                                    return (

                                                                            {value ? {value} : null}

                                                                    );
                                                                }
                                                                return null;
                                                            })}
                                                            {xBuckets.length > xLimit && }

                                                    );
                                                }));
                                                if (groupBuckets.length > yLimit && !this.state.allYGroupsOpen) {
                                                    rows.push(

                                                                <GroupMoreButton
                                                                    id={group.key}
                                                                    handleClick={this.handleClick}
                                                                    displayText={this.state.yGroupOpen[group.key] ? '- See fewer' : `+ See ${groupBuckets.length - yLimit} more…`}
                                                                />

                                                            {_.range(colCount - 1).map(n => )}

                                                    );
                                                }
                                                return rows;
                                            })}

                                            {/* Display the See Fewer/See All button controlling
                                                the whole table if at least one biosample_type has
                                                more than the limit. We know this is the case if at
                                                least one yGroupOpen state member exists. */}
                                            {Object.keys(this.state.yGroupOpen).length ?

                                                        <button className="group-all-groups-cell__button" onClick={this.handleSeeAllClick}>
                                                            {this.state.allYGroupsOpen ? 'See fewer audits' : 'See all audits'}
                                                        </button>

                                            : null}

                                    {context.batch_download ?
                                        <BatchDownload context={context} />
                                    : null}
                                    {' '}
                                    {visualizeKeys.length ?
                                        <BrowserSelector
                                            visualizeCfg={context.visualize_batch}
                                            disabled={visualizeDisabled}
                                            title={visualizeDisabled ? `Filter to ${visualizeLimit} to visualize` : 'Visualize'}
                                        />
                                    : null}

            );
        }
        return
{notification}
;
    }
}

AuditMatrix.propTypes = {
    context: React.PropTypes.object.isRequired,
};

AuditMatrix.contextTypes = {
    location_href: PropTypes.string,
    navigate: PropTypes.func,
    biosampleTypeColors: PropTypes.object, // DataColor instance for experiment project
    auditCategoryColors: PropTypes.object,
};

globals.contentViews.register(AuditMatrix, 'AuditMatrix');
<|endoftext|>


<commit_before>const assert = require('assert')
const checkProps = require('./check-props')

const allowedProps = [
  'name',
  'data',
  'width',
  'height',
  'pixelFormat',
  'internalFormat',
  'type',
  'encoding',
  'flipY',
  'mipmap',
  'target',
  'min',
  'mag',
  'wrap',
  'wrapS',
  'wrapT',
  'aniso',
  'premultiplyAlpha',
  'compressed'
]

function createTexture(ctx, opts) {
  checkProps(allowedProps, opts)

  const gl = ctx.gl

  const texture = {
    class: 'texture',
    handle: gl.createTexture(),
    target: opts.target,
    width: 0,
    height: 0,
    _update: updateTexture2D,
    _dispose: function() {
      gl.deleteTexture(this.handle)
      this.handle = null
    }
  }

  updateTexture2D(ctx, texture, opts)

  return texture
}

function orValue(a, b) {
  return a !== undefined ? a : b
}

// opts = { data, width, height, pixelFormat, encoding, flipY }
function updateTexture2D(ctx, texture, opts) {
  // checkProps(allowedProps, opts)

  const gl = ctx.gl
  let compressed = opts.compressed

  let data = null
  let width = opts.width
  let height = opts.height
  let lod = 0
  let flipY = orValue(opts.flipY, orValue(texture.flipY, false))
  let target = opts.target || texture.target
  let pixelFormat =
    opts.pixelFormat || texture.pixelFormat || ctx.PixelFormat.RGBA8
  let encoding = opts.encoding || texture.encoding || ctx.Encoding.Linear
  let min = opts.min || texture.min || gl.NEAREST
  let mag = opts.mag || texture.mag || gl.NEAREST
  let wrapS =
    opts.wrapS || texture.wrapS || opts.wrap || texture.wrap || gl.CLAMP_TO_EDGE
  let wrapT =
    opts.wrapT || texture.wrapT || opts.wrap || texture.wrap || gl.CLAMP_TO_EDGE
  let aniso = opts.aniso || texture.aniso || 0
  let premultiplyAlpha = orValue(
    opts.premultiplyAlpha,
    orValue(texture.premultiplyAlpha, false)
  )
  let internalFormat
  let type
  let format

  var anisoExt = gl.getExtension('EXT_texture_filter_anisotropic')

  const textureUnit = 0
  gl.activeTexture(gl.TEXTURE0 + textureUnit)
  gl.bindTexture(texture.target, texture.handle)
  ctx.state.activeTextures[textureUnit] = texture

  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY)
  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha)
  gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, mag)
  gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, min)
  gl.texParameteri(target, gl.TEXTURE_WRAP_S, wrapS)
  gl.texParameteri(target, gl.TEXTURE_WRAP_T, wrapT)
  if (anisoExt && aniso > 0) {
    gl.texParameterf(target, anisoExt.TEXTURE_MAX_ANISOTROPY_EXT, aniso)
  }

  // just an image
  // opts = HTMLImage

  // image with flags
  // opts = { data: HTMLImage, flipY: Boolean }

  // pixel data
  // opts = { data: Array, width: Number, height: Number, flipY: Boolean }

  // pixel data with flags
  // opts = { data: { data: Array, width: Number, height: Number }, flipY: Boolean },

  // array of images for cubemaps (and array textures in webgl2)
  // opts = { data: [ HTMLImage, ... ], width: Number, height: Number, flipY: Boolean }

  // array of pixel data for cubemaps and compressed texture (and array texture in webgl2)
  // opts = { data: [ { data: Array, width: Number, height: Number }, ..], flipY: Boolean }

  const img = opts.data ? opts.data : opts
  if (img && img.nodeName) {
    assert(
      img instanceof window.HTMLImageElement ||
        img instanceof window.HTMLVideoElement ||
        img instanceof window.HTMLCanvasElement,
      'Texture2D.update opts has to be Image, Canvas or Video element'
    )
    width = img.width || img.videoHeight
    height = img.height || img.videoHeight
    internalFormat = gl.RGBA
    format = gl.RGBA
    type = gl.UNSIGNED_BYTE
    gl.texImage2D(target, lod, internalFormat, format, type, img)
    texture.width = width
    texture.height = height
  } else if (typeof opts === 'object') {
    assert(
      !data ||
        Array.isArray(opts.data) ||
        opts.data instanceof Uint8Array ||
        opts.data instanceof Float32Array,
      'Texture2D.update opts.data has to be null or an Array, Uint8Array or Float32Array'
    )

    data = opts.data ? opts.data.data || opts.data : null

    if (!opts.width && data && data.width) width = data.width
    if (!opts.height && data && data.height) height = data.height

    if (!compressed) {
      assert(
        !data || (width !== undefined && height !== undefined),
        'Texture2D.update opts.width and opts.height are required when providing opts.data'
      )
    }

    // Get internalFormat (format the GPU use internally) from opts.internalFormat (mainly for compressed texture) or pixelFormat
    internalFormat = opts.internalFormat || gl[pixelFormat] || gl.RGBA
    assert(
      internalFormat,
      `Texture2D.update Unknown internalFormat ${internalFormat}.`
    )

    // Get actual format and type (data supplied)
    ;[format, type] = ctx.TextureFormat[pixelFormat]
    type = opts.type || type
    assert(type, `Texture2D.update Unknown type ${type}.`)

    // WEBGL_depth_texture (WebGL1 only) just adds DEPTH_COMPONENT and DEPTH_STENCIL
    if (
      ctx.capabilities.depthTexture &&
      ['DEPTH_COMPONENT16', 'DEPTH_COMPONENT24'].includes(pixelFormat)
    ) {
      internalFormat = gl['DEPTH_COMPONENT']
    }

    if (target === gl.TEXTURE_2D) {
      if (compressed) {
        data = Array.isArray(data) ? data : [data]

        for (let level = 0; level < data.length; level++) {
          assert(
            !data[level].data ||
              (data[level].width !== undefined &&
                data[level].height !== undefined),
            'Texture2D.update opts.width and opts.height are required when providing opts.data'
          )
          gl.compressedTexImage2D(
            target,
            level,
            internalFormat,
            data[level].width,
            data[level].height,
            0,
            data[level].data
          )
        }

        // Set filtering
        // TODO: allow override?
        if (data.length > 1) {
          if (texture.min === gl.LINEAR) texture.min = gl.NEAREST_MIPMAP_LINEAR
        } else {
          if (texture.min === gl.NEAREST_MIPMAP_LINEAR) texture.min = gl.LINEAR
        }

        texture.width = data[0].width
        texture.height = data[0].height
      } else {
        if (Array.isArray(data)) {
          if (type === gl.UNSIGNED_BYTE) {
            data = new Uint8Array(data)
          } else if (type === gl.FLOAT) {
            data = new Float32Array(data)
          } else if (type === gl.HALF_FLOAT) {
            data = new Float32Array(data)
          } else {
            assert.fail(`Unknown texture data type: ${type}`)
          }
        }

        if (width && height) {
          gl.texImage2D(
            target,
            lod,
            internalFormat,
            width,
            height,
            0,
            format,
            type,
            data
          )
          texture.width = width
          texture.height = height
        }
      }
    } else if (target === gl.TEXTURE_CUBE_MAP) {
      assert(
        !data || (Array.isArray(data) && data.length === 6),
        'TextureCube requires data for 6 faces'
      )
      // TODO: gl.compressedTexImage2D for cubemap target
      for (let i = 0; i < 6; i++) {
        let faceData = data ? data[i].data || data[i] : null
        const faceTarget = gl.TEXTURE_CUBE_MAP_POSITIVE_X + i
        if (Array.isArray(faceData)) {
          if (type === gl.UNSIGNED_BYTE) {
            faceData = new Uint8Array(faceData)
          } else if (type === gl.FLOAT) {
            faceData = new Float32Array(data)
          } else {
            assert.fail(`Unknown texture data type: ${type}`)
          }
          gl.texImage2D(
            faceTarget,
            lod,
            internalFormat,
            width,
            height,
            0,
            format,
            type,
            faceData
          )
        } else if (faceData && faceData.nodeName) {
          gl.texImage2D(faceTarget, lod, internalFormat, format, type, faceData)
        } else {
          gl.texImage2D(
            faceTarget,
            lod,
            internalFormat,
            width,
            height,
            0,
            format,
            type,
            faceData
          )
        }
        texture.width = width
        texture.height = height
      }
    }
  } else {
    // TODO: should i assert of throw new Error(msg)?
    assert.fail('Texture2D.update opts has to be a HTMLElement or Object')
  }

  if (opts.mipmap) {
    gl.generateMipmap(texture.target)
  }

  texture.target = target
  texture.pixelFormat = pixelFormat
  texture.encoding = encoding
  texture.min = min
  texture.mag = mag
  texture.wrapS = wrapS
  texture.wrapT = wrapT
  texture.format = format
  texture.flipY = flipY
  texture.internalFormat = internalFormat
  texture.type = type
  texture.info = ''
  texture.info += Object.keys(ctx.PixelFormat).find(
    (key) => ctx.PixelFormat[key] === pixelFormat
  )
  texture.info += '_'
  texture.info += Object.keys(ctx.Encoding).find(
    (key) => ctx.Encoding[key] === encoding
  )

  return texture
}

module.exports = createTexture
<commit_msg>fix: move texture update opts.wrap before texture.wrapS/T check<commit_after>const assert = require('assert')
const checkProps = require('./check-props')

const allowedProps = [
  'name',
  'data',
  'width',
  'height',
  'pixelFormat',
  'internalFormat',
  'type',
  'encoding',
  'flipY',
  'mipmap',
  'target',
  'min',
  'mag',
  'wrap',
  'wrapS',
  'wrapT',
  'aniso',
  'premultiplyAlpha',
  'compressed'
]

function createTexture(ctx, opts) {
  checkProps(allowedProps, opts)

  const gl = ctx.gl

  const texture = {
    class: 'texture',
    handle: gl.createTexture(),
    target: opts.target,
    width: 0,
    height: 0,
    _update: updateTexture2D,
    _dispose: function() {
      gl.deleteTexture(this.handle)
      this.handle = null
    }
  }

  updateTexture2D(ctx, texture, opts)

  return texture
}

function orValue(a, b) {
  return a !== undefined ? a : b
}

// opts = { data, width, height, pixelFormat, encoding, flipY }
function updateTexture2D(ctx, texture, opts) {
  // checkProps(allowedProps, opts)

  const gl = ctx.gl
  let compressed = opts.compressed

  let data = null
  let width = opts.width
  let height = opts.height
  let lod = 0
  let flipY = orValue(opts.flipY, orValue(texture.flipY, false))
  let target = opts.target || texture.target
  let pixelFormat =
    opts.pixelFormat || texture.pixelFormat || ctx.PixelFormat.RGBA8
  let encoding = opts.encoding || texture.encoding || ctx.Encoding.Linear
  let min = opts.min || texture.min || gl.NEAREST
  let mag = opts.mag || texture.mag || gl.NEAREST
  let wrapS =
    opts.wrapS || opts.wrap || texture.wrapS || texture.wrap || gl.CLAMP_TO_EDGE
  let wrapT =
    opts.wrapT || opts.wrap || texture.wrapT || texture.wrap || gl.CLAMP_TO_EDGE
  let aniso = opts.aniso || texture.aniso || 0
  let premultiplyAlpha = orValue(
    opts.premultiplyAlpha,
    orValue(texture.premultiplyAlpha, false)
  )
  let internalFormat
  let type
  let format

  var anisoExt = gl.getExtension('EXT_texture_filter_anisotropic')

  const textureUnit = 0
  gl.activeTexture(gl.TEXTURE0 + textureUnit)
  gl.bindTexture(texture.target, texture.handle)
  ctx.state.activeTextures[textureUnit] = texture

  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY)
  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha)
  gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, mag)
  gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, min)
  gl.texParameteri(target, gl.TEXTURE_WRAP_S, wrapS)
  gl.texParameteri(target, gl.TEXTURE_WRAP_T, wrapT)
  if (anisoExt && aniso > 0) {
    gl.texParameterf(target, anisoExt.TEXTURE_MAX_ANISOTROPY_EXT, aniso)
  }

  // just an image
  // opts = HTMLImage

  // image with flags
  // opts = { data: HTMLImage, flipY: Boolean }

  // pixel data
  // opts = { data: Array, width: Number, height: Number, flipY: Boolean }

  // pixel data with flags
  // opts = { data: { data: Array, width: Number, height: Number }, flipY: Boolean },

  // array of images for cubemaps (and array textures in webgl2)
  // opts = { data: [ HTMLImage, ... ], width: Number, height: Number, flipY: Boolean }

  // array of pixel data for cubemaps and compressed texture (and array texture in webgl2)
  // opts = { data: [ { data: Array, width: Number, height: Number }, ..], flipY: Boolean }

  const img = opts.data ? opts.data : opts
  if (img && img.nodeName) {
    assert(
      img instanceof window.HTMLImageElement ||
        img instanceof window.HTMLVideoElement ||
        img instanceof window.HTMLCanvasElement,
      'Texture2D.update opts has to be Image, Canvas or Video element'
    )
    width = img.width || img.videoHeight
    height = img.height || img.videoHeight
    internalFormat = gl.RGBA
    format = gl.RGBA
    type = gl.UNSIGNED_BYTE
    gl.texImage2D(target, lod, internalFormat, format, type, img)
    texture.width = width
    texture.height = height
  } else if (typeof opts === 'object') {
    assert(
      !data ||
        Array.isArray(opts.data) ||
        opts.data instanceof Uint8Array ||
        opts.data instanceof Float32Array,
      'Texture2D.update opts.data has to be null or an Array, Uint8Array or Float32Array'
    )

    data = opts.data ? opts.data.data || opts.data : null

    if (!opts.width && data && data.width) width = data.width
    if (!opts.height && data && data.height) height = data.height

    if (!compressed) {
      assert(
        !data || (width !== undefined && height !== undefined),
        'Texture2D.update opts.width and opts.height are required when providing opts.data'
      )
    }

    // Get internalFormat (format the GPU use internally) from opts.internalFormat (mainly for compressed texture) or pixelFormat
    internalFormat = opts.internalFormat || gl[pixelFormat] || gl.RGBA
    assert(
      internalFormat,
      `Texture2D.update Unknown internalFormat ${internalFormat}.`
    )

    // Get actual format and type (data supplied)
    ;[format, type] = ctx.TextureFormat[pixelFormat]
    type = opts.type || type
    assert(type, `Texture2D.update Unknown type ${type}.`)

    // WEBGL_depth_texture (WebGL1 only) just adds DEPTH_COMPONENT and DEPTH_STENCIL
    if (
      ctx.capabilities.depthTexture &&
      ['DEPTH_COMPONENT16', 'DEPTH_COMPONENT24'].includes(pixelFormat)
    ) {
      internalFormat = gl['DEPTH_COMPONENT']
    }

    if (target === gl.TEXTURE_2D) {
      if (compressed) {
        data = Array.isArray(data) ? data : [data]

        for (let level = 0; level < data.length; level++) {
          assert(
            !data[level].data ||
              (data[level].width !== undefined &&
                data[level].height !== undefined),
            'Texture2D.update opts.width and opts.height are required when providing opts.data'
          )
          gl.compressedTexImage2D(
            target,
            level,
            internalFormat,
            data[level].width,
            data[level].height,
            0,
            data[level].data
          )
        }

        // Set filtering
        // TODO: allow override?
        if (data.length > 1) {
          if (texture.min === gl.LINEAR) texture.min = gl.NEAREST_MIPMAP_LINEAR
        } else {
          if (texture.min === gl.NEAREST_MIPMAP_LINEAR) texture.min = gl.LINEAR
        }

        texture.width = data[0].width
        texture.height = data[0].height
      } else {
        if (Array.isArray(data)) {
          if (type === gl.UNSIGNED_BYTE) {
            data = new Uint8Array(data)
          } else if (type === gl.FLOAT) {
            data = new Float32Array(data)
          } else if (type === gl.HALF_FLOAT) {
            data = new Float32Array(data)
          } else {
            assert.fail(`Unknown texture data type: ${type}`)
          }
        }

        if (width && height) {
          gl.texImage2D(
            target,
            lod,
            internalFormat,
            width,
            height,
            0,
            format,
            type,
            data
          )
          texture.width = width
          texture.height = height
        }
      }
    } else if (target === gl.TEXTURE_CUBE_MAP) {
      assert(
        !data || (Array.isArray(data) && data.length === 6),
        'TextureCube requires data for 6 faces'
      )
      // TODO: gl.compressedTexImage2D for cubemap target
      for (let i = 0; i < 6; i++) {
        let faceData = data ? data[i].data || data[i] : null
        const faceTarget = gl.TEXTURE_CUBE_MAP_POSITIVE_X + i
        if (Array.isArray(faceData)) {
          if (type === gl.UNSIGNED_BYTE) {
            faceData = new Uint8Array(faceData)
          } else if (type === gl.FLOAT) {
            faceData = new Float32Array(data)
          } else {
            assert.fail(`Unknown texture data type: ${type}`)
          }
          gl.texImage2D(
            faceTarget,
            lod,
            internalFormat,
            width,
            height,
            0,
            format,
            type,
            faceData
          )
        } else if (faceData && faceData.nodeName) {
          gl.texImage2D(faceTarget, lod, internalFormat, format, type, faceData)
        } else {
          gl.texImage2D(
            faceTarget,
            lod,
            internalFormat,
            width,
            height,
            0,
            format,
            type,
            faceData
          )
        }
        texture.width = width
        texture.height = height
      }
    }
  } else {
    // TODO: should i assert of throw new Error(msg)?
    assert.fail('Texture2D.update opts has to be a HTMLElement or Object')
  }

  if (opts.mipmap) {
    gl.generateMipmap(texture.target)
  }

  texture.target = target
  texture.pixelFormat = pixelFormat
  texture.encoding = encoding
  texture.min = min
  texture.mag = mag
  texture.wrapS = wrapS
  texture.wrapT = wrapT
  texture.format = format
  texture.flipY = flipY
  texture.internalFormat = internalFormat
  texture.type = type
  texture.info = ''
  texture.info += Object.keys(ctx.PixelFormat).find(
    (key) => ctx.PixelFormat[key] === pixelFormat
  )
  texture.info += '_'
  texture.info += Object.keys(ctx.Encoding).find(
    (key) => ctx.Encoding[key] === encoding
  )

  return texture
}

module.exports = createTexture
<|endoftext|>