{"size": 5895, "ext": "js", "max_stars_repo_path": "client/coral-framework/services/plugins.js", "max_stars_repo_name": "MinnPost/coralproject-talk", "max_stars_repo_stars_event_min_datetime": null, "max_stars_repo_stars_event_max_datetime": null, "max_issues_repo_path": "client/coral-framework/services/plugins.js", "max_issues_repo_name": "MinnPost/coralproject-talk", "max_issues_repo_issues_event_min_datetime": null, "max_issues_repo_issues_event_max_datetime": null, "max_forks_repo_path": "client/coral-framework/services/plugins.js", "max_forks_repo_name": "MinnPost/coralproject-talk", "max_forks_repo_forks_event_min_datetime": null, "max_forks_repo_forks_event_max_datetime": null}
{"max_stars_count": null, "max_issues_count": null, "max_forks_count": null, "avg_line_length": 29.6231155779, "max_line_length": 176, "alphanum_fraction": 0.6276505513}
import React from 'react';
import uniq from 'lodash/uniq';
import pick from 'lodash/pick';
import merge from 'lodash/merge';
import flattenDeep from 'lodash/flattenDeep';
import isEmpty from 'lodash/isEmpty';
import flatten from 'lodash/flatten';
import mapValues from 'lodash/mapValues';
import {getDisplayName} from 'coral-framework/helpers/hoc';
import camelize from '../helpers/camelize';

// This is returned for pluginConfig when it is empty.
const emptyConfig = {};

// Memoize the warnings so we only show them once.
const memoizedWarnings = [];

// withWarnings decorates the props of queryData with a proxy that
// prints a warning when accessing deeper props.
function withWarnings(component, queryData) {
  if (process.env.NODE_ENV !== 'production' && window.Proxy) {

    // Show warnings when accessing queryData only when not in production.
    return mapValues(queryData, (value, key) => {

      // Keep null values..
      if (!queryData[key]) {
        return queryData[key];
      }
      return new Proxy(queryData[key], {
        get(target, name) {

          // Detect access from React DevTools and ignore those.
          const error = new Error();
          const accessFromDevTools = ['backend.js', 'dehydrate']
            .every((keyword) => error.stack && error.stack.includes(keyword));

          // Only care about the components defined in the plugins.
          if (component.talkPluginName && !accessFromDevTools) {
            const warning = `'${getDisplayName(component)}' of '${component.talkPluginName}' accessed '${key}.${name}' but did not define fragments using the withFragment HOC`;
            if (memoizedWarnings.indexOf(warning) === -1) {
              console.warn(warning);
              memoizedWarnings.push(warning);
            }
          }
          return queryData[key][name];
        }
      });
    });
  }

  return queryData;
}

function addMetaDataToSlotComponents(plugins) {

  // Add talkPluginName to Slot Components.
  plugins.forEach((plugin) => {
    const slots = plugin.module.slots;
    slots && Object.keys(slots).forEach((slot) => {
      slots[slot].forEach((component) => {

        // Attach plugin name to the component
        component.talkPluginName = plugin.name;
      });
    });
  });
}

class PluginsService {
  constructor(plugins) {
    this.plugins = plugins;
    addMetaDataToSlotComponents(plugins);
  }

  isSlotEmpty(slot, reduxState, props = {}, queryData = {}) {
    return this.getSlotElements(slot, reduxState, props, queryData).length === 0;
  }

  /**
   * getSlotComponentProps calculate the props we would pass to the slot component.
   * query datas are only passed to the component if it is defined in `component.fragments`.
   */
  getSlotComponentProps(component, reduxState, props, queryData) {
    const pluginConfig = reduxState.config.plugin_config || emptyConfig;
    return {
      ...props,
      config: pluginConfig,
      ...(
        component.fragments
          ? pick(queryData, Object.keys(component.fragments))
          : withWarnings(component, queryData)
      )
    };
  }

  /**
   * Returns React Elements for given slot.
   */
  getSlotElements(slot, reduxState, props = {}, queryData = {}) {
    const pluginConfig = reduxState.config.plugin_config || emptyConfig;

    const isDisabled = (component) => {
      if (
        pluginConfig &&
        pluginConfig[component.talkPluginName] &&
        pluginConfig[component.talkPluginName].disable_components
      ) {
        return true;
      }

      // Check if component is excluded.
      if(component.isExcluded) {
        let resolvedProps = this.getSlotComponentProps(component, reduxState, props, queryData);
        if (component.mapStateToProps) {
          resolvedProps = {...resolvedProps, ...component.mapStateToProps(reduxState)};
        }
        return component.isExcluded(resolvedProps);
      }

      return false;
    };

    return flatten(this.plugins
      .filter((o) => o.module.slots && o.module.slots[slot])
      .map((o) => o.module.slots[slot])
    )
      .map((component, i) => ({
        component,
        disabled: isDisabled(component),
        key: i,
      }))
      .filter((o) => !o.disabled)
      .map(({component, key}) =>
        React.createElement(component, {key, ...this.getSlotComponentProps(component, reduxState, props, queryData)})
      );
  }

  getSlotFragments(slot, part) {
    const components = uniq(flattenDeep(this.plugins
      .filter((o) => o.module.slots ? o.module.slots[slot] : false)
      .map((o) => o.module.slots[slot])
    ));

    const documents = components
      .map((c) => c.fragments)
      .filter((fragments) => fragments && fragments[part])
      .reduce((res, fragments) => {
        res.push(fragments[part]);
        return res;
      }, []);

    return documents;
  }

  getGraphQLExtensions() {
    return this.plugins
      .map((o) => pick(o.module, ['mutations', 'queries', 'fragments']))
      .filter((o) => !isEmpty(o));
  }

  getModQueueConfigs() {
    return merge(...this.plugins
      .map((o) => o.module.modQueues)
      .filter((o) => o));
  }

  getTranslations() {
    return this.plugins
      .map((o) => o.module.translations)
      .filter((o) => o);
  }

  getReducers() {
    return merge(
      ...this.plugins
        .filter((o) => o.module.reducer)
        .map((o) => ({[camelize(o.name)] : o.module.reducer}))
    );
  }

  async executeInit(context) {
    const results = this.plugins
      .map((o) => o.module.init)
      .filter((fn) => fn)
      .map((fn) => fn(context));
    await Promise.all(results);
  }
}

/**
 * createPluginsService returns a plugins service.
 * @param  {Array}   plugins config as returned from importing `pluginsConfig`
 * @return {Object}  plugins service
 */
export function createPluginsService(pluginsConfig) {
  return new PluginsService(pluginsConfig);
}
