<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
import { schemeCategory10 } from 'd3';
import { push } from 'react-router-redux';
import composeAsyncContainer from '../common/AsyncContainer';
import LoadingSpinner from '../common/LoadingSpinner';
import { LEVEL_ERROR } from '../common/Notice';
import { addNotice } from '../../actions/appActions';
import { selectBySearchParams, fetchSampleSearches, updateQuerySourceLookupInfo, updateQueryCollectionLookupInfo,
  fetchQuerySourcesByIds, fetchQueryCollectionsByIds, demoQuerySourcesByIds, demoQueryCollectionsByIds } from '../../actions/explorerActions';
// import { FETCH_INVALID, FETCH_SUCCEEDED } from '../../lib/fetchConstants';
import { DEFAULT_COLLECTION_OBJECT_ARRAY, autoMagicQueryLabel, generateQueryParamString, decodeQueryParamString } from '../../lib/explorerUtil';
import { getPastTwoWeeksDateRange } from '../../lib/dateUtil';
import { notEmptyString } from '../../lib/formValidators';

const localMessages = {
  errorInURLParams: { id: 'explorer.queryBuilder.urlParams',
    defaultMessage: 'Your URL query is incomplete. Check the URL and make sure the keyword(s), start and end dates, and collection(s) are properly specified.' },
};

/**
 * Only render the widget once we've pared the query from the URL - complicated because we do an async fetch
 * to get source and collection details from the server.
 */
function composeUrlBasedQueryContainer() {
  return (ChildComponent) => {
    class UrlBasedQueryContainer extends React.Component {
      state = {
        queryInStore: false,
      };
      componentWillMount() {
        const { location } = this.props;
        this.setState({ queryInStore: false }); // necc?
        this.setQueryFromLocation(location);
      }
      componentWillReceiveProps(nextProps) {
        const { location, lastSearchTime, updateUrl, isLoggedIn } = this.props;
        // console.log('new props');
        // if URL has been updated by hand, reparse and store
        if ((nextProps.location.pathname !== location.pathname) && (lastSearchTime === nextProps.lastSearchTime)) {
          this.setState({ queryInStore: false }); // show spinner while parsing and loading query
          // console.log('  url change');
          this.setQueryFromLocation(location);
        // if we don't have all the data in the store yet
        } else if (this.state.queryInStore === false) {   // make sure to only do this once
          // console.log('  waiting for media info from server');
          // mark the whole thing as ready once any sources and collections have been set
          if (this.isAllMediaDetailsReady()) {
            // console.log('  got media info from server, ready!');
            this.setState({ queryInStore: true });  // mark that the parsing process has finished
          }
        } else if (lastSearchTime !== nextProps.lastSearchTime) {
          updateUrl(nextProps.queries, isLoggedIn);
        } else {
          // console.log('  other change');
        }
      }
      setQueryFromLocation(location) {
        // regular searches are in a queryParam, but samples by id are part of the path
        const url = location.pathname;
        const lastPathPart = url.slice(url.lastIndexOf('/') + 1, url.length);
        const sampleNumber = parseInt(lastPathPart, 10);
        if (!isNaN(sampleNumber)) {
          this.setQueryFromSample(sampleNumber);
        } else {
          this.setQueryFromSearch(location.search);
        }
      }
      setQueryFromSearch(search) {
        const queryAsJsonStr = search.slice(3, search.length);
        this.setQueryFromString(queryAsJsonStr);
      }
      setQueryFromSample(sampleNumber) {
        const { saveQueriesFromParsedUrl, samples, isLoggedIn } = this.props;
        const queriesFromUrl = samples[sampleNumber].queries;
        // push the queries in to the store
        saveQueriesFromParsedUrl(queriesFromUrl, isLoggedIn);
      }
      setQueryFromString(queryAsJsonStr) {
        const { addAppNotice, saveQueriesFromParsedUrl, isLoggedIn } = this.props;
        const { formatMessage } = this.props.intl;
        let queriesFromUrl;

        try {
          queriesFromUrl = decodeQueryParamString(decodeURIComponent(queryAsJsonStr));
        } catch (f) {
          addAppNotice({ level: LEVEL_ERROR, message: formatMessage(localMessages.errorInURLParams) });
          return;
        }

        let extraDefaults = {};
        // add in an index, label, and color if they are not there
        if (!isLoggedIn) {  // and demo mode needs some extra stuff too
          const defaultDates = getPastTwoWeeksDateRange();
          extraDefaults = {
            sources: [],
            collections: DEFAULT_COLLECTION_OBJECT_ARRAY,
            startDate: defaultDates.start,
            endDate: defaultDates.end,
          };
        }
        queriesFromUrl = queriesFromUrl.map((query, index) => ({
          ...query, // let anything on URL override label and color
          label: notEmptyString(query.label) ? query.label : autoMagicQueryLabel(query),
          // remember demo queries won't have sources or collections on the URL
          sources: query.sources ? query.sources.map(s => ({ id: s, media_id: s })) : undefined,
          collections: query.collections ? query.collections.map(s => ({ id: s, tags_id: s })) : undefined,
          q: query.q,
          color: query.color ? query.color : schemeCategory10[index % 10],
          index,  // redo index to be zero-based on reload of query
          ...extraDefaults, // for demo mode
        }));
        // push the queries in to the store
        saveQueriesFromParsedUrl(queriesFromUrl, isLoggedIn);
      }
      isAllMediaDetailsReady() {
        // we will only render the wrapped component once all the details of the sources and collections
        // have been fetched from the server and put in the right place on each query
        const { queries } = this.props;
        if (queries.length === 0) return false; // need to bail if no queries (ie. first page mount)
        const queryCollectionStatus = queries.map(q => q.collections.length === 0 ||
          q.collections.reduce((combined, c) => combined && c.tag_sets_id !== undefined, true));
        const collectionsAreReady = queryCollectionStatus.reduce((combined, q) => combined && q, true);
        const querySourceStatus = queries.map(q => q.sources.length === 0 ||
          q.sources.reduce((combined, s) => combined && s.name !== undefined, true));
        const sourcesAreReady = querySourceStatus.reduce((combined, q) => combined && q, true);
        return collectionsAreReady && sourcesAreReady;
      }
      render() {
        let content;
        if (this.state.queryInStore) {
          content = <ChildComponent {...this.props} />;
        } else {
          content = <LoadingSpinner />;
        }
        return (

            {content}

        );
      }
    }

    UrlBasedQueryContainer.propTypes = {
      intl: PropTypes.object.isRequired,
      location: PropTypes.object,
      // from store
      isLoggedIn: PropTypes.bool.isRequired,
      fetchStatus: PropTypes.string.isRequired,
      samples: PropTypes.array,
      queries: PropTypes.array,
      lastSearchTime: PropTypes.number,
      // from dispatch
      saveQueriesFromParsedUrl: PropTypes.func.isRequired,
      addAppNotice: PropTypes.func.isRequired,
      updateUrl: PropTypes.func.isRequired,
    };

    const mapStateToProps = state => ({
      isLoggedIn: state.user.isLoggedIn,
      fetchStatus: state.explorer.samples.fetchStatus,
      samples: state.explorer.samples.list,
      queries: state.explorer.queries.queries,
      lastSearchTime: state.explorer.lastSearchTime.time,
    });

    // push any updates (including selected) into queries in state, will trigger async load in sub sections
    const mapDispatchToProps = dispatch => ({
      addAppNotice: (info) => {
        dispatch(addNotice(info));
      },
      // handles demo mode by allowing you to pass in extraDefaults
      saveQueriesFromParsedUrl: (queriesToUse, isLoggedIn) => {
        dispatch(selectBySearchParams(queriesToUse)); // load query data into the store
        // lookup ancillary data eg collection and source info for display purposes in QueryForm
        queriesToUse.forEach((q) => {
          const queryInfo = {
            ...q,
          };
          const sourceDetailsAction = isLoggedIn ? fetchQuerySourcesByIds : demoQuerySourcesByIds;
          if (q.sources && q.sources.length > 0) {
            queryInfo.sources = q.sources.map(src => src.media_id || src.id || src); // the latter in case of sample search
            dispatch(sourceDetailsAction(queryInfo))
            .then((results) => {
              queryInfo.sources = results;
              dispatch(updateQuerySourceLookupInfo(queryInfo)); // updates the query and the selected query
            });
          }
          const collectionDetailsAction = isLoggedIn ? fetchQueryCollectionsByIds : demoQueryCollectionsByIds;
          if (q.collections && q.collections.length > 0) {
            queryInfo.collections = q.collections.map(coll => coll.tags_id || coll.id || coll); // the latter in case of sample search
            dispatch(collectionDetailsAction(queryInfo))
            .then((results) => {
              queryInfo.collections = results;
              dispatch(updateQueryCollectionLookupInfo(queryInfo)); // updates the query and the selected query
            });
          }
        });
      },
      asyncFetch: () => {
        dispatch(fetchSampleSearches());   // inefficient: we need the sample searches loaded just in case
      },
      updateUrl: (queries, isLoggedIn) => {
        const unDeletedQueries = queries.filter(q => q.deleted !== true);
        const nonEmptyQueries = unDeletedQueries.filter(q => q.q !== undefined && q.q !== '');
        if (!isLoggedIn) {
          const urlParamString = nonEmptyQueries.map((q, idx) => `{"index":${idx},"q":"${encodeURIComponent(q.q)}","color":"${encodeURIComponent(q.color)}"}`);
          dispatch(push({ pathname: '/queries/demo/search', search: `?q=[${urlParamString}]` }));
        } else {
          const search = generateQueryParamString(queries.map(q => ({
            label: q.label,
            q: q.q,
            color: q.color,
            startDate: q.startDate,
            endDate: q.endDate,
            sources: q.sources, // de-aggregate media bucket into sources and collections
            collections: q.collections,
          })));
          dispatch(push({ pathname: '/queries/search', search: `?q=${search}` })); // query adds a '?query='
        }
      },
    });

    return injectIntl(
      connect(mapStateToProps, mapDispatchToProps)(
        composeAsyncContainer(
          UrlBasedQueryContainer
        )
      )
    );
  };
}

export default composeUrlBasedQueryContainer;
<commit_msg>#859 quotes in query string problem<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
import { schemeCategory10 } from 'd3';
import { push } from 'react-router-redux';
import composeAsyncContainer from '../common/AsyncContainer';
import LoadingSpinner from '../common/LoadingSpinner';
import { LEVEL_ERROR } from '../common/Notice';
import { addNotice } from '../../actions/appActions';
import { selectBySearchParams, fetchSampleSearches, updateQuerySourceLookupInfo, updateQueryCollectionLookupInfo,
  fetchQuerySourcesByIds, fetchQueryCollectionsByIds, demoQuerySourcesByIds, demoQueryCollectionsByIds } from '../../actions/explorerActions';
// import { FETCH_INVALID, FETCH_SUCCEEDED } from '../../lib/fetchConstants';
import { DEFAULT_COLLECTION_OBJECT_ARRAY, autoMagicQueryLabel, generateQueryParamString, decodeQueryParamString } from '../../lib/explorerUtil';
import { getPastTwoWeeksDateRange } from '../../lib/dateUtil';
import { notEmptyString } from '../../lib/formValidators';

const localMessages = {
  errorInURLParams: { id: 'explorer.queryBuilder.urlParams',
    defaultMessage: 'Your URL query is incomplete. Check the URL and make sure the keyword(s), start and end dates, and collection(s) are properly specified.' },
};

/**
 * Only render the widget once we've pared the query from the URL - complicated because we do an async fetch
 * to get source and collection details from the server.
 */
function composeUrlBasedQueryContainer() {
  return (ChildComponent) => {
    class UrlBasedQueryContainer extends React.Component {
      state = {
        queryInStore: false,
      };
      componentWillMount() {
        const { location } = this.props;
        this.setState({ queryInStore: false }); // necc?
        this.setQueryFromLocation(location);
      }
      componentWillReceiveProps(nextProps) {
        const { location, lastSearchTime, updateUrl, isLoggedIn } = this.props;
        // console.log('new props');
        // if URL has been updated by hand, reparse and store
        if ((nextProps.location.pathname !== location.pathname) && (lastSearchTime === nextProps.lastSearchTime)) {
          this.setState({ queryInStore: false }); // show spinner while parsing and loading query
          // console.log('  url change');
          this.setQueryFromLocation(location);
        // if we don't have all the data in the store yet
        } else if (this.state.queryInStore === false) {   // make sure to only do this once
          // console.log('  waiting for media info from server');
          // mark the whole thing as ready once any sources and collections have been set
          if (this.isAllMediaDetailsReady()) {
            // console.log('  got media info from server, ready!');
            this.setState({ queryInStore: true });  // mark that the parsing process has finished
          }
        } else if (lastSearchTime !== nextProps.lastSearchTime) {
          updateUrl(nextProps.queries, isLoggedIn);
        } else {
          // console.log('  other change');
        }
      }
      setQueryFromLocation(location) {
        // regular searches are in a queryParam, but samples by id are part of the path
        const url = location.pathname;
        const lastPathPart = url.slice(url.lastIndexOf('/') + 1, url.length);
        const sampleNumber = parseInt(lastPathPart, 10);
        if (!isNaN(sampleNumber)) {
          this.setQueryFromSample(sampleNumber);
        } else {
          this.setQueryFromSearch(location.search);
        }
      }
      setQueryFromSearch(search) {
        const queryAsJsonStr = search.slice(3, search.length);
        this.setQueryFromString(queryAsJsonStr);
      }
      setQueryFromSample(sampleNumber) {
        const { saveQueriesFromParsedUrl, samples, isLoggedIn } = this.props;
        const queriesFromUrl = samples[sampleNumber].queries;
        // push the queries in to the store
        saveQueriesFromParsedUrl(queriesFromUrl, isLoggedIn);
      }
      setQueryFromString(queryAsJsonStr) {
        const { addAppNotice, saveQueriesFromParsedUrl, isLoggedIn } = this.props;
        const { formatMessage } = this.props.intl;
        let queriesFromUrl;

        try {
          queriesFromUrl = decodeQueryParamString(queryAsJsonStr);
        } catch (e) { // clunky but a necessary check for Firefox
          try {
            queriesFromUrl = decodeQueryParamString(decodeURIComponent(queryAsJsonStr)); // and this doesn't work for quoted search strings in chrome
          } catch (f) {
            addAppNotice({ level: LEVEL_ERROR, message: formatMessage(localMessages.errorInURLParams) });
            return;
          }
        }

        let extraDefaults = {};
        // add in an index, label, and color if they are not there
        if (!isLoggedIn) {  // and demo mode needs some extra stuff too
          const defaultDates = getPastTwoWeeksDateRange();
          extraDefaults = {
            sources: [],
            collections: DEFAULT_COLLECTION_OBJECT_ARRAY,
            startDate: defaultDates.start,
            endDate: defaultDates.end,
          };
        }
        queriesFromUrl = queriesFromUrl.map((query, index) => ({
          ...query, // let anything on URL override label and color
          label: notEmptyString(query.label) ? query.label : autoMagicQueryLabel(query),
          // remember demo queries won't have sources or collections on the URL
          sources: query.sources ? query.sources.map(s => ({ id: s, media_id: s })) : undefined,
          collections: query.collections ? query.collections.map(s => ({ id: s, tags_id: s })) : undefined,
          q: query.q,
          color: query.color ? query.color : schemeCategory10[index % 10],
          index,  // redo index to be zero-based on reload of query
          ...extraDefaults, // for demo mode
        }));
        // push the queries in to the store
        saveQueriesFromParsedUrl(queriesFromUrl, isLoggedIn);
      }
      isAllMediaDetailsReady() {
        // we will only render the wrapped component once all the details of the sources and collections
        // have been fetched from the server and put in the right place on each query
        const { queries } = this.props;
        if (queries.length === 0) return false; // need to bail if no queries (ie. first page mount)
        const queryCollectionStatus = queries.map(q => q.collections.length === 0 ||
          q.collections.reduce((combined, c) => combined && c.tag_sets_id !== undefined, true));
        const collectionsAreReady = queryCollectionStatus.reduce((combined, q) => combined && q, true);
        const querySourceStatus = queries.map(q => q.sources.length === 0 ||
          q.sources.reduce((combined, s) => combined && s.name !== undefined, true));
        const sourcesAreReady = querySourceStatus.reduce((combined, q) => combined && q, true);
        return collectionsAreReady && sourcesAreReady;
      }
      render() {
        let content;
        if (this.state.queryInStore) {
          content = <ChildComponent {...this.props} />;
        } else {
          content = <LoadingSpinner />;
        }
        return (

            {content}

        );
      }
    }

    UrlBasedQueryContainer.propTypes = {
      intl: PropTypes.object.isRequired,
      location: PropTypes.object,
      // from store
      isLoggedIn: PropTypes.bool.isRequired,
      fetchStatus: PropTypes.string.isRequired,
      samples: PropTypes.array,
      queries: PropTypes.array,
      lastSearchTime: PropTypes.number,
      // from dispatch
      saveQueriesFromParsedUrl: PropTypes.func.isRequired,
      addAppNotice: PropTypes.func.isRequired,
      updateUrl: PropTypes.func.isRequired,
    };

    const mapStateToProps = state => ({
      isLoggedIn: state.user.isLoggedIn,
      fetchStatus: state.explorer.samples.fetchStatus,
      samples: state.explorer.samples.list,
      queries: state.explorer.queries.queries,
      lastSearchTime: state.explorer.lastSearchTime.time,
    });

    // push any updates (including selected) into queries in state, will trigger async load in sub sections
    const mapDispatchToProps = dispatch => ({
      addAppNotice: (info) => {
        dispatch(addNotice(info));
      },
      // handles demo mode by allowing you to pass in extraDefaults
      saveQueriesFromParsedUrl: (queriesToUse, isLoggedIn) => {
        dispatch(selectBySearchParams(queriesToUse)); // load query data into the store
        // lookup ancillary data eg collection and source info for display purposes in QueryForm
        queriesToUse.forEach((q) => {
          const queryInfo = {
            ...q,
          };
          const sourceDetailsAction = isLoggedIn ? fetchQuerySourcesByIds : demoQuerySourcesByIds;
          if (q.sources && q.sources.length > 0) {
            queryInfo.sources = q.sources.map(src => src.media_id || src.id || src); // the latter in case of sample search
            dispatch(sourceDetailsAction(queryInfo))
            .then((results) => {
              queryInfo.sources = results;
              dispatch(updateQuerySourceLookupInfo(queryInfo)); // updates the query and the selected query
            });
          }
          const collectionDetailsAction = isLoggedIn ? fetchQueryCollectionsByIds : demoQueryCollectionsByIds;
          if (q.collections && q.collections.length > 0) {
            queryInfo.collections = q.collections.map(coll => coll.tags_id || coll.id || coll); // the latter in case of sample search
            dispatch(collectionDetailsAction(queryInfo))
            .then((results) => {
              queryInfo.collections = results;
              dispatch(updateQueryCollectionLookupInfo(queryInfo)); // updates the query and the selected query
            });
          }
        });
      },
      asyncFetch: () => {
        dispatch(fetchSampleSearches());   // inefficient: we need the sample searches loaded just in case
      },
      updateUrl: (queries, isLoggedIn) => {
        const unDeletedQueries = queries.filter(q => q.deleted !== true);
        const nonEmptyQueries = unDeletedQueries.filter(q => q.q !== undefined && q.q !== '');
        if (!isLoggedIn) {
          const urlParamString = nonEmptyQueries.map((q, idx) => `{"index":${idx},"q":"${encodeURIComponent(q.q)}","color":"${encodeURIComponent(q.color)}"}`);
          dispatch(push({ pathname: '/queries/demo/search', search: `?q=[${urlParamString}]` }));
        } else {
          const search = generateQueryParamString(queries.map(q => ({
            label: q.label,
            q: q.q,
            color: q.color,
            startDate: q.startDate,
            endDate: q.endDate,
            sources: q.sources, // de-aggregate media bucket into sources and collections
            collections: q.collections,
          })));
          dispatch(push({ pathname: '/queries/search', search: `?q=${search}` })); // query adds a '?query='
        }
      },
    });

    return injectIntl(
      connect(mapStateToProps, mapDispatchToProps)(
        composeAsyncContainer(
          UrlBasedQueryContainer
        )
      )
    );
  };
}

export default composeUrlBasedQueryContainer;
<|endoftext|>


<commit_before>function plugin(UIkit) {

    if (plugin.installed) {
        return;
    }

    var { util } = UIkit;
    var { $ } = util;

    UIkit.component('grid-parallax', {

        props: {
            translate: Number,
            smooth: Number,
            target: String
        },

        defaults: {
            target    : false,
            smooth    : 150,
            translate : 150
        },

        connected() {
            this.initItems();
        },

        methods: {

            initItems: function() {

                var smooth = this.smooth;

                this.items = (this.target ? this.$el.find(this.target) : this.$el.children()).each(function(){
                    $(this).css({
                        transition: `transform ${smooth}ms linear`,
                        transform: ''
                    });
                });

                return this;
            }
        },

        update: [

            {

                write() {

                    var columns  = getcolumns(this.$el);
                    var margin = '';

                    if (columns > 1) {
                         margin = this.translate + parseInt(this.$el.css('margin-bottom'));
                    }

                    this.$el.css('margin-bottom', margin);
                },

                events: ['load', 'resize', 'orientationchange']
            },

            {

                write() {

                    var percent = percentageInViewport(this.$el);
                    var columns = getcolumns(this.$el);
                    var mods = [(columns-1)];

                    if (columns == 1 || !percent) {
                        this.items.css('transform', '');
                        return;
                    }

                    while(mods.length < columns) {
                       if (!(mods[mods.length-1] - 2)) break;
                       mods.push(mods[mods.length-1] - 2);
                    }

                    var percentTranslate = percent * this.translate, translate;

                    this.items.each(function(index) {
                        translate = mods.indexOf((index+1) % columns) != -1 ? percentTranslate : percentTranslate / 8;
                        $(this).css('transform', `translate3d(0, ${translate}px, 0)`);
                    });
                },

                events: ['scroll']
            }
        ]

    });

}

function getcolumns(element) {

    var children = element.children(),
        first    = children.filter(':visible:first'),
        top      = first[0].offsetTop + first.outerHeight();

    for (var column=0;column<children.length;column++) {
        if (children[column].offsetTop >= top)  break;
    }

    return column || 1;
}

function percentageInViewport(element) {

    var top       = element.offset().top,
        height    = element.outerHeight(),
        scrolltop = $(window).scrollTop(),
        wh        = window.innerHeight,
        distance, percentage, percent;

    if (top > (scrolltop + wh)) {
        percent = 0;
    } else if ((top + height) < scrolltop) {
        percent = 1;
    } else {

        if ((top + height) < wh) {
            percent = (scrolltop < wh ? scrolltop : scrolltop - wh) / (top+height);
        } else {

            distance   = (scrolltop + wh) - top;
            percentage = Math.round(distance / ((wh + height) / 100));
            percent    = percentage/100;
        }

        if (top < wh) {
            percent = percent * scrolltop / ((top + height) - wh);
        }
    }

    return percent > 1 ? 1:percent;
}

if (!BUNDLED && typeof window !== 'undefined' && window.UIkit) {
    window.UIkit.use(plugin);
}

export default plugin;
<commit_msg>cleanup<commit_after>function plugin(UIkit) {

    if (plugin.installed) {
        return;
    }

    var { util } = UIkit;
    var { $ } = util;

    var supports3d = 'transformOrigin' in document.documentElement.style;

    UIkit.component('grid-parallax', {

        props: {
            target: String,
            translate: Number,
            smooth: Number
        },

        defaults: {
            target    : false,
            translate : 150,
            smooth    : 150
        },

        connected() {
            this.initItems();
        },

        methods: {

            initItems: function() {

                var smooth = this.smooth;

                this.items = (this.target ? this.$el.find(this.target) : this.$el.children()).each(function(){
                    $(this).css({
                        transition: `transform ${smooth}ms linear`,
                        transform: ''
                    });
                });

                return this;
            }
        },

        update: [

            {

                write() {

                    var columns  = getColumnsCount(this.$el);
                    var margin = '';

                    if (columns > 1) {
                         margin = this.translate + parseInt(this.$el.css('margin-bottom'));
                    }

                    this.$el.css('margin-bottom', margin);
                },

                events: ['load', 'resize', 'orientationchange']
            },

            {

                write() {

                    var percent = percentageInViewport(this.$el);
                    var columns = getColumnsCount(this.$el);
                    var mods = [(columns-1)];

                    if (columns == 1 || !percent) {
                        this.items.css('transform', '');
                        return;
                    }

                    while (mods.length < columns) {

                       if (!(mods[mods.length-1] - 2)) {
                           break;
                       }

                       mods.push(mods[mods.length-1] - 2);
                    }

                    var percentTranslate = percent * this.translate, translate;

                    this.items.each(function(index) {
                        translate = mods.indexOf((index+1) % columns) != -1 ? percentTranslate : percentTranslate / 8;
                        $(this).css('transform', supports3d ? `translate3d(0, ${translate}px, 0)` : `translateY(${translate}px)`);
                    });
                },

                events: ['scroll']
            }
        ]

    });

}

function getColumnsCount(element) {

    var children = element.children();
    var first = children.filter(':visible:first');
    var top = first[0].offsetTop + first.outerHeight();

    for (var column=0;column<children.length;column++) {

        if (children[column].offsetTop >= top)  {
            break;
        }
    }

    return column || 1;
}

function percentageInViewport(element) {

    var top = element.offset().top;
    var height = element.outerHeight();
    var scrolltop = $(window).scrollTop();
    var wh = window.innerHeight;

    var distance, percentage, percent;

    if (top > (scrolltop + wh)) {
        percent = 0;
    } else if ((top + height) < scrolltop) {
        percent = 1;
    } else {

        if ((top + height) < wh) {
            percent = (scrolltop < wh ? scrolltop : scrolltop - wh) / (top+height);
        } else {

            distance   = (scrolltop + wh) - top;
            percentage = Math.round(distance / ((wh + height) / 100));
            percent    = percentage/100;
        }

        if (top < wh) {
            percent = percent * scrolltop / ((top + height) - wh);
        }
    }

    return percent > 1 ? 1:percent;
}

if (!BUNDLED && typeof window !== 'undefined' && window.UIkit) {
    window.UIkit.use(plugin);
}

export default plugin;
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies
 */

// Public node modules.
const _ = require('lodash');

/**
 * Expose main package JSON of the application
 * with basic info, dependencies, etc.
 */

module.exports = scope => {
  const cliPkg = scope.strapiPackageJSON || {};

  // Finally, return the JSON.
  return _.merge(scope.appPackageJSON || {}, {
    'name': `strapi-plugin-${scope.id}`,
    'version': '0.0.0',
    'description': 'This is the description of the plugin.',
    'strapi': {
      'name': scope.id,
      'icon': 'plug',
      'description': `Description of ${scope.id} plugin.`
    },
    'scripts': {
      'analyze:clean': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/rimraf stats.json',
      'preanalyze': 'npm run analyze:clean',
      'analyze': 'node ./node_modules/strapi-helper-plugin/lib/internals/scripts/analyze.js',
      'prebuild': 'npm run build:clean',
      'build:dev': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=development node ./node_modules/strapi-helper-plugin/node_modules/.bin/webpack --config node_modules/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js --color -p --progress',
      'build': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=production node node_modules/strapi-helper-plugin/node_modules/.bin/webpack --config node_modules/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js --color -p --progress',
      'build:clean': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/rimraf admin/build',
      'start': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=development node ./node_modules/strapi-helper-plugin/lib/server',
      "generate": "node ./node_modules/strapi-helper-plugin/node_modules/plop --plopfile ./node_modules/strapi-helper-plugin/lib/internals/generators/index.js",
      'lint': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/eslint --ignore-path .gitignore --ignore-pattern \'/admin/build/\' --config ./node_modules/strapi-helper-plugin/lib/internals/eslint/.eslintrc.json admin',
      'prettier': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/prettier --single-quote --trailing-comma es5 --write "{admin,__{tests,mocks}__}/**/*.js"',
      'test': 'npm run lint',
      'prepublishOnly': 'npm run build'
    },
    'dependencies': {},
    'devDependencies': {
      'strapi-helper-plugin': getDependencyVersion(cliPkg, 'strapi')
    },
    'author': {
      'name': scope.author || 'A Strapi developer',
      'email': scope.email || '',
      'url': scope.website || ''
    },
    'maintainers': [{
      'name': scope.author || 'A Strapi developer',
      'email': scope.email || '',
      'url': scope.website || ''
    }],
    'engines': {
      "node": ">= 10.0.0",
      "npm": ">= 6.0.0"
    },
    'license': scope.license || 'MIT'
  });
};

  /**
 * Get dependencies version
 */

function getDependencyVersion(packageJSON, module) {
  return module === packageJSON.name ? packageJSON.version : packageJSON.dependencies && packageJSON.dependencies[module];
}
<commit_msg>Fix PR feedback<commit_after>'use strict';

/**
 * Module dependencies
 */

// Public node modules.
const _ = require('lodash');

/**
 * Expose main package JSON of the application
 * with basic info, dependencies, etc.
 */

module.exports = scope => {
  const cliPkg = scope.strapiPackageJSON || {};

  // Finally, return the JSON.
  return _.merge(scope.appPackageJSON || {}, {
    'name': `strapi-plugin-${scope.id}`,
    'version': '0.0.0',
    'description': 'This is the description of the plugin.',
    'strapi': {
      'name': scope.id,
      'icon': 'plug',
      'description': `Description of ${scope.id} plugin.`
    },
    'scripts': {
      'analyze:clean': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/rimraf stats.json',
      'preanalyze': 'npm run analyze:clean',
      'analyze': 'node ./node_modules/strapi-helper-plugin/lib/internals/scripts/analyze.js',
      'prebuild': 'npm run build:clean',
      'build:dev': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=development node ./node_modules/strapi-helper-plugin/node_modules/.bin/webpack --config node_modules/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js --color -p --progress',
      'build': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=production node node_modules/strapi-helper-plugin/node_modules/.bin/webpack --config node_modules/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js --color -p --progress',
      'build:clean': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/rimraf admin/build',
      'start': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/cross-env NODE_ENV=development node ./node_modules/strapi-helper-plugin/lib/server',
      'generate': 'node ./node_modules/strapi-helper-plugin/node_modules/plop --plopfile ./node_modules/strapi-helper-plugin/lib/internals/generators/index.js',
      'lint': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/eslint --ignore-path .gitignore --ignore-pattern \'/admin/build/\' --config ./node_modules/strapi-helper-plugin/lib/internals/eslint/.eslintrc.json admin',
      'prettier': 'node ./node_modules/strapi-helper-plugin/node_modules/.bin/prettier --single-quote --trailing-comma es5 --write "{admin,__{tests,mocks}__}/**/*.js"',
      'test': 'npm run lint',
      'prepublishOnly': 'npm run build'
    },
    'dependencies': {},
    'devDependencies': {
      'strapi-helper-plugin': getDependencyVersion(cliPkg, 'strapi')
    },
    'author': {
      'name': scope.author || 'A Strapi developer',
      'email': scope.email || '',
      'url': scope.website || ''
    },
    'maintainers': [{
      'name': scope.author || 'A Strapi developer',
      'email': scope.email || '',
      'url': scope.website || ''
    }],
    'engines': {
      "node": ">= 10.0.0",
      "npm": ">= 6.0.0"
    },
    'license': scope.license || 'MIT'
  });
};

  /**
 * Get dependencies version
 */

function getDependencyVersion(packageJSON, module) {
  return module === packageJSON.name ? packageJSON.version : packageJSON.dependencies && packageJSON.dependencies[module];
}
<|endoftext|>


<commit_before>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2015 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';

YUI.add('machine-view-scale-up', function() {

  juju.components.MachineViewScaleUp = React.createClass({
    propTypes: {
      addGhostAndEcsUnits: React.PropTypes.func.isRequired,
      services: React.PropTypes.object.isRequired,
      toggleScaleUp: React.PropTypes.func.isRequired
    },

    /**
      Display a list of services.

      @method _generateServices
      @returns {Object} A unit list or onboarding.
    */
    _generateServices: function() {
      var components = [];
      var services = this.props.services.toArray();
      services.forEach((service) => {
        if (service.get('subordinate')) {
          return;
        }
        components.push(

            {service.get('name')}
            <input
              className="machine-view__scale-up-unit-input"
              placeholder="units"
              ref={'scaleUpUnit-' + service.get('id')}
              type="text" />

);
      });
      return (

          {components}

);
    },

    /**
      Add units to the services.

      @method _handleAddUnits
    */
    _handleAddUnits: function() {
      var re = /(scaleUpUnit-)(.*)/;
      Object.keys(this.refs).forEach((ref) => {
        var parts = re.exec(ref);
        if (parts) {
          var service = this.props.services.getById(parts[parts.length-1]);
          this.props.addGhostAndEcsUnits(service, this.refs[ref].value);
        }
      });
      this.props.toggleScaleUp();
    },

    render: function() {
      var buttons = [{
        action: this.props.toggleScaleUp,
        title: 'Cancel'
      }, {
        action: this._handleAddUnits,
        title: 'Add units',
        type: 'confirm'
      }];
      return (

          {this._generateServices()}
          <juju.components.ButtonRow buttons={buttons} />

      );
    }
  });
}, '0.1.0', {
  requires: [
    'button-row'
  ]
});
<commit_msg>review<commit_after>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2015 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';

YUI.add('machine-view-scale-up', function() {

  juju.components.MachineViewScaleUp = React.createClass({
    propTypes: {
      addGhostAndEcsUnits: React.PropTypes.func.isRequired,
      services: React.PropTypes.object.isRequired,
      toggleScaleUp: React.PropTypes.func.isRequired
    },

    /**
      Display a list of services.

      @method _generateServices
      @returns {Object} A unit list or onboarding.
    */
    _generateServices: function() {
      var components = [];
      var services = this.props.services.toArray();
      services.forEach((service) => {
        if (service.get('subordinate')) {
          return;
        }
        components.push(

            {service.get('name')}
            <input
              className="machine-view__scale-up-unit-input"
              placeholder="units"
              ref={'scaleUpUnit-' + service.get('id')}
              type="text" />

);
      });
      return (

          {components}

);
    },

    /**
      Add units to the services.

      @method _handleAddUnits
    */
    _handleAddUnits: function() {
      var re = /(scaleUpUnit-)(.*)/;
      Object.keys(this.refs).forEach((ref) => {
        var parts = re.exec(ref);
        if (parts) {
          var service = this.props.services.getById(parts[2]);
          this.props.addGhostAndEcsUnits(service, this.refs[ref].value);
        }
      });
      this.props.toggleScaleUp();
    },

    render: function() {
      var buttons = [{
        action: this.props.toggleScaleUp,
        title: 'Cancel'
      }, {
        action: this._handleAddUnits,
        title: 'Add units',
        type: 'confirm'
      }];
      return (

          {this._generateServices()}
          <juju.components.ButtonRow buttons={buttons} />

      );
    }
  });
}, '0.1.0', {
  requires: [
    'button-row'
  ]
});
<|endoftext|>


<commit_before>L.DNC = L.DNC || {};
/*
**
** AppController is the primary point of app initialization. It basically 'ties
** every together'
**
*/
L.DNC.AppController = L.Class.extend({

    statics: {},

    // default options
    options: {},

    // L.Class.extend automatically executes 'initialize'
    initialize : function( options ) {

        // override defaults with passed options
        L.setOptions(this, options);

        this.mapView = new L.DNC.MapView();
        this.dropzone = new L.DNC.DropZone( this.mapView._map, {} );
        this.layerlist = new L.DNC.LayerList( { layerContainerId: 'dropzone' } ).addTo( this.mapView._map );

        this.menubar = new L.DNC.MenuBar( { id: 'menu-bar' } ).addTo( document.body );

        // build out menus
        this.menus = {
            geo: new L.DNC.Menu('Geoprocessing', {  // New dropdown menu
                items: ['buffer', 'union']          // Items in menu
            }).addTo( this.menubar )                // Append to menubar
        };

        this.geoOpsConfig = {
            operations: new L.DNC.Geo(),        // Configurations of GeoOperations
            executor: new L.DNC.GeoExecute()    // Executor of GeoOperations
        };

        this.forms = new L.DNC.Forms();

        this.notification = new L.DNC.Notifications();
        this._addEventHandlers();

    },

    _addEventHandlers: function(){
        this.dropzone.fileReader.on( 'fileparsed', this._handleParsedFile.bind( this ) );

        // Handle clicks on items within geoMenu
        // NOTE: This is where an operation is tied to a menu item
        this.menus.geo.on( 'clickedOperation', this._handleOperationClick.bind( this, this.geoOpsConfig ) );
    },

    /*
    **
    ** Lookup operation by name from operations configuration, validate selection,
    ** and render appropriate form
    **
    */
    _handleOperationClick: function( opsConfig, e ) {
        var config = opsConfig.operations[e.action];
        try {
            opsConfig.executor.validate( this.getLayerSelection(), config );
        }
        catch(err) {
            this.notification.add({
                text: err.message,
                type: 'alert',
                time: 2500
            });
            console.error(err);
            return;
        }

        var form = this.forms.render( e.action, config );
        form.on( 'submit', this._handleFormSubmit.bind( this, opsConfig ) );
    },

    /*
    **
    ** Take input from an options form, use input to execute operation, get new
    ** layer, pass new layer off to be added to map.
    **
    */
    _handleFormSubmit: function( opsConfig, e ) {
        var config = opsConfig.operations[e.action];
        var results = opsConfig.executor.execute(
            e.action,
            e.parameters,
            config,
            this.getLayerSelection()
        );

        if (config.createsLayer) {
            this._handleGeoResult(results);
        } else {
            // TODO: Handle non-geo results
        }
    },

    /*
    **
    ** Take newly parsed file, add to make and layerlist
    **
    */
    _handleParsedFile: function( e ) {
        var layer = L.mapbox.featureLayer( e.file );
        this.layerlist.addLayerToList( layer, e.fileInfo.name, true );
        this.mapView.numLayers++;

        this.notification.add({
            text: '' + e.fileInfo.name + ' added successfully.',
            type: 'success',
            time: 2500
        });
    },

    /*
    **
    ** Take new layer, add to map and layerlist
    **
    */
    _handleGeoResult: function( layer ) {
        var mapLayer = L.mapbox.featureLayer( layer.geometry );
        this.layerlist.addLayerToList( mapLayer, layer.name, true );

        this.notification.add({
            text: '' + layer.name + ' created successfully.',
            type: 'success',
            time: 2500
        });
    },

    getLayerSelection: function(){
        return this.layerlist.selection.list || [];
    }

});
<commit_msg>Comment<commit_after>L.DNC = L.DNC || {};
/*
**
** AppController is the primary point of app initialization. It basically 'ties
** every together'
**
*/
L.DNC.AppController = L.Class.extend({

    statics: {},

    // default options
    options: {},

    // L.Class.extend automatically executes 'initialize'
    initialize : function( options ) {

        // override defaults with passed options
        L.setOptions(this, options);

        this.mapView = new L.DNC.MapView();
        this.dropzone = new L.DNC.DropZone( this.mapView._map, {} );
        this.layerlist = new L.DNC.LayerList( { layerContainerId: 'dropzone' } ).addTo( this.mapView._map );

        this.menubar = new L.DNC.MenuBar( { id: 'menu-bar' } ).addTo( document.body );

        // build out menus
        this.menus = {
            geo: new L.DNC.Menu('Geoprocessing', {  // New dropdown menu
                items: ['buffer', 'union']          // Items in menu
            }).addTo( this.menubar )                // Append to menubar
        };

        this.geoOpsConfig = {
            operations: new L.DNC.Geo(),        // Configurations of GeoOperations
            executor: new L.DNC.GeoExecute()    // Executor of GeoOperations
        };

        this.forms = new L.DNC.Forms();

        this.notification = new L.DNC.Notifications();
        this._addEventHandlers();

    },

    _addEventHandlers: function(){
        this.dropzone.fileReader.on( 'fileparsed', this._handleParsedFile.bind( this ) );

        // Handle clicks on items within geoMenu
        // NOTE: This is where an operation is tied to a menu item
        this.menus.geo.on( 'clickedOperation', this._handleOperationClick.bind( this, this.geoOpsConfig ) );
    },

    /*
    **
    ** Lookup operation by name from operations configuration, validate selection,
    ** and render appropriate form
    **
    */
    _handleOperationClick: function( opsConfig, e ) {
        var config = opsConfig.operations[e.action];

        // Validate selection
        try {
            opsConfig.executor.validate( this.getLayerSelection(), config );
        }
        catch(err) {
            this.notification.add({
                text: err.message,
                type: 'alert',
                time: 2500
            });
            console.error(err);
            return;
        }

        var form = this.forms.render( e.action, config );
        form.on( 'submit', this._handleFormSubmit.bind( this, opsConfig ) );
    },

    /*
    **
    ** Take input from an options form, use input to execute operation, get new
    ** layer, pass new layer off to be added to map.
    **
    */
    _handleFormSubmit: function( opsConfig, e ) {
        var config = opsConfig.operations[e.action];
        var results = opsConfig.executor.execute(
            e.action,
            e.parameters,
            config,
            this.getLayerSelection()
        );

        if (config.createsLayer) {
            this._handleGeoResult(results);
        } else {
            // TODO: Handle non-geo results
        }
    },

    /*
    **
    ** Take newly parsed file, add to make and layerlist
    **
    */
    _handleParsedFile: function( e ) {
        var layer = L.mapbox.featureLayer( e.file );
        this.layerlist.addLayerToList( layer, e.fileInfo.name, true );
        this.mapView.numLayers++;

        this.notification.add({
            text: '' + e.fileInfo.name + ' added successfully.',
            type: 'success',
            time: 2500
        });
    },

    /*
    **
    ** Take new layer, add to map and layerlist
    **
    */
    _handleGeoResult: function( layer ) {
        var mapLayer = L.mapbox.featureLayer( layer.geometry );
        this.layerlist.addLayerToList( mapLayer, layer.name, true );

        this.notification.add({
            text: '' + layer.name + ' created successfully.',
            type: 'success',
            time: 2500
        });
    },

    getLayerSelection: function(){
        return this.layerlist.selection.list || [];
    }

});
<|endoftext|>


<commit_before>/* eslint no-unused-vars: ["off"]*/
/* eslint comma-dangle: ["error", "only-multiline"]*/
/* global window:false, document:false requestAnimationFrame:false cancelAnimationFrame: false */

import * as THREE from 'three';
import UTMLatLng from 'utm-latlng';
import Config from './config.js';
import PyriteLoader from './loader.js';
import FlyControls from './FlyControls.js';
import CTMLoader from './ctm/CTMLoader.js';
import PyriteException from './pyriteexception.js';

require('./skyShader.js');

class Pyrite {
  constructor(container, set, modelConfig, lod, maxlod, debug) {
    if (!container) {
      throw new PyriteException('container must be specified', 'Pyrite');
    }

    console.log(container);
    console.log(`${container.clientHeight} ${container.clientWidth}`);

    this.animationStops = [];
    this.previousAnimationStops = [];

    this.modelConfig = modelConfig || {};

    this.container = container;
    this.clock = new THREE.Clock();
    this.cameraRig = new THREE.Object3D();
    this.cameraRigDummy = new THREE.Object3D();
    this.cameraPos = new THREE.Vector3();
    this.cameraRot = new THREE.Euler();

    this.config = new Config();

    this.config.lod = parseInt(lod, 10) || this.config.lod;
    this.config.maxlod = parseInt(maxlod, 10) || this.config.maxlod;
    this.config.set = set || this.config.set;
    this.config.version = this.modelConfig.version || this.config.version;
    this.config.fmt = 'ctm';
    this.config.debug = parseInt(debug, 10) || this.config.debug;

    this.cubeDetectorGroup = new THREE.Object3D();
    this.modelMeshGroup = new THREE.Object3D();

    this.loader = new PyriteLoader(this, this.config);
    this.loader.handleInitialCubesLoaded = this.handleInitialCubesLoaded.bind(this);
    this.loader.isUpgradingEnabled = this.isUpgradingEnabled.bind(this);
    this.camera = new THREE.PerspectiveCamera(50, this.container.clientWidth / this.container.clientHeight, 1, 9000);
    this.camera.updateProjectionMatrix();
    this.cameraRig.add(this.camera);
    this.resetCamera();

    this.scene = new THREE.Scene();
    this.uiScene = new THREE.Scene();

    const ambient = new THREE.AmbientLight(0xffffff, 0.5);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.75);
    directionalLight.position.set(0, 1, 0);
    this.scene.add(directionalLight);
    this.scene.add(ambient);
    this.uiScene.add(this.cameraRig);

    const uiAmbient = new THREE.AmbientLight(0xffffff, 0.8);
    const uiDirectionalLight = new THREE.DirectionalLight(0xffffff, 0.75);
    directionalLight.position.set(0, 1, 0);
    this.uiScene.add(uiAmbient);
    this.uiScene.add(uiDirectionalLight);

    this.up = new THREE.Vector3(0, 1, 0);
    this.manualLookTargetPosition = new THREE.Vector3();

    this.scene.add(this.cubeDetectorGroup);
    this.scene.add(this.modelMeshGroup);

    const utm = new UTMLatLng();

    this.started = false;

    const ctmloader = new CTMLoader();

    this.renderer = new THREE.WebGLRenderer({ antialias: true });
    this.renderer.autoClear = false;
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
    container.appendChild(this.renderer.domElement);

    this.controls = new FlyControls(this.cameraRig);
    this.controls.movementSpeed = 20.0;
    this.controls.rollSpeed = (Math.PI / 24) * 2;

    this.mousePosition = new THREE.Vector2();
    this.raycaster = new THREE.Raycaster();

    this.controls.autoForward = false;
    this.controls.dragToLook = true;

    // // skybox
    this.initSky();

    this.debugStats = false;

    if (this.debugStats) {
      this.statsContainer = document.createElement('div');
      this.statsContainer.style.position = 'absolute';
      this.statsContainer.style.width = 100;
      this.statsContainer.style.height = 100;
      this.statsContainer.style.backgroundColor = 'blue';
      this.statsContainer.style.top = '20px';
      this.statsContainer.style.left = '20px';
      document.body.appendChild(this.statsContainer);
    }

    this.onWindowResize = this.onWindowResize.bind(this);
    window.addEventListener('resize', this.onWindowResize, false);

    this.nextAnimationRequestId = null;

    this.lookTarget = null;

    this.animate = this.animate.bind(this);
  }

  initSky() {
    // Add Sky Mesh
    const sky = new THREE.Sky();
    this.scene.add(sky.mesh);

    // Add Sun Helper
    const sunSphere = new THREE.Mesh(
      new THREE.SphereBufferGeometry(20000, 16, 8),
      new THREE.MeshBasicMaterial({ color: 0xffffff })
    );
    sunSphere.position.y = -700000;
    sunSphere.visible = false;
    this.scene.add(sunSphere);

    // / GUI

    const effectController = {
      turbidity: 1,
      reileigh: 0.3,
      mieCoefficient: 0.0054,
      mieDirectionalG: 0.38,
      luminance: 1,
      inclination: 0.47, // elevation / inclination
      azimuth: 0.26, // Facing front,
      sun: !true,
    };

    const distance = 400000;

    const uniforms = sky.uniforms;
    uniforms.turbidity.value = effectController.turbidity;
    uniforms.reileigh.value = effectController.reileigh;
    uniforms.luminance.value = effectController.luminance;
    uniforms.mieCoefficient.value = effectController.mieCoefficient;
    uniforms.mieDirectionalG.value = effectController.mieDirectionalG;

    const theta = Math.PI * (effectController.inclination - 0.5);
    const phi = 2 * Math.PI * (effectController.azimuth - 0.5);

    sunSphere.position.x = distance * Math.cos(phi);
    sunSphere.position.y = distance * Math.sin(phi) * Math.sin(theta);
    sunSphere.position.z = distance * Math.sin(phi) * Math.cos(theta);

    sunSphere.visible = effectController.sun;

    sky.uniforms.sunPosition.value.copy(sunSphere.position);
  }

  setCamera(positon, euler, lookAt) {
    this.cameraPos = positon;
    this.cameraRot = euler;
    this.cameraRig.position.set(positon.x, positon.y, positon.z);
    this.camera.rotation.set(euler.x, euler.y, euler.z, euler.order);
  }

  resetCamera() {
    this.cameraRig.position.set(this.cameraPos.x, this.cameraPos.y + 700, this.cameraPos.z);
    this.camera.rotation.set(
      this.cameraRot.x,
      this.cameraRot.y,
      this.cameraRot.z,
      this.cameraRot.order
    );
  }

  onWindowResize() {
    this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);

    this.nextAnimationRequestId = requestAnimationFrame(this.animate);
  }

  isUpgradingEnabled() {
    return this.controls.moveVector.length() === 0 &&
      this.controls.rotationVector.length() === 0;
  }

  animate(time) {
    if (!this.clock) {
      return;
    }
    this.nextAnimationRequestId = requestAnimationFrame(this.animate);
    const delta = this.clock.getDelta();
    let distanceToLookAtTarget = null;
    if (this.lookTarget) {
      distanceToLookAtTarget = this.cameraRig.position.distanceTo(this.lookTarget.position);
    }

    this.controls.update(delta);
    if (distanceToLookAtTarget) {
      this.camera.getWorldDirection(this.manualLookTargetPosition);
      this.manualLookTargetPosition.setLength(distanceToLookAtTarget);
      this.manualLookTargetPosition.add(this.cameraRig.position);
    }

    this.update();

    // this MUST be the last call of animate
    this.render();
  }

  update() {
    this.loader.update(this.cameraRig);
  }

  render() {
    this.renderer.clear();
    this.renderer.render(this.scene, this.camera);
    this.renderer.clearDepth();
    this.renderer.render(this.uiScene, this.camera);
  }

  start() {
    this.started = true;
    this.startTime = Date.now();
    this.loader.load(this.cameraRig, this.handleLoadCompleted.bind(this));
    this.nextAnimationRequestId = requestAnimationFrame(this.animate);
  }

  handleLoadCompleted() {
    // TODO: Put camera somewhere the model can be seen
  }

  getNewRotationXY(position) {
    this.cameraRigDummy.position.copy(this.cameraRig.position);
    this.cameraRigDummy.rotation.copy(this.cameraRig.rotation);

    this.cameraRigDummy.up = this.up;
    this.cameraRigDummy.rotation.order = 'YXZ';
    this.cameraRigDummy.lookAt(position);

    return {
      x: this.cameraRigDummy.rotation.x * -1,
      y: this.cameraRigDummy.rotation.y + THREE.Math.degToRad(180),
    };
  }

  cameraRigLookAt(position) {
    if (!this.lookTarget) {
      this.lookTarget = new THREE.Object3D();
    }
    this.lookTarget.position.copy(position);

    const { x, y } = this.getNewRotationXY(position);

    this.camera.rotation.order = 'YXZ';
    this.camera.rotation.x = x;
    this.camera.rotation.y = 0;
    this.camera.rotation.z = 0;

    this.cameraRig.rotation.x = 0;
    this.cameraRig.rotation.z = 0;
    this.cameraRig.rotation.y = y;
  }

  handleInitialCubesLoaded() {
    console.log(`InitialLoad Time ${Date.now() - this.startTime}`);
    if (this.lastNavigationTarget) {
      this.moveCameraToHole(this.lastNavigationTarget);
      delete this.lastNavigationTarget;
    }
  }

  moveCameraToDefaultPosition() {
    if (this.modelConfig && this.modelConfig.defaultCameraSettings) {
      const cameraSettings = this.modelConfig.defaultCameraSettings;
      const rigPosition = cameraSettings.rigPosition;
      const rigRotation = cameraSettings.rigRotation;
      const cameraRotation = cameraSettings.cameraRotation;
      if (!this.lookTarget) {
        this.lookTarget = new THREE.Object3D();
      }

      if (cameraSettings.lookTarget) {
        this.lookTarget.position.copy(cameraSettings.lookTarget);
      }

      this.cameraRig.position.set(rigPosition.x, rigPosition.y, rigPosition.z);
      this.cameraRig.rotation.order = rigRotation.order;
      this.cameraRig.rotation.x = rigRotation.x;
      this.cameraRig.rotation.y = rigRotation.y;
      this.cameraRig.rotation.z = rigRotation.z;

      this.camera.rotation.order = cameraRotation.order;
      this.camera.rotation.x = cameraRotation.x;
      this.camera.rotation.y = cameraRotation.y;
      this.camera.rotation.z = cameraRotation.z;
    }
  }

  stop() {
    clearTimeout(this.timerToken);
  }

  shutdown() {
    // Probably need to dispose of everything Three js related
    // TODO: Validate the following
    // https://stackoverflow.com/questions/21453309/how-can-i-destroy-threejs-scene
    cancelAnimationFrame(this.nextAnimationRequestId);

    this.controls.dispose();
    delete this.controls;

    // this.cleanSkybox();

    this.loader.unload();
    this.scene.remove(this.cubeDetectorGroup);
    this.scene.remove(this.modelMeshGroup);
    delete this.loader.handleInitialCubesLoaded;
    delete this.loader.isUpgradingEnabled;
    delete this.loader;
    delete this.scene;
    delete this.uiScene;
    delete this.camera;
    delete this.cameraRig;
    delete this.cameraRigDummy;
    delete this.renderer;
    delete this.controls;
    delete this.cubeDetectorGroup;
    delete this.modelMeshGroup;

    delete this.lookTarget;

    window.removeEventListener('resize', this.onWindowResize, false);
    while (this.container.lastChild) { this.container.removeChild(this.container.lastChild); }

    if (this.debugStats) {
      document.body.removeChild(this.statsContainer);
    }

    this.clock.stop();
    delete this.clock;
  }
}

export default Pyrite;
<commit_msg>Fix lint issues (pyrite.js)<commit_after>/* eslint no-unused-vars: ["off"]*/
/* eslint comma-dangle: ["error", "only-multiline"]*/
/* global window:false, document:false requestAnimationFrame:false cancelAnimationFrame: false */

import * as THREE from 'three';
import UTMLatLng from 'utm-latlng';
import Config from './config.js';
import PyriteLoader from './loader.js';
import FlyControls from './FlyControls.js';
import CTMLoader from './ctm/CTMLoader.js';
import PyriteException from './pyriteexception.js';

require('./skyShader.js');

class Pyrite {
  constructor(container, set, modelConfig, lod, maxlod, debug) {
    if (!container) {
      throw new PyriteException('container must be specified', 'Pyrite');
    }

    console.log(container);
    console.log(`${container.clientHeight} ${container.clientWidth}`);

    this.animationStops = [];
    this.previousAnimationStops = [];

    this.modelConfig = modelConfig || {};

    this.container = container;
    this.clock = new THREE.Clock();
    this.cameraRig = new THREE.Object3D();
    this.cameraRigDummy = new THREE.Object3D();
    this.cameraPos = new THREE.Vector3();
    this.cameraRot = new THREE.Euler();

    this.config = new Config();

    this.config.lod = parseInt(lod, 10) || this.config.lod;
    this.config.maxlod = parseInt(maxlod, 10) || this.config.maxlod;
    this.config.set = set || this.config.set;
    this.config.version = this.modelConfig.version || this.config.version;
    this.config.fmt = 'ctm';
    this.config.debug = parseInt(debug, 10) || this.config.debug;

    this.cubeDetectorGroup = new THREE.Object3D();
    this.modelMeshGroup = new THREE.Object3D();

    this.loader = new PyriteLoader(this, this.config);
    this.loader.handleInitialCubesLoaded = this.handleInitialCubesLoaded.bind(this);
    this.loader.isUpgradingEnabled = this.isUpgradingEnabled.bind(this);
    this.camera = new THREE.PerspectiveCamera(
      50,
      this.container.clientWidth / this.container.clientHeight,
      1,
      9000
    );
    this.camera.updateProjectionMatrix();
    this.cameraRig.add(this.camera);
    this.resetCamera();

    this.scene = new THREE.Scene();
    this.uiScene = new THREE.Scene();

    const ambient = new THREE.AmbientLight(0xffffff, 0.5);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.75);
    directionalLight.position.set(0, 1, 0);
    this.scene.add(directionalLight);
    this.scene.add(ambient);
    this.uiScene.add(this.cameraRig);

    const uiAmbient = new THREE.AmbientLight(0xffffff, 0.8);
    const uiDirectionalLight = new THREE.DirectionalLight(0xffffff, 0.75);
    directionalLight.position.set(0, 1, 0);
    this.uiScene.add(uiAmbient);
    this.uiScene.add(uiDirectionalLight);

    this.up = new THREE.Vector3(0, 1, 0);
    this.manualLookTargetPosition = new THREE.Vector3();

    this.scene.add(this.cubeDetectorGroup);
    this.scene.add(this.modelMeshGroup);

    const utm = new UTMLatLng();

    this.started = false;

    const ctmloader = new CTMLoader();

    this.renderer = new THREE.WebGLRenderer({ antialias: true });
    this.renderer.autoClear = false;
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
    container.appendChild(this.renderer.domElement);

    this.controls = new FlyControls(this.cameraRig);
    this.controls.movementSpeed = 20.0;
    this.controls.rollSpeed = (Math.PI / 24) * 2;

    this.mousePosition = new THREE.Vector2();
    this.raycaster = new THREE.Raycaster();

    this.controls.autoForward = false;
    this.controls.dragToLook = true;

    // // skybox
    this.initSky();

    this.debugStats = false;

    if (this.debugStats) {
      this.statsContainer = document.createElement('div');
      this.statsContainer.style.position = 'absolute';
      this.statsContainer.style.width = 100;
      this.statsContainer.style.height = 100;
      this.statsContainer.style.backgroundColor = 'blue';
      this.statsContainer.style.top = '20px';
      this.statsContainer.style.left = '20px';
      document.body.appendChild(this.statsContainer);
    }

    this.onWindowResize = this.onWindowResize.bind(this);
    window.addEventListener('resize', this.onWindowResize, false);

    this.nextAnimationRequestId = null;

    this.lookTarget = null;

    this.animate = this.animate.bind(this);
  }

  initSky() {
    // Add Sky Mesh
    const sky = new THREE.Sky();
    this.scene.add(sky.mesh);

    // Add Sun Helper
    const sunSphere = new THREE.Mesh(
      new THREE.SphereBufferGeometry(20000, 16, 8),
      new THREE.MeshBasicMaterial({ color: 0xffffff })
    );
    sunSphere.position.y = -700000;
    sunSphere.visible = false;
    this.scene.add(sunSphere);

    // / GUI

    const effectController = {
      turbidity: 1,
      reileigh: 0.3,
      mieCoefficient: 0.0054,
      mieDirectionalG: 0.38,
      luminance: 1,
      inclination: 0.47, // elevation / inclination
      azimuth: 0.26, // Facing front,
      sun: !true,
    };

    const distance = 400000;

    const uniforms = sky.uniforms;
    uniforms.turbidity.value = effectController.turbidity;
    uniforms.reileigh.value = effectController.reileigh;
    uniforms.luminance.value = effectController.luminance;
    uniforms.mieCoefficient.value = effectController.mieCoefficient;
    uniforms.mieDirectionalG.value = effectController.mieDirectionalG;

    const theta = Math.PI * (effectController.inclination - 0.5);
    const phi = 2 * Math.PI * (effectController.azimuth - 0.5);

    sunSphere.position.x = distance * Math.cos(phi);
    sunSphere.position.y = distance * Math.sin(phi) * Math.sin(theta);
    sunSphere.position.z = distance * Math.sin(phi) * Math.cos(theta);

    sunSphere.visible = effectController.sun;

    sky.uniforms.sunPosition.value.copy(sunSphere.position);
  }

  setCamera(positon, euler, lookAt) {
    this.cameraPos = positon;
    this.cameraRot = euler;
    this.cameraRig.position.set(positon.x, positon.y, positon.z);
    this.camera.rotation.set(euler.x, euler.y, euler.z, euler.order);
  }

  resetCamera() {
    this.cameraRig.position.set(this.cameraPos.x, this.cameraPos.y + 700, this.cameraPos.z);
    this.camera.rotation.set(
      this.cameraRot.x,
      this.cameraRot.y,
      this.cameraRot.z,
      this.cameraRot.order
    );
  }

  onWindowResize() {
    this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);

    this.nextAnimationRequestId = requestAnimationFrame(this.animate);
  }

  isUpgradingEnabled() {
    return this.controls.moveVector.length() === 0 &&
      this.controls.rotationVector.length() === 0;
  }

  animate(time) {
    if (!this.clock) {
      return;
    }
    this.nextAnimationRequestId = requestAnimationFrame(this.animate);
    const delta = this.clock.getDelta();
    let distanceToLookAtTarget = null;
    if (this.lookTarget) {
      distanceToLookAtTarget = this.cameraRig.position.distanceTo(this.lookTarget.position);
    }

    this.controls.update(delta);
    if (distanceToLookAtTarget) {
      this.camera.getWorldDirection(this.manualLookTargetPosition);
      this.manualLookTargetPosition.setLength(distanceToLookAtTarget);
      this.manualLookTargetPosition.add(this.cameraRig.position);
    }

    this.update();

    // this MUST be the last call of animate
    this.render();
  }

  update() {
    this.loader.update(this.cameraRig);
  }

  render() {
    this.renderer.clear();
    this.renderer.render(this.scene, this.camera);
    this.renderer.clearDepth();
    this.renderer.render(this.uiScene, this.camera);
  }

  start() {
    this.started = true;
    this.startTime = Date.now();
    this.loader.load(this.cameraRig, () => {
      // TODO: Hanlde load completed
    });
    this.nextAnimationRequestId = requestAnimationFrame(this.animate);
  }

  getNewRotationXY(position) {
    this.cameraRigDummy.position.copy(this.cameraRig.position);
    this.cameraRigDummy.rotation.copy(this.cameraRig.rotation);

    this.cameraRigDummy.up = this.up;
    this.cameraRigDummy.rotation.order = 'YXZ';
    this.cameraRigDummy.lookAt(position);

    return {
      x: this.cameraRigDummy.rotation.x * -1,
      y: this.cameraRigDummy.rotation.y + THREE.Math.degToRad(180),
    };
  }

  cameraRigLookAt(position) {
    if (!this.lookTarget) {
      this.lookTarget = new THREE.Object3D();
    }
    this.lookTarget.position.copy(position);

    const { x, y } = this.getNewRotationXY(position);

    this.camera.rotation.order = 'YXZ';
    this.camera.rotation.x = x;
    this.camera.rotation.y = 0;
    this.camera.rotation.z = 0;

    this.cameraRig.rotation.x = 0;
    this.cameraRig.rotation.z = 0;
    this.cameraRig.rotation.y = y;
  }

  handleInitialCubesLoaded() {
    console.log(`InitialLoad Time ${Date.now() - this.startTime}`);
    if (this.lastNavigationTarget) {
      this.moveCameraToHole(this.lastNavigationTarget);
      delete this.lastNavigationTarget;
    }
  }

  moveCameraToDefaultPosition() {
    if (this.modelConfig && this.modelConfig.defaultCameraSettings) {
      const cameraSettings = this.modelConfig.defaultCameraSettings;
      const rigPosition = cameraSettings.rigPosition;
      const rigRotation = cameraSettings.rigRotation;
      const cameraRotation = cameraSettings.cameraRotation;
      if (!this.lookTarget) {
        this.lookTarget = new THREE.Object3D();
      }

      if (cameraSettings.lookTarget) {
        this.lookTarget.position.copy(cameraSettings.lookTarget);
      }

      this.cameraRig.position.set(rigPosition.x, rigPosition.y, rigPosition.z);
      this.cameraRig.rotation.order = rigRotation.order;
      this.cameraRig.rotation.x = rigRotation.x;
      this.cameraRig.rotation.y = rigRotation.y;
      this.cameraRig.rotation.z = rigRotation.z;

      this.camera.rotation.order = cameraRotation.order;
      this.camera.rotation.x = cameraRotation.x;
      this.camera.rotation.y = cameraRotation.y;
      this.camera.rotation.z = cameraRotation.z;
    }
  }

  stop() {
    clearTimeout(this.timerToken);
  }

  shutdown() {
    // Probably need to dispose of everything Three js related
    // TODO: Validate the following
    // https://stackoverflow.com/questions/21453309/how-can-i-destroy-threejs-scene
    cancelAnimationFrame(this.nextAnimationRequestId);

    this.controls.dispose();
    delete this.controls;

    // this.cleanSkybox();

    this.loader.unload();
    this.scene.remove(this.cubeDetectorGroup);
    this.scene.remove(this.modelMeshGroup);
    delete this.loader.handleInitialCubesLoaded;
    delete this.loader.isUpgradingEnabled;
    delete this.loader;
    delete this.scene;
    delete this.uiScene;
    delete this.camera;
    delete this.cameraRig;
    delete this.cameraRigDummy;
    delete this.renderer;
    delete this.controls;
    delete this.cubeDetectorGroup;
    delete this.modelMeshGroup;

    delete this.lookTarget;

    window.removeEventListener('resize', this.onWindowResize, false);
    while (this.container.lastChild) { this.container.removeChild(this.container.lastChild); }

    if (this.debugStats) {
      document.body.removeChild(this.statsContainer);
    }

    this.clock.stop();
    delete this.clock;
  }
}

export default Pyrite;
<|endoftext|>


<commit_before>xdescribe('mdDataTableColumnDirective', function(){
    var $compile,
        $rootScope,
        $scope,
        $controller,
        element;

    var DIRECTIVE_DEFAULT_CASE = 'DIRECTIVE_DEFAULT_CASE';

    beforeEach(module('templates'));
    beforeEach(module('mdDataTable'));

    beforeEach(inject(function($injector){
        $compile = $injector.get('$compile');
        $rootScope = $injector.get('$rootScope');
        $controller = $injector.get('$controller');

        $scope = $rootScope.$new();
    }));

    xdescribe('WHEN created', function(){
        beforeEach(function(){
            //given

            //when
            compileDirective();
        });

        it('THEN it should have the required methods', function(){
            //then
            console.log(element.isolateScope().getColumnAlignClass);
            console.log(element.isolateScope().direction);

            expect(element.isolateScope().getColumnAlignClass).toBeDefined();
        });
    });
/*
    describe('WHEN calling getColumnClass AND the scope.alignRule is undefined', function(){
        it('THEN it should return the left aligned value', function(){
            //given
            compileDirective(DIRECTIVE_WITHOUT_PARAMS);

            //when
            var resultedClass = element.isolateScope().getColumnClass();

            expect(resultedClass).toEqual(leftAlignedCssClass);
        });
    });

    describe('WHEN calling getColumnClass AND the scope.alignRule is left', function(){
        it('THEN it should return the left aligned value', function(){
            //given
            compileDirective(DIRECTIVE_WITH_LEFT_ALIGNED_PARAM);

            //when
            var resultedClass = element.isolateScope().getColumnClass();

            expect(resultedClass).toEqual(leftAlignedCssClass);
        });
    });

    describe('WHEN calling getColumnClass AND the scope.alignRule is right', function(){
        it('THEN it should return the right aligned value', function(){
            //given
            compileDirective(DIRECTIVE_WITH_RIGHT_ALIGNED_PARAM);

            //when
            var resultedClass = element.isolateScope().getColumnClass();

            expect(resultedClass).toEqual(rightAlignedCssClass);
        });
    });
*/
    function compileDirective(status){
        switch(status){
            case DIRECTIVE_DEFAULT_CASE:
            default:
                element = $compile('' +
                    '<md-data-table>' +
                    '   <md-data-table-header-row>' +
                    '       <md-data-table-column align-rule="left">A Column</md-data-table-column>' +
                    '   <md-data-table-header-row>' +
                    '</md-data-table>')($scope);
                console.log('asdf')
                console.log(element.children().isolateScope());
                console.log('asdf')
        }

        $scope.$digest();
    }
});<commit_msg>some unit tests for mdDataTableColumnDirective<commit_after>describe('mdDataTableColumnDirective', function(){
    var $compile,
        $rootScope,
        $scope,
        element,
        elementScope;

    var DIRECTIVE_DEFAULT_CASE = 'DIRECTIVE_DEFAULT_CASE';

    beforeEach(module('templates'));
    beforeEach(module('mdDataTable'));

    beforeEach(inject(function($injector){
        $compile = $injector.get('$compile');
        $rootScope = $injector.get('$rootScope');

        $scope = $rootScope.$new();
    }));

    describe('WHEN created', function(){
        beforeEach(function(){
            //given

            //when
            compileDirective();
        });

        it('THEN it should have the required methods', function(){
            //then
            expect(elementScope.getColumnAlignClass).toBeDefined();
            expect(elementScope.ColumnOptionProvider).toBeDefined();
            expect(elementScope.columnOptions).toBeDefined();
            expect(elementScope.direction).toBeDefined();
            expect(elementScope.isSorted).toBeDefined();
            expect(elementScope.clickHandler).toBeDefined();
            expect(elementScope.isSortableColumns).toBeDefined();
        });
    });

    function compileDirective(status){
        switch(status){
            case DIRECTIVE_DEFAULT_CASE:
            default:
                element = $compile('' +
                    '<md-data-table>' +
                    '   <md-data-table-header-row>' +
                    '       <md-data-table-column align-rule="left">A Column</md-data-table-column>' +
                    '   </md-data-table-header-row>' +
                    '</md-data-table>')($scope);
        }

        $scope.$digest();

        elementScope = element.find('.ng-scope').scope().$parent;
    }
});<|endoftext|>


<commit_before>import { IconCloseSmallOutline, IconChevronDownSmallOutline } from '@teamleader/ui-icons';
import React, { forwardRef, useCallback } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';

import { Heading4, Monospaced, TextBodyCompact } from '../typography';
import Icon from '../icon';
import Box from '../box';
import Tooltip from '../tooltip';
import theme from './theme.css';

const TooltippedBox = Tooltip(Box);

export const Status = {
  ACTIVE: 'active',
  DEFAULT: 'default',
  DISABLED: 'disabled',
  FOCUSED: 'focused',
  INVALID: 'invalid',
  BROKEN: 'broken',
};

const ColorByStatus = {
  [Status.DEFAULT]: 'neutral',
  [Status.ACTIVE]: 'aqua',
  [Status.FOCUSED]: 'aqua',
  [Status.DISABLED]: 'neutral',
  [Status.INVALID]: 'gold',
  [Status.BROKEN]: 'ruby',
};

const BackgroundTintByStatus = {
  [Status.DEFAULT]: undefined,
  [Status.ACTIVE]: 'light',
  [Status.FOCUSED]: 'light',
  [Status.DISABLED]: undefined,
  [Status.INVALID]: 'lightest',
  [Status.BROKEN]: 'lightest',
};

const FilterSelection = ({ label, modificationText, applied, status, amountApplied, onClick, onClearClick }, ref) => {
  const showAmount = typeof amountApplied === 'number';
  const modified = typeof modificationText === 'string';

  const Container = modified ? TooltippedBox : Box;
  const tooltipProps = modified
    ? {
        tooltip: modificationText,
        tooltipColor: 'white',
        tooltipPosition: 'top',
        tooltipSize: 'small',
      }
    : {};

  const handleClearClick = useCallback(
    (event) => {
      event.stopPropagation();
      onClearClick();
    },
    [onClearClick],
  );

  return (
    <Box
      ref={ref}
      className={cx(theme['select-control'])}
      display="flex"
      boxSizing="border-box"
      paddingHorizontal={3}
      backgroundColor={ColorByStatus[status] || 'neutral'}
      backgroundTint={BackgroundTintByStatus[status] || undefined}
      borderWidth={applied ? 2 : 0}
      borderColor={ColorByStatus[status] || 'neutral'}
      borderTint="dark"
      borderRadius="rounded"
      onClick={onClick}
    >
      <Container
        display="flex"
        flex={1}
        alignItems="center"
        justifyContent="space-between"
        marginRight={2}
        {...tooltipProps}
      >
        <TextBodyCompact
          color={status === Status.DEFAULT ? 'teal' : ColorByStatus[status] || 'neutral'}
          tint={status === Status.DISABLED ? 'dark' : 'darkest'}
          marginRight={applied ? 2 : 0}
        >{`${label}${modified ? ' •' : ''}`}</TextBodyCompact>
        {applied && (
          <Box
            display="flex"
            alignItems="center"
            borderRadius="rounded"
            className={cx(theme['select-clear'], theme[`select-clear--${status}`])}
          >
            {showAmount && (
              <Heading4
                paddingLeft={2}
                paddingRight={1}
                color={ColorByStatus[status] || 'neutral'}
                tint="dark"
                borderTopLeftRadius="rounded"
                borderBottomLeftRadius="rounded"
                borderTopRightRadius={showAmount ? 'square' : 'rounded'}
                borderBottomRightRadius={showAmount ? 'square' : 'rounded'}
              >
                <Monospaced>{amountApplied}</Monospaced>
              </Heading4>
            )}
            <Box
              alignItems="center"
              display="flex"
              onClick={handleClearClick}
              onMouseDown={(event) => event.stopPropagation()}
              paddingHorizontal={1}
              borderTopLeftRadius={showAmount ? 'square' : 'rounded'}
              borderBottomLeftRadius={showAmount ? 'square' : 'rounded'}
              borderTopRightRadius="rounded"
              borderBottomRightRadius="rounded"
              className={theme['select-clear-icon']}
            >
              <Icon color={ColorByStatus[status] || 'neutral'} tint="dark" opacity={1}>
                <IconCloseSmallOutline />
              </Icon>
            </Box>
          </Box>
        )}
      </Container>
      <Icon
        color={status === Status.DEFAULT ? 'teal' : ColorByStatus[status] || 'neutral'}
        tint={status === Status.DISABLED ? 'dark' : 'darkest'}
        className={cx(
          theme['select-dropdown-indicator'],
          status === Status.FOCUSED && theme['select-dropdown-indicator--focused'],
        )}
      >
        <IconChevronDownSmallOutline />
      </Icon>
    </Box>
  );
};

FilterSelection.propTypes = {
  label: PropTypes.string,
  modificationText: PropTypes.string,
  applied: PropTypes.bool,
  status: PropTypes.oneOf(Object.values(Status)),
  amountApplied: PropTypes.number,
  onClick: PropTypes.func,
  onClearClick: PropTypes.func,
};

FilterSelection.defaultProps = {
  label: '',
  modificationText: null,
  applied: false,
  status: Status.DEFAULT,
  amountApplied: null,
  onClick: null,
  onClearClick: null,
};

export default forwardRef(FilterSelection);
<commit_msg>Don't fire handler if disabled<commit_after>import { IconCloseSmallOutline, IconChevronDownSmallOutline } from '@teamleader/ui-icons';
import React, { forwardRef } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';

import { Heading4, Monospaced, TextBodyCompact } from '../typography';
import Icon from '../icon';
import Box from '../box';
import Tooltip from '../tooltip';
import theme from './theme.css';

const TooltippedBox = Tooltip(Box);

export const Status = {
  ACTIVE: 'active',
  DEFAULT: 'default',
  DISABLED: 'disabled',
  FOCUSED: 'focused',
  INVALID: 'invalid',
  BROKEN: 'broken',
};

const ColorByStatus = {
  [Status.DEFAULT]: 'neutral',
  [Status.ACTIVE]: 'aqua',
  [Status.FOCUSED]: 'aqua',
  [Status.DISABLED]: 'neutral',
  [Status.INVALID]: 'gold',
  [Status.BROKEN]: 'ruby',
};

const BackgroundTintByStatus = {
  [Status.DEFAULT]: undefined,
  [Status.ACTIVE]: 'light',
  [Status.FOCUSED]: 'light',
  [Status.DISABLED]: undefined,
  [Status.INVALID]: 'lightest',
  [Status.BROKEN]: 'lightest',
};

const FilterSelection = ({ label, modificationText, applied, status, amountApplied, onClick, onClearClick }, ref) => {
  const showAmount = typeof amountApplied === 'number';
  const modified = typeof modificationText === 'string';

  const Container = modified ? TooltippedBox : Box;
  const tooltipProps = modified
    ? {
        tooltip: modificationText,
        tooltipColor: 'white',
        tooltipPosition: 'top',
        tooltipSize: 'small',
      }
    : {};

  return (
    <Box
      ref={ref}
      className={cx(theme['select-control'])}
      display="flex"
      boxSizing="border-box"
      paddingHorizontal={3}
      backgroundColor={ColorByStatus[status] || 'neutral'}
      backgroundTint={BackgroundTintByStatus[status] || undefined}
      borderWidth={applied ? 2 : 0}
      borderColor={ColorByStatus[status] || 'neutral'}
      borderTint="dark"
      borderRadius="rounded"
      onClick={status !== Status.DISABLED && onClick}
    >
      <Container
        display="flex"
        flex={1}
        alignItems="center"
        justifyContent="space-between"
        marginRight={2}
        {...tooltipProps}
      >
        <TextBodyCompact
          color={status === Status.DEFAULT ? 'teal' : ColorByStatus[status] || 'neutral'}
          tint={status === Status.DISABLED ? 'dark' : 'darkest'}
          marginRight={applied ? 2 : 0}
        >{`${label}${modified ? ' •' : ''}`}</TextBodyCompact>
        {applied && (
          <Box
            display="flex"
            alignItems="center"
            borderRadius="rounded"
            className={cx(theme['select-clear'], theme[`select-clear--${status}`])}
          >
            {showAmount && (
              <Heading4
                paddingLeft={2}
                paddingRight={1}
                color={ColorByStatus[status] || 'neutral'}
                tint="dark"
                borderTopLeftRadius="rounded"
                borderBottomLeftRadius="rounded"
                borderTopRightRadius={showAmount ? 'square' : 'rounded'}
                borderBottomRightRadius={showAmount ? 'square' : 'rounded'}
              >
                <Monospaced>{amountApplied}</Monospaced>
              </Heading4>
            )}
            <Box
              alignItems="center"
              display="flex"
              onClick={status !== Status.DISABLED && onClearClick}
              paddingHorizontal={1}
              borderTopLeftRadius={showAmount ? 'square' : 'rounded'}
              borderBottomLeftRadius={showAmount ? 'square' : 'rounded'}
              borderTopRightRadius="rounded"
              borderBottomRightRadius="rounded"
              className={theme['select-clear-icon']}
            >
              <Icon color={ColorByStatus[status] || 'neutral'} tint="dark" opacity={1}>
                <IconCloseSmallOutline />
              </Icon>
            </Box>
          </Box>
        )}
      </Container>
      <Icon
        color={status === Status.DEFAULT ? 'teal' : ColorByStatus[status] || 'neutral'}
        tint={status === Status.DISABLED ? 'dark' : 'darkest'}
        className={cx(
          theme['select-dropdown-indicator'],
          status === Status.FOCUSED && theme['select-dropdown-indicator--focused'],
        )}
      >
        <IconChevronDownSmallOutline />
      </Icon>
    </Box>
  );
};

FilterSelection.propTypes = {
  label: PropTypes.string,
  modificationText: PropTypes.string,
  applied: PropTypes.bool,
  status: PropTypes.oneOf(Object.values(Status)),
  amountApplied: PropTypes.number,
  onClick: PropTypes.func,
  onClearClick: PropTypes.func,
};

FilterSelection.defaultProps = {
  label: '',
  modificationText: null,
  applied: false,
  status: Status.DEFAULT,
  amountApplied: null,
  onClick: null,
  onClearClick: null,
};

export default forwardRef(FilterSelection);
<|endoftext|>