<commit_before>import React from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { Paper, Typography, TextField, Select, Input, Switch } from 'material-ui';
import { InputLabel } from 'material-ui/Input';
import { FormControl, FormGroup, FormLabel } from 'material-ui/Form';
import { MenuItem } from 'material-ui/Menu';

import { INITIAL } from '../constants';

/**
 * @name render - rendering component
 */

const renderTextField = ({ input, meta: { touched, error }, ...other }) => (

      <TextField {...input} {...other} />

    {touched && error &&
{error}
}

);

const renderSelect = ({ input, label, meta: { touched, error }, list, ...other }) => (

      <InputLabel htmlFor={label}>{label}</InputLabel>
      <Select
        {...input}
        {...other}
        input={<Input id={label} style={{ width: '7rem' }} />}
      >
        <MenuItem value="">None</MenuItem>
        {
          list.map(({ value, label }, index) => (
            <MenuItem key={index} value={value}>{label}</MenuItem>
          ))
        }
      </Select>

    {touched && error &&
{error}
}

);

const renderMultipleSelect = ({ input, label, meta: { touched, error }, list, ...other }) => (

      <InputLabel htmlFor={label}>{label}</InputLabel>
      <Select
        {...input}
        {...other}
        multiple
        value={input.value || []}
        input={<Input id={label} style={{ width: '15rem' }} />}
      >
        <MenuItem value="">None</MenuItem>
        {
          list.map((item, index) => (
            <MenuItem key={index} value={item}>{item}</MenuItem>
          ))
        }
      </Select>

    {touched && error &&
{error}
}

);

const renderSwitch = ({ input, label, meta: { touched, error }, ...other }) => (

      <FormGroup row>
        <FormLabel component="legend" style={{ alignSelf: 'center' }}>{label}</FormLabel>
        <Switch
          {...input}
          {...other}
        />
      </FormGroup>

    {touched && error &&
{error}
}

);

/**
 * @name Component
 */

let WithReduxForm = ({ selector }) => {

  return (

      <Paper>
        <Typography type="title" gutterBottom style={{ padding: '1rem 1rem 0' }}>
          With Redux Form
        </Typography>

        <form className="form">

            {/* input */}
            <FormControl>
              <Field name="name" component={renderTextField} label="Name" />
            </FormControl>

{selector('name')}

            {/* select */}
            <FormControl>
              <Field name="age" component={renderSelect} label="Age" list={INITIAL['listOfage']} />
            </FormControl>

{selector('age')}

            {/* multiple select */}
            <FormControl>
              <Field name="countries" component={renderMultipleSelect} label="Countries" list={INITIAL['listOfCountries']} />
            </FormControl>

{selector('countries') ? selector('countries').join(', ') : selector('countries')}

            {/* switch */}
            <FormControl component="fieldset">
              <Field name="autoplay" component={renderSwitch} label="Autoplay" />
            </FormControl>

              {selector('autoplay') ? `${selector('autoplay')}`.charAt(0).toUpperCase() + `${selector('autoplay')}`.slice(1) : ''}

            ...

        </form>
      </Paper>

  );
};

WithReduxForm = reduxForm({ form: 'example' })(WithReduxForm);

const selector = formValueSelector('example');

export default connect(
  state => ({
    selector: field => selector(state, field)
  })
)(WithReduxForm);
<commit_msg>193rd Commit<commit_after>import React from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { Paper, Typography, TextField, Select, Input, Radio, Switch } from 'material-ui';
import { InputLabel } from 'material-ui/Input';
import { FormControl, FormGroup, FormControlLabel, FormLabel } from 'material-ui/Form';
import { MenuItem } from 'material-ui/Menu';
import { RadioGroup } from 'material-ui/Radio';

import { INITIAL } from '../constants';

/**
 * @name render - rendering component
 */

const renderInput = ({ input, meta: { touched, error }, ...other }) => (

      <TextField {...input} {...other} />

    {touched && error &&
{error}
}

);

const renderSelect = ({ input, label, meta: { touched, error }, list, ...other }) => (

      <InputLabel htmlFor={label}>{label}</InputLabel>
      <Select
        {...input}
        {...other}
        input={<Input id={label} style={{ width: '7rem' }} />}
      >
        <MenuItem value="">None</MenuItem>
        {
          list.map(({ value, label }, index) => (
            <MenuItem key={index} value={value}>{label}</MenuItem>
          ))
        }
      </Select>

    {touched && error &&
{error}
}

);

const renderMultipleSelect = ({ input, label, meta: { touched, error }, list, ...other }) => (

      <InputLabel htmlFor={label}>{label}</InputLabel>
      <Select
        {...input}
        {...other}
        multiple
        value={input.value || []}
        input={<Input id={label} style={{ width: '15rem' }} />}
      >
        <MenuItem value="">None</MenuItem>
        {
          list.map((item, index) => (
            <MenuItem key={index} value={item}>{item}</MenuItem>
          ))
        }
      </Select>

    {touched && error &&
{error}
}

);

const renderRadioButtons = ({ input, meta: { touched, error }, ...other }) => (

      <FormLabel component="legend">Gender</FormLabel>
      <RadioGroup
        {...input}
        {...other}
        style={{ display: 'flex', flexDirection: 'row' }}
        aria-label="gender"
        name="gender"
      >
        <FormControlLabel value="male" control={<Radio />} label="Male" />
        <FormControlLabel value="female" control={<Radio />} label="Female" />
        <FormControlLabel value="other" control={<Radio />} label="Other" />
      </RadioGroup>

    {touched && error &&
{error}
}

);

const renderSwitch = ({ input, label, meta: { touched, error }, ...other }) => (

      <FormGroup row>
        <FormLabel component="legend" style={{ alignSelf: 'center' }}>{label}</FormLabel>
        <Switch
          {...input}
          {...other}
        />
      </FormGroup>

    {touched && error &&
{error}
}

);

/**
 * @name Component
 */

let WithReduxForm = ({ selector }) => {

  return (

      <Paper>
        <Typography type="title" gutterBottom style={{ padding: '1rem 1rem 0' }}>
          With Redux Form
        </Typography>

        <form className="form">

            {/* input */}
            <FormControl>
              <Field name="name" component={renderInput} label="Name" />
            </FormControl>

{selector('name')}

            {/* select */}
            <FormControl>
              <Field name="age" component={renderSelect} label="Age" list={INITIAL['listOfage']} />
            </FormControl>

{selector('age')}

            {/* multiple select */}
            <FormControl>
              <Field name="countries" component={renderMultipleSelect} label="Countries" list={INITIAL['listOfCountries']} />
            </FormControl>

{selector('countries') ? selector('countries').join(', ') : selector('countries')}

            {/* radio buttons */}
            <FormControl component="fieldset">
              <Field name="gender" component={renderRadioButtons} />
            </FormControl>

              {selector('gender') ? `${selector('gender')}`.charAt(0).toUpperCase() + `${selector('gender')}`.slice(1) : ''}

            {/* switch */}
            <FormControl component="fieldset">
              <Field name="autoplay" component={renderSwitch} label="Autoplay" />
            </FormControl>

              {selector('autoplay') ? `${selector('autoplay')}`.charAt(0).toUpperCase() + `${selector('autoplay')}`.slice(1) : ''}

            ...

        </form>
      </Paper>

  );
};

WithReduxForm = reduxForm({ form: 'example' })(WithReduxForm);

const selector = formValueSelector('example');

export default connect(
  state => ({
    selector: field => selector(state, field)
  })
)(WithReduxForm);
<|endoftext|>


<commit_before>define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "settings", "ui", "watcher", "menus", "tabManager", "find", "fs",
        "panels", "fs.cache.xml", "preferences", "c9", "tree"
    ];
    main.provides = ["navigate"];
    return main;

    function main(options, imports, register) {
        var Plugin   = imports.Plugin;
        var settings = imports.settings;
        var ui       = imports.ui;
        var c9       = imports.c9;
        var fs       = imports.fs;
        var fsCache  = imports["fs.cache.xml"];
        var tabs     = imports.tabManager;
        var menus    = imports.menus;
        var watcher  = imports.watcher;
        var panels   = imports.panels;
        var find     = imports.find;
        var filetree = imports.tree;
        var prefs    = imports.preferences;

        var markup   = require("text!./navigate.xml");
        var search   = require('./search');
        var Tree     = require("ace_tree/tree");
        var ListData = require("./dataprovider");

        /***** Initialization *****/

        var plugin = new Plugin("Ajax.org", main.consumes);
        var emit   = plugin.getEmitter();

        var winGoToFile, txtGoToFile, tree, ldSearch;
        var lastPanel, lastSearch, lastPreviewed;

        var dirty         = true;
        var arrayCache    = [];
        var timer;

        var loaded = false;
        function load(){
            if (loaded) return false;
            loaded = true;

            // Register this panel on the left-side panels
            panels.register({
                index        : 200,
                caption      : "Navigate",
                command      : "navigate",
                hint         : "search for a filename, line or symbol and jump to it",
                bindKey      : { mac: "Command-E|Command-P", win: "Ctrl-E|Ctrl-P" },
                panel        : plugin,
                elementName  : "winGoToFile",
                minWidth     : 130,
                autohide     : true,
                draw         : draw
            });

            panels.on("showpanelNavigate", function(e){
                lastPanel = e.lastPanel;
                txtGoToFile.focus();
                txtGoToFile.select();
            });
            panels.on("hidepanelNavigate", function(e){
                tree && tree.clearSelection();
            });
            panels.on("afterAnimate", function(){
                tree && tree.resize();
            })

            // Menus
            var mnuItem = new apf.item({ command : "navigate" });
            menus.addItemByPath("File/Open...", mnuItem, 500, plugin);
            menus.addItemByPath("Goto/Goto File...", mnuItem.cloneNode(false), 100, plugin);

            // Settings
            settings.on("read", function(){
                settings.setDefaults("user/general", [["preview-navigate", "false"]]);
            }, plugin);

            // Prefs
            prefs.add({
                "General" : {
                    "General" : {
                        "Enable Preview on Navigation" : {
                            type     : "checkbox",
                            position : 2000,
                            path     : "user/general/@preview-navigate"
                        }
                    }
                }
            }, plugin);

            // Update when the fs changes
            fs.on("afterWriteFile", function(e){
                // Only mark dirty if file didn't exist yet
                if (arrayCache.indexOf(e.path) == -1)
                    markDirty(e);
            });
            fs.on("afterUnlink",    markDirty);
            fs.on("afterRmfile",    markDirty);
            fs.on("afterRmdir",     markDirty);
            fs.on("afterCopy",      markDirty);
            fs.on("afterRename",    markDirty);
            fs.on("afterSymlink",   markDirty);

            // Or when a watcher fires
            watcher.on("delete",     markDirty);
            watcher.on("directory",  markDirty);

            // Or when the user refreshes the tree
            filetree.on("refresh", markDirty);

            // Or when we change the visibility of hidden files
            fsCache.on("setShowHidden", markDirty);

            // Pre-load file list
            updateFileCache();
        }

        function offlineHandler(e){
            // Online
            if (e.state & c9.STORAGE) {
                txtGoToFile.enable();
                //@Harutyun This doesn't work
                // tree.enable();
            }
            // Offline
            else {
                txtGoToFile.disable();
                //@Harutyun This doesn't work
                // tree.disable();
            }
        }

        var drawn = false;
        function draw(options){
            if (drawn) return;
            drawn = true;

            // Create UI elements
            ui.insertMarkup(options.panel, markup, plugin);

            // Import CSS
            ui.insertCss(require("text!./style.css"), plugin);

            var treeParent   = plugin.getElement("navigateList");
            txtGoToFile      = plugin.getElement("txtGoToFile");
            winGoToFile      = plugin.getElement("winGoToFile");
            txtGoToFile      = plugin.getElement("txtGoToFile");

            // Create the Ace Tree
            tree      = new Tree(treeParent.$int);
            ldSearch  = new ListData(arrayCache);
            ldSearch.search = search;

            // Assign the dataprovider
            tree.setDataProvider(ldSearch);

            tree.renderer.setScrollMargin(0, 10);

            // @TODO this is probably not sufficient
            window.addEventListener("resize", function(){ tree.resize() });

            tree.textInput = txtGoToFile.ace.textInput;

            txtGoToFile.ace.commands.addCommands([
                {
                    bindKey : "ESC",
                    exec    : function(){ hide(); }
                }, {
                    bindKey : "Up",
                    exec    : function(){ tree.execCommand("goUp"); }
                }, {
                    bindKey : "Down",
                    exec    : function(){ tree.execCommand("goDown"); }
                }, {
                    bindKey : "Enter",
                    exec    : function(){ openFile(true); }
                }
            ]);

            tree.on("click", function(e){
                openFile(true);
            });

            txtGoToFile.ace.on("input", function(e) {
                var val = txtGoToFile.getValue();
                filter(val);

                if (dirty && val.length > 0 && ldSearch.loaded) {
                    dirty = false;
                    updateFileCache(true);
                }
            });

            // @Harutyun, the select event should be on the tree or the selection object
            //      I just hacked it into the dataprovider for now, but that is very wrong.
            // tree.getSelection().on("changeSelection", function(){ previewFile(); });
            // tree.on("select", function(){ previewFile(); });
            ldSearch.on("select", function(){ previewFile(); });

            function onblur(e){
                if (!winGoToFile.visible)
                    return;

                var to = e.toElement;
                if (!to || apf.isChildOf(winGoToFile, to, true)
                  || (lastPreviewed && tabs.previewTab
                  && tabs.previewTab === lastPreviewed
                  && (apf.isChildOf(lastPreviewed.aml.relPage, to, true)
                  || lastPreviewed.aml == to))) {
                    return;
                }

                hide();
            }

            apf.addEventListener("movefocus", onblur);

            // Focus the input field
            txtGoToFile.focus();

            // Offline
            c9.on("stateChange", offlineHandler, plugin);
            offlineHandler({ state: c9.status });

            emit("draw");
        }

        /***** Methods *****/

        function reloadResults(){
            if (!winGoToFile)
                return;

            // Wait until window is visible
            if (!winGoToFile.visible) {
                winGoToFile.on("propVisible", function visible(e){
                    if (e.value) {
                        reloadResults();
                        winGoToFile.off("prop.visible", visible);
                    }
                });
                return;
            }

            // @Harutyun, how do I get the selection, scrollTop?

            // var sel = [];
            // dgGoToFile.getSelection().forEach(function(node){
            //     var i = node.firstChild.nodeValue;
            //     sel.push(searchResults[i]);
            // });

            // var state = {
            //     sel : sel, //store previous selection
            //     caret : dgGoToFile.caret && searchResults[dgGoToFile.caret.firstChild.nodeValue],
            //     scrollTop : dgGoToFile.$viewport.getScrollTop()
            // };

            if (lastSearch)
                filter(lastSearch);//, state.sel.length);
            else
                ldSearch.updateData(arrayCache);

            // @Harutyun, how do I set the selection, scrollTop?

            // if (state.sel.length && state.sel.length < 100) {
            //     var list = [];
            //     sel = state.sel;
            //     for (var i = 0, l = sel.length; i < l; i++) {
            //         list.push(dgGoToFile.queryNode("//d:href[text()='"
            //             + searchResults.indexOf(sel[i]) + "']"));
            //     }
            //     dgGoToFile.selectList(list);
            //     if (state.caret)
            //         dgGoToFile.setCaret(dgGoToFile.queryNode("//d:href[text()='"
            //             + searchResults.indexOf(state.caret) + "']"));
            //     dgGoToFile.$viewport.setScrollTop(state.scrollTop);
            // }
        }

        function markDirty(options){
            // Ignore hidden files
            var path = options && options.path || "";
            if (path && !fsCache.showHidden && path.charAt(0) == ".")
                return;

            dirty = true;
            if (panels.isActive("navigate")) {
                clearTimeout(timer);
                timer = setTimeout(function(){ updateFileCache(true); }, 2000);
            }
        }

        function updateFileCache(isDirty){
            clearTimeout(timer);

            find.getFileList({
                path    : "/",
                nocache : isDirty,
                hidden  : fsCache.showHidden,
                buffer  : true
            }, function(err, data){
                if (err)
                    return;

                arrayCache = data.trim().split("\n");
                reloadResults();
            });

            dirty = false;
        }

        /**
         * Searches through the dataset
         *
         */
        function filter(keyword, nosel){
            keyword = keyword.replace(/\*/g, "");

            if (!arrayCache.length) {
                lastSearch = keyword;
                return;
            }

            // Needed for highlighting
            ldSearch.keyword = keyword;

            var searchResults;
            if (!keyword || !keyword.length) {
                var result = arrayCache.slice();
                // More prioritization for already open files
                tabs.getTabs().forEach(function (tab) {
                    if (!tab.path
                      || tab.document.meta.preview) return;

                    var idx = result.indexOf(tab.path);
                    if (idx > -1) {
                        result.splice(idx, 1);
                        result.unshift(tab.path);
                    }
                });
                searchResults = result;
            }
            else {
                // @Harutyun, how do I set the scroll position?
                // dgGoToFile.$viewport.setScrollTop(0);
                searchResults = search.fileSearch(arrayCache, keyword);
            }

            lastSearch = keyword;

            if (searchResults)
                ldSearch.updateData(searchResults);

            if (nosel || !searchResults.length)
                return;

            // See if there are open files that match the search results
            // and the first if in the displayed results
            var pages = tabs.getTabs(), hash = {};
            for (var i = pages.length - 1; i >= 0; i--) {
                if (!pages[i].document.meta.preview)
                    hash[pages[i].path] = true;
            }

            // loop over all visible items. If we find a visible item
            // that is in the `hash`, select it and return.
            var first = tree.renderer.getFirstVisibleRow();
            var last = tree.renderer.getLastVisibleRow();
            for (var i = first; i < last; i++) {
                if (hash[ldSearch.visibleItems[i]]) {
                    tree.select(i);
                    return;
                }
            }

            // select the first item in the list
            if (keyword) {
                tree.select(0);
            }
        }

        function openFile(noanim){
            if (!ldSearch.loaded)
                return false;

            // @Harutyun, How do I get the selection (multiple)
            // var nodes = dgGoToFile.getSelection();

            // Temporary
            var nodes = [];
            var row   = ldSearch.selectedRow;
            nodes.push(ldSearch.visibleItems[row]);

            // Cancel Preview and Keep the tab if there's only one
            if (tabs.preview({ cancel: true, keep : nodes.length == 1 }) === true)
                return hide();

            hide();

            var fn = function(){};
            for (var i = 0, l = nodes.length; i < l; i++) {
                var path  = "/" + nodes[i].replace(/^[\/]+/, "");

                tabs.open({
                    path   : path,
                    noanim : l > 1,
                    active : i == l - 1
                }, fn);
            }

            lastPreviewed = null;
        }

        function previewFile(noanim){
            if (!settings.getBool("user/general/@preview-navigate"))
                return;

            if (!ldSearch.loaded)
                return false;

            var row   = ldSearch.selectedRow;
            var value = ldSearch.visibleItems[row];
            if (!value)
                return;

            var path  = "/" + value.replace(/^[\/]+/, "");
            lastPreviewed = tabs.preview({ path: path }, function(){});
        }

        function show() {
            panels.activate("navigate");
        }

        function hide(){
            if (panels.isActive("navigate")) {
                if (lastPanel)
                    panels.activate(lastPanel);
                else
                    panels.deactivate("navigate");

                // Cancel Preview
                tabs.preview({ cancel: true });

                if (tabs.focussedTab)
                    tabs.focussedTab.editor.focus();
            }
        }

        /***** Lifecycle *****/

        plugin.on("load", function(){
            load();
        });
        plugin.on("enable", function(){

        });
        plugin.on("disable", function(){

        });
        plugin.on("unload", function(){
            loaded = false;
            drawn  = false;
        });

        /***** Register and define API *****/

        /**
         * Navigation panel. Navigates to files, lines and symbols
         **/
        plugin.freezePublicAPI({
            /**
             *
             */
            show : show
        });

        register(null, {
            navigate: plugin
        });
    }
});<commit_msg>numerous fixes for name changes<commit_after>define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "settings", "ui", "watcher", "menus", "tabManager", "find", "fs",
        "panels", "fs.cache.xml", "preferences", "c9", "tree"
    ];
    main.provides = ["navigate"];
    return main;

    function main(options, imports, register) {
        var Plugin   = imports.Plugin;
        var settings = imports.settings;
        var ui       = imports.ui;
        var c9       = imports.c9;
        var fs       = imports.fs;
        var fsCache  = imports["fs.cache.xml"];
        var tabs     = imports.tabManager;
        var menus    = imports.menus;
        var watcher  = imports.watcher;
        var panels   = imports.panels;
        var find     = imports.find;
        var filetree = imports.tree;
        var prefs    = imports.preferences;

        var markup   = require("text!./navigate.xml");
        var search   = require('./search');
        var Tree     = require("ace_tree/tree");
        var ListData = require("./dataprovider");

        /***** Initialization *****/

        var plugin = new Plugin("Ajax.org", main.consumes);
        var emit   = plugin.getEmitter();

        var winGoToFile, txtGoToFile, tree, ldSearch;
        var lastPanel, lastSearch, lastPreviewed;

        var dirty         = true;
        var arrayCache    = [];
        var timer;

        var loaded = false;
        function load(){
            if (loaded) return false;
            loaded = true;

            // Register this panel on the left-side panels
            panels.register({
                index        : 200,
                caption      : "Navigate",
                command      : "navigate",
                hint         : "search for a filename, line or symbol and jump to it",
                bindKey      : { mac: "Command-E|Command-P", win: "Ctrl-E|Ctrl-P" },
                panel        : plugin,
                elementName  : "winGoToFile",
                minWidth     : 130,
                autohide     : true,
                draw         : draw
            });

            panels.on("showpanelNavigate", function(e){
                lastPanel = e.lastPanel;
                txtGoToFile.focus();
                txtGoToFile.select();
            });
            panels.on("hidepanelNavigate", function(e){
                tree && tree.clearSelection();
            });
            panels.on("afterAnimate", function(){
                tree && tree.resize();
            })

            // Menus
            var mnuItem = new apf.item({ command : "navigate" });
            menus.addItemByPath("File/Open...", mnuItem, 500, plugin);
            menus.addItemByPath("Goto/Goto File...", mnuItem.cloneNode(false), 100, plugin);

            // Settings
            settings.on("read", function(){
                settings.setDefaults("user/general", [["preview-navigate", "false"]]);
            }, plugin);

            // Prefs
            prefs.add({
                "General" : {
                    "General" : {
                        "Enable Preview on Navigation" : {
                            type     : "checkbox",
                            position : 2000,
                            path     : "user/general/@preview-navigate"
                        }
                    }
                }
            }, plugin);

            // Update when the fs changes
            fs.on("afterWriteFile", function(e){
                // Only mark dirty if file didn't exist yet
                if (arrayCache.indexOf(e.path) == -1)
                    markDirty(e);
            });
            fs.on("afterUnlink",    markDirty);
            fs.on("afterRmfile",    markDirty);
            fs.on("afterRmdir",     markDirty);
            fs.on("afterCopy",      markDirty);
            fs.on("afterRename",    markDirty);
            fs.on("afterSymlink",   markDirty);

            // Or when a watcher fires
            watcher.on("delete",     markDirty);
            watcher.on("directory",  markDirty);

            // Or when the user refreshes the tree
            filetree.on("refresh", markDirty);

            // Or when we change the visibility of hidden files
            fsCache.on("setShowHidden", markDirty);

            // Pre-load file list
            updateFileCache();
        }

        function offlineHandler(e){
            // Online
            if (e.state & c9.STORAGE) {
                txtGoToFile.enable();
                //@Harutyun This doesn't work
                // tree.enable();
            }
            // Offline
            else {
                txtGoToFile.disable();
                //@Harutyun This doesn't work
                // tree.disable();
            }
        }

        var drawn = false;
        function draw(options){
            if (drawn) return;
            drawn = true;

            // Create UI elements
            ui.insertMarkup(options.panel, markup, plugin);

            // Import CSS
            ui.insertCss(require("text!./style.css"), plugin);

            var treeParent   = plugin.getElement("navigateList");
            txtGoToFile      = plugin.getElement("txtGoToFile");
            winGoToFile      = plugin.getElement("winGoToFile");
            txtGoToFile      = plugin.getElement("txtGoToFile");

            // Create the Ace Tree
            tree      = new Tree(treeParent.$int);
            ldSearch  = new ListData(arrayCache);
            ldSearch.search = search;

            // Assign the dataprovider
            tree.setDataProvider(ldSearch);

            tree.renderer.setScrollMargin(0, 10);

            // @TODO this is probably not sufficient
            window.addEventListener("resize", function(){ tree.resize() });

            tree.textInput = txtGoToFile.ace.textInput;

            txtGoToFile.ace.commands.addCommands([
                {
                    bindKey : "ESC",
                    exec    : function(){ hide(); }
                }, {
                    bindKey : "Up",
                    exec    : function(){ tree.execCommand("goUp"); }
                }, {
                    bindKey : "Down",
                    exec    : function(){ tree.execCommand("goDown"); }
                }, {
                    bindKey : "Enter",
                    exec    : function(){ openFile(true); }
                }
            ]);

            tree.on("click", function(e){
                openFile(true);
            });

            txtGoToFile.ace.on("input", function(e) {
                var val = txtGoToFile.getValue();
                filter(val);

                if (dirty && val.length > 0 && ldSearch.loaded) {
                    dirty = false;
                    updateFileCache(true);
                }
            });

            // @Harutyun, the select event should be on the tree or the selection object
            //      I just hacked it into the dataprovider for now, but that is very wrong.
            // tree.getSelection().on("changeSelection", function(){ previewFile(); });
            // tree.on("select", function(){ previewFile(); });
            ldSearch.on("select", function(){ previewFile(); });

            function onblur(e){
                if (!winGoToFile.visible)
                    return;

                var to = e.toElement;
                if (!to || apf.isChildOf(winGoToFile, to, true)
                  || (lastPreviewed && tabs.previewTab
                  && tabs.previewTab === lastPreviewed
                  && (apf.isChildOf(lastPreviewed.aml.relPage, to, true)
                  || lastPreviewed.aml == to))) {
                    return;
                }

                hide();
            }

            apf.addEventListener("movefocus", onblur);

            // Focus the input field
            txtGoToFile.focus();

            // Offline
            c9.on("stateChange", offlineHandler, plugin);
            offlineHandler({ state: c9.status });

            emit("draw");
        }

        /***** Methods *****/

        function reloadResults(){
            if (!winGoToFile)
                return;

            // Wait until window is visible
            if (!winGoToFile.visible) {
                winGoToFile.on("prop.visible", function visible(e){
                    if (e.value) {
                        reloadResults();
                        winGoToFile.off("prop.visible", visible);
                    }
                });
                return;
            }

            // @Harutyun, how do I get the selection, scrollTop?

            // var sel = [];
            // dgGoToFile.getSelection().forEach(function(node){
            //     var i = node.firstChild.nodeValue;
            //     sel.push(searchResults[i]);
            // });

            // var state = {
            //     sel : sel, //store previous selection
            //     caret : dgGoToFile.caret && searchResults[dgGoToFile.caret.firstChild.nodeValue],
            //     scrollTop : dgGoToFile.$viewport.getScrollTop()
            // };

            if (lastSearch)
                filter(lastSearch);//, state.sel.length);
            else
                ldSearch.updateData(arrayCache);

            // @Harutyun, how do I set the selection, scrollTop?

            // if (state.sel.length && state.sel.length < 100) {
            //     var list = [];
            //     sel = state.sel;
            //     for (var i = 0, l = sel.length; i < l; i++) {
            //         list.push(dgGoToFile.queryNode("//d:href[text()='"
            //             + searchResults.indexOf(sel[i]) + "']"));
            //     }
            //     dgGoToFile.selectList(list);
            //     if (state.caret)
            //         dgGoToFile.setCaret(dgGoToFile.queryNode("//d:href[text()='"
            //             + searchResults.indexOf(state.caret) + "']"));
            //     dgGoToFile.$viewport.setScrollTop(state.scrollTop);
            // }
        }

        function markDirty(options){
            // Ignore hidden files
            var path = options && options.path || "";
            if (path && !fsCache.showHidden && path.charAt(0) == ".")
                return;

            dirty = true;
            if (panels.isActive("navigate")) {
                clearTimeout(timer);
                timer = setTimeout(function(){ updateFileCache(true); }, 2000);
            }
        }

        function updateFileCache(isDirty){
            clearTimeout(timer);

            find.getFileList({
                path    : "/",
                nocache : isDirty,
                hidden  : fsCache.showHidden,
                buffer  : true
            }, function(err, data){
                if (err)
                    return;

                arrayCache = data.trim().split("\n");
                reloadResults();
            });

            dirty = false;
        }

        /**
         * Searches through the dataset
         *
         */
        function filter(keyword, nosel){
            keyword = keyword.replace(/\*/g, "");

            if (!arrayCache.length) {
                lastSearch = keyword;
                return;
            }

            // Needed for highlighting
            ldSearch.keyword = keyword;

            var searchResults;
            if (!keyword || !keyword.length) {
                var result = arrayCache.slice();
                // More prioritization for already open files
                tabs.getTabs().forEach(function (tab) {
                    if (!tab.path
                      || tab.document.meta.preview) return;

                    var idx = result.indexOf(tab.path);
                    if (idx > -1) {
                        result.splice(idx, 1);
                        result.unshift(tab.path);
                    }
                });
                searchResults = result;
            }
            else {
                // @Harutyun, how do I set the scroll position?
                // dgGoToFile.$viewport.setScrollTop(0);
                searchResults = search.fileSearch(arrayCache, keyword);
            }

            lastSearch = keyword;

            if (searchResults)
                ldSearch.updateData(searchResults);

            if (nosel || !searchResults.length)
                return;

            // See if there are open files that match the search results
            // and the first if in the displayed results
            var pages = tabs.getTabs(), hash = {};
            for (var i = pages.length - 1; i >= 0; i--) {
                if (!pages[i].document.meta.preview)
                    hash[pages[i].path] = true;
            }

            // loop over all visible items. If we find a visible item
            // that is in the `hash`, select it and return.
            var first = tree.renderer.getFirstVisibleRow();
            var last = tree.renderer.getLastVisibleRow();
            for (var i = first; i < last; i++) {
                if (hash[ldSearch.visibleItems[i]]) {
                    tree.select(i);
                    return;
                }
            }

            // select the first item in the list
            if (keyword) {
                tree.select(0);
            }
        }

        function openFile(noanim){
            if (!ldSearch.loaded)
                return false;

            // @Harutyun, How do I get the selection (multiple)
            // var nodes = dgGoToFile.getSelection();

            // Temporary
            var nodes = [];
            var row   = ldSearch.selectedRow;
            nodes.push(ldSearch.visibleItems[row]);

            // Cancel Preview and Keep the tab if there's only one
            if (tabs.preview({ cancel: true, keep : nodes.length == 1 }) === true)
                return hide();

            hide();

            var fn = function(){};
            for (var i = 0, l = nodes.length; i < l; i++) {
                var path  = "/" + nodes[i].replace(/^[\/]+/, "");

                tabs.open({
                    path   : path,
                    noanim : l > 1,
                    active : i == l - 1
                }, fn);
            }

            lastPreviewed = null;
        }

        function previewFile(noanim){
            if (!settings.getBool("user/general/@preview-navigate"))
                return;

            if (!ldSearch.loaded)
                return false;

            var row   = ldSearch.selectedRow;
            var value = ldSearch.visibleItems[row];
            if (!value)
                return;

            var path  = "/" + value.replace(/^[\/]+/, "");
            lastPreviewed = tabs.preview({ path: path }, function(){});
        }

        function show() {
            panels.activate("navigate");
        }

        function hide(){
            if (panels.isActive("navigate")) {
                if (lastPanel)
                    panels.activate(lastPanel);
                else
                    panels.deactivate("navigate");

                // Cancel Preview
                tabs.preview({ cancel: true });

                if (tabs.focussedTab)
                    tabs.focussedTab.editor.focus();
            }
        }

        /***** Lifecycle *****/

        plugin.on("load", function(){
            load();
        });
        plugin.on("enable", function(){

        });
        plugin.on("disable", function(){

        });
        plugin.on("unload", function(){
            loaded = false;
            drawn  = false;
        });

        /***** Register and define API *****/

        /**
         * Navigation panel. Navigates to files, lines and symbols
         **/
        plugin.freezePublicAPI({
            /**
             *
             */
            show : show
        });

        register(null, {
            navigate: plugin
        });
    }
});<|endoftext|>


<commit_before>const { get } = require('lodash')

const { isValidGuid } = require('../../../lib/controller-utils')
const { getInteraction } = require('../../interactions/repos')
const { getDitCompany } = require('../../companies/repos')
const { transformFromApi } = require('../../interactions/services/formatting')
const { getInvestment } = require('../repos')

function handleEmptyMiddleware (req, res, next) {
  if (req.path === '/') {
    return res.redirect(`/investment-projects/create`)
  }
  next()
}

function getLocalNavMiddleware (req, res, next) {
  res.locals.localNavItems = [
    { label: 'Project details', slug: 'details' },
    { label: 'Project team', slug: 'team' },
    { label: 'Interactions', slug: 'interactions' },
    { label: 'Evaluation', slug: 'evaluation' },
    { label: 'Audit history', slug: 'audit' },
  ]
  next()
}

async function getInvestmentDetails (req, res, next, id = req.params.id) {
  if (!isValidGuid(id)) {
    return next()
  }
  try {
    const investmentData = await getInvestment(req.session.token, req.params.id)
    const investorCompany = await getDitCompany(req.session.token, get(investmentData, 'investor_company.id'))
    const ukCompanyId = get(investmentData, 'uk_company.id')

    investmentData.investor_company = Object.assign({}, investmentData.investor_company, investorCompany)

    if (ukCompanyId) {
      const companyDetails = await getDitCompany(req.session.token, ukCompanyId)
      investmentData.uk_company = Object.assign({}, investmentData.uk_company, companyDetails)
    }

    res.locals.investmentData = investmentData
    res.locals.equityCompany = investmentData.investor_company

    res.locals.investmentStatus = {
      id: investmentData.id,
      projectCode: investmentData.project_code,
      stageName: investmentData.stage.name,
      valuation: investmentData.value_complete ? 'Project valued' : 'Not yet valued',
    }

    res.breadcrumb({
      name: investmentData.name,
      url: `/investment-projects/${investmentData.id}`,
    })

    next()
  } catch (error) {
    next(error)
  }
}

async function getInteractionDetails (req, res, next, interactionId = req.params.interactionId) {
  if (!isValidGuid(interactionId)) {
    return next()
  }
  try {
    const interactionResponse = await getInteraction(req.session.token, interactionId)

    res.locals.interaction = transformFromApi(interactionResponse)

    next()
  } catch (error) {
    next(error)
  }
}

module.exports = {
  handleEmptyMiddleware,
  getLocalNavMiddleware,
  getInvestmentDetails,
  getInteractionDetails,
}
<commit_msg>Expose company name and URL in investmentStatus object<commit_after>const { get } = require('lodash')

const { isValidGuid } = require('../../../lib/controller-utils')
const { getInteraction } = require('../../interactions/repos')
const { getDitCompany } = require('../../companies/repos')
const { transformFromApi } = require('../../interactions/services/formatting')
const { buildCompanyUrl } = require('../../companies/services/data')
const { getInvestment } = require('../repos')

function handleEmptyMiddleware (req, res, next) {
  if (req.path === '/') {
    return res.redirect(`/investment-projects/create`)
  }
  next()
}

function getLocalNavMiddleware (req, res, next) {
  res.locals.localNavItems = [
    { label: 'Project details', slug: 'details' },
    { label: 'Project team', slug: 'team' },
    { label: 'Interactions', slug: 'interactions' },
    { label: 'Evaluation', slug: 'evaluation' },
    { label: 'Audit history', slug: 'audit' },
  ]
  next()
}

async function getInvestmentDetails (req, res, next, id = req.params.id) {
  if (!isValidGuid(id)) {
    return next()
  }
  try {
    const investmentData = await getInvestment(req.session.token, req.params.id)
    const investorCompany = await getDitCompany(req.session.token, get(investmentData, 'investor_company.id'))
    const ukCompanyId = get(investmentData, 'uk_company.id')

    investmentData.investor_company = Object.assign({}, investmentData.investor_company, investorCompany)

    if (ukCompanyId) {
      const companyDetails = await getDitCompany(req.session.token, ukCompanyId)
      investmentData.uk_company = Object.assign({}, investmentData.uk_company, companyDetails)
    }

    res.locals.investmentData = investmentData
    res.locals.equityCompany = investmentData.investor_company

    res.locals.investmentStatus = {
      id: investmentData.id,
      projectCode: investmentData.project_code,
      stageName: investmentData.stage.name,
      valuation: investmentData.value_complete ? 'Project valued' : 'Not yet valued',
      company: {
        name: investmentData.investor_company.name,
        url: buildCompanyUrl(investmentData.investor_company),
      },
    }

    res.breadcrumb({
      name: investmentData.name,
      url: `/investment-projects/${investmentData.id}`,
    })

    next()
  } catch (error) {
    next(error)
  }
}

async function getInteractionDetails (req, res, next, interactionId = req.params.interactionId) {
  if (!isValidGuid(interactionId)) {
    return next()
  }
  try {
    const interactionResponse = await getInteraction(req.session.token, interactionId)

    res.locals.interaction = transformFromApi(interactionResponse)

    next()
  } catch (error) {
    next(error)
  }
}

module.exports = {
  handleEmptyMiddleware,
  getLocalNavMiddleware,
  getInvestmentDetails,
  getInteractionDetails,
}
<|endoftext|>


<commit_before>/**
 * @class  	UIUtil
 * @author  Flavio De Stefano <flavio.destefano@caffeinalab.com>
 */

var Image = require('T/image');
var HTTP = require('T/http');

/**
 * @method populateListViewFromCollection
 * Parse an array or a Backbone.Collection and populate a ListView with this values.
 *
 * @param {Object} C Array or Backbone.Collection
 * @param {Object} opt Options
 * @param {Function} opt.datasetCb You must provide a callback to fill the ListItem, like this:
 *
 * ```
 * return {
 *	   properties: {
 *	      height: 52,
 *			itemId: el.id,
 *			searchableText: el.get('city')
 *		},
 *		title: { text: el.get('title') },
 *		address: { text: el.get('address')+', '+el.get('city') }
 *	}
 * ```
 *
 * @param {Object} opt.groupBy See `_.groupBy`
 * @param {Function} opt.headerViewCb A callback to generate the headerView for the ListView.
 * @param {Boolean} opt.sectionIndex If `true`, provide the **alphabet on the right** functionality.
 * @param {Ti.UI.ListView} [$ui] The ListView to populate.
 * @return {Array}
 */
exports.populateListViewFromCollection = function(C, opt, $ui) {
	var sec = [];

	if (opt.groupBy != null && opt.groupBy != false) {

		var array = (C instanceof Backbone.Collection) ? C.groupBy(opt.groupBy) : ( _.isArray(C) ? _.groupBy(C, opt.groupBy) : C );
		sec = _.map(array, function(els, key) {
			var hViewExtend = _.isFunction(opt.headerViewCb) ? { headerView: opt.headerViewCb(key) } : { headerTitle: key };

			return Ti.UI.createListSection(_.extend({
				items: _.map(els, opt.datasetCb),
			}, hViewExtend));
		});

		if (OS_IOS && $ui != null && opt.sectionIndex == true) {
			$ui.sectionIndexTitles = _.map(Object.keys(array), function(u, k) {
				return {
					title: u,
					index: k
				};
			});
		}

	} else {
		sec = [ Ti.UI.createListSection({
			items: (C instanceof Backbone.Collection) ? C.map(opt.datasetCb) : _.compact(_.map(C, opt.datasetCb))
		}) ];
	}

	if ($ui != null) $ui.sections = sec;
	return sec;
};

/**
* @method setBackgroundCoverForView
* Set the background image with cover method
* @param {String} url
*/
exports.setBackgroundCoverForView = function($this, url, callback) {
	if ($this.size == null || $this.size.width == 0 || $this.size.height == 0) {

		$this.addEventListener('postlayout', function postlayout() {
			$this.removeEventListener('postlayout', postlayout);
			if ($this.size != null && $this.size.width != 0 && $this.size.height != 0) {
				exports.setBackgroundCoverForView($this, url, callback);
			}
		});

		return;
	}

	var w = $this.size.width, h = $this.size.height;
	var hashedCachedName = Ti.Utils.md5HexDigest(url) + '_' + (w+'x'+h) + '.png';
	var cachedFile = Ti.Filesystem.getFile(Ti.Filesystem.applicationCacheDirectory, hashedCachedName);

	var onBlobReady = function(blob) {
		Ti.Filesystem.getFile(Ti.Filesystem.applicationCacheDirectory).createDirectory();

		var cachedFileStatus = Image.process({
			blob: blob,
			width: w,
			height: h,
			retina: true,
			file: cachedFile
		});

		if (cachedFileStatus != false && cachedFileStatus.nativePath) {
			callback(cachedFileStatus.nativePath);
		} else {
			Ti.API.error('UIUtil: Can\'t write cover file for url <' + url + '>');
		}
	};

	if (cachedFile.exists()) {
		callback(cachedFile.nativePath);

	} else {

		if (/^https?\:\/\//.test(url)) {
			HTTP.send({
				url: url,
				format: 'blob',
				cache: false,
				refresh: true,
				errorAlert: false,
				silent: true
			}).success(function(data) {
				onBlobReady(data);
			}).error(function() {
				Ti.API.error('UIUtil: URL <' + url + '> can\'t be downloaded');
			});

		} else {

			if (OS_ANDROID) url = url.replace(/^\//, '');
			var origFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, url);

			if (origFile.exists()) {
				var blob = origFile.read();
				if (blob != null) {
					onBlobReady(blob);
				} else {
					Ti.API.error('UIUtil: File <' + url + '> exists but is unreadable');
				}
			} else {
				Ti.API.error('UIUtil: File <' + url + '> doesn\'t exists');
			}
		}
	}

};

/**
 * @method  buildKeyboardToolbar
 * @return {Ti.UI.iOS.Toolbar}
 */
exports.buildKeyboardToolbar = function(opt) {
	var $doneBtn = Ti.UI.createButton({
		title: L('done', 'Done'),
		style: Ti.UI.iPhone.SystemButtonStyle.DONE
	});
	$doneBtn.addEventListener('click', opt.done);

	var $cancelBtn = Ti.UI.createButton({
		title: L('cancel', 'Cancel'),
	});
	$cancelBtn.addEventListener('click', opt.cancel);

	return Ti.UI.iOS.createToolbar({
		borderTop: true,
		borderBottom: true,
		items:[
		$cancelBtn,
		Ti.UI.createButton({ systemButton: Ti.UI.iPhone.SystemButton.FLEXIBLE_SPACE }),
		$doneBtn
		]
	});
};
<commit_msg>Compact a priori<commit_after>/**
 * @class  	UIUtil
 * @author  Flavio De Stefano <flavio.destefano@caffeinalab.com>
 */

var Image = require('T/image');
var HTTP = require('T/http');

/**
 * @method populateListViewFromCollection
 * Parse an array or a Backbone.Collection and populate a ListView with this values.
 *
 * @param {Object} C Array or Backbone.Collection
 * @param {Object} opt Options
 * @param {Function} opt.datasetCb You must provide a callback to fill the ListItem, like this:
 *
 * ```
 * return {
 *	   properties: {
 *	      height: 52,
 *			itemId: el.id,
 *			searchableText: el.get('city')
 *		},
 *		title: { text: el.get('title') },
 *		address: { text: el.get('address')+', '+el.get('city') }
 *	}
 * ```
 *
 * @param {Object} opt.groupBy See `_.groupBy`
 * @param {Function} opt.headerViewCb A callback to generate the headerView for the ListView.
 * @param {Boolean} opt.sectionIndex If `true`, provide the **alphabet on the right** functionality.
 * @param {Ti.UI.ListView} [$ui] The ListView to populate.
 * @return {Array}
 */
exports.populateListViewFromCollection = function(C, opt, $ui) {
	var sec = [];

	if (opt.groupBy != null && opt.groupBy != false) {

		var array = (C instanceof Backbone.Collection) ? C.groupBy(opt.groupBy) : ( _.isArray(C) ? _.groupBy(C, opt.groupBy) : C );
		sec = _.map(array, function(els, key) {
			var hViewExtend = _.isFunction(opt.headerViewCb) ? { headerView: opt.headerViewCb(key) } : { headerTitle: key };

			return Ti.UI.createListSection(_.extend({
				items: _.map(els, opt.datasetCb),
			}, hViewExtend));
		});

		if (OS_IOS && $ui != null && opt.sectionIndex == true) {
			$ui.sectionIndexTitles = _.map(Object.keys(array), function(u, k) {
				return {
					title: u,
					index: k
				};
			});
		}

	} else {
		sec = [ Ti.UI.createListSection({
			items: _.compact( (C instanceof Backbone.Collection) ? C.map(opt.datasetCb) : _.map(C, opt.datasetCb) )
		}) ];
	}

	if ($ui != null) $ui.sections = sec;
	return sec;
};

/**
* @method setBackgroundCoverForView
* Set the background image with cover method
* @param {String} url
*/
exports.setBackgroundCoverForView = function($this, url, callback) {
	if ($this.size == null || $this.size.width == 0 || $this.size.height == 0) {

		$this.addEventListener('postlayout', function postlayout() {
			$this.removeEventListener('postlayout', postlayout);
			if ($this.size != null && $this.size.width != 0 && $this.size.height != 0) {
				exports.setBackgroundCoverForView($this, url, callback);
			}
		});

		return;
	}

	var w = $this.size.width, h = $this.size.height;
	var hashedCachedName = Ti.Utils.md5HexDigest(url) + '_' + (w+'x'+h) + '.png';
	var cachedFile = Ti.Filesystem.getFile(Ti.Filesystem.applicationCacheDirectory, hashedCachedName);

	var onBlobReady = function(blob) {
		Ti.Filesystem.getFile(Ti.Filesystem.applicationCacheDirectory).createDirectory();

		var cachedFileStatus = Image.process({
			blob: blob,
			width: w,
			height: h,
			retina: true,
			file: cachedFile
		});

		if (cachedFileStatus != false && cachedFileStatus.nativePath) {
			callback(cachedFileStatus.nativePath);
		} else {
			Ti.API.error('UIUtil: Can\'t write cover file for url <' + url + '>');
		}
	};

	if (cachedFile.exists()) {
		callback(cachedFile.nativePath);

	} else {

		if (/^https?\:\/\//.test(url)) {
			HTTP.send({
				url: url,
				format: 'blob',
				cache: false,
				refresh: true,
				errorAlert: false,
				silent: true
			}).success(function(data) {
				onBlobReady(data);
			}).error(function() {
				Ti.API.error('UIUtil: URL <' + url + '> can\'t be downloaded');
			});

		} else {

			if (OS_ANDROID) url = url.replace(/^\//, '');
			var origFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, url);

			if (origFile.exists()) {
				var blob = origFile.read();
				if (blob != null) {
					onBlobReady(blob);
				} else {
					Ti.API.error('UIUtil: File <' + url + '> exists but is unreadable');
				}
			} else {
				Ti.API.error('UIUtil: File <' + url + '> doesn\'t exists');
			}
		}
	}

};

/**
 * @method  buildKeyboardToolbar
 * @return {Ti.UI.iOS.Toolbar}
 */
exports.buildKeyboardToolbar = function(opt) {
	var $doneBtn = Ti.UI.createButton({
		title: L('done', 'Done'),
		style: Ti.UI.iPhone.SystemButtonStyle.DONE
	});
	$doneBtn.addEventListener('click', opt.done);

	var $cancelBtn = Ti.UI.createButton({
		title: L('cancel', 'Cancel'),
	});
	$cancelBtn.addEventListener('click', opt.cancel);

	return Ti.UI.iOS.createToolbar({
		borderTop: true,
		borderBottom: true,
		items:[
		$cancelBtn,
		Ti.UI.createButton({ systemButton: Ti.UI.iPhone.SystemButton.FLEXIBLE_SPACE }),
		$doneBtn
		]
	});
};
<|endoftext|>


<commit_before>"use strict";

var error = require('debug')('error');
var generate = require('./base');
var room = require('../room');
var verbose = require('debug')('verbose:queue');

var TEAMSIZE = 6;

var user = {};
var teams = { waitPlayer: [], waitConfirm: [] };
var queue = [];

function Team() {
  verbose('Team() constructor called');
  this.room = new room.Room();
  this.members = [];
  this.confirmed = {};
}

Team.prototype.push = function (username) {
  verbose('Team.push(%s)', username);
  var socket = user[username].socket;
  this.room.push(socket);
  this.members.push(username);
  user[username].team = this;
  this.confirmed[username] = false;
};

Team.prototype.move = function (src, dest) {
  verbose('Team.move(%s, %s)', src, dest);
  var index = src.indexOf(this);
  if (index > -1) {
    src.splice(index, 1);
    verbose('Team.move(%s, %s), remove team from src', src, dest);
    if (dest) {
      verbose('Team.move(%s, %s), push team to dest', src, dest);
      dest.push(this);
    }
  } else {
    verbose('Team.move(%s, %s), src does not contain this team.', src, dest);
  }
};

Team.prototype.removeUser = function (username) {
  verbose('Team.removeUser(%s)', username);
  var index = this.members.indexOf(username);
  if (index > -1) {
    this.members.splice(index, 1);
    this.room.remove(user[username].socket);
    user[username].team = null;
    delete this.confirmed[username];
  }
};

function updateClient(team, state) {
  if (!team instanceof Team || typeof state !== 'string') {
    error('updateClient, check arguments');
    return;
  }

  var current;
  if (state === 'wait-player') {
    current = team.members.length;
  } else if (state === 'wait-confirm') {
    current = Object.keys(team.confirmed)
      .map(function (k) {
        return team.confirmed[k];
      }).filter(function (v) {
        return v;
      }).length;
  } else {
    error('queue/update, abnormal state(%s)', state);
    return;
  }

  verbose('emit queue/update, state: %s, current: %s', state, current);
  team.room.emit('queue/update', {
    state: state,
    current: current
  });
}

function assignTeam() {
  verbose('assignTeam');
  if (queue.length && teams.waitPlayer.length === 0) {
    verbose('no team waiting player. make a new one.');
    teams.waitPlayer.push(new Team());
    verbose('teams.waitPlayer.length = %s', teams.waitPlayer.length);
  }

  for (var i = 0; i < teams.waitPlayer.length && queue.length; i++) {
    var team = teams.waitPlayer[i];
    var changed = false;
    while (team.members.length < TEAMSIZE && queue.length) {
      team.push(queue.shift());
      changed = true;
    }

    // if any changes, emit queue/update(wait-player) to every team member
    if (changed) {
      verbose('player(s) assigned. broadcast wait-player');
      updateClient(team, 'wait-player');
    }

    if (team.members.length === TEAMSIZE) {
      verbose('team is full(%s). move this team to wait-confirm list', TEAMSIZE);
      // add this team to wait-confirm list
      team.move(teams.waitPlayer, teams.waitConfirm);

      // emit wait-confirm to every team member.
      updateClient(team, 'wait-confirm');
    }
  }
}

function join() {
  verbose('queue/join, username: %s', this.socket.username);
  queue.push(this.socket.username);
  user[this.socket.username] = { socket: this.socket };
  assignTeam();
}

function exit() {
  var username = this.socket.username;
  verbose('queue/exit, username: %s', username);
  if (user[username]) {
    var team = user[username].team;
    if (team) {
      var isWaitingPlayer = teams.waitPlayer.indexOf(team) > -1;
      var isWaitingConfirm = teams.waitConfirm.indexOf(team) > -1;
      if (isWaitingPlayer || isWaitingConfirm) {
        team.removeUser(username);
        if (isWaitingConfirm) {
          updateClient(team, 'wait-player');
          team.move(teams.waitConfirm, teams.waitPlayer);
        }
      } else {
        error('queue/exit, abnormal state');
      }
    }
  }
}

function confirm() {
  var username = this.socket.username;
  verbose('queue/confirm, username: %s', username);
  if (user[username]) {
    var team = user[username].team;
    if (teams.waitConfirm.indexOf(team) < 0) {
      error('queue/confirm, abnormal state');
      return;
    }

    team.confirmed[username] = true;
    updateClient(team, 'wait-confirm');
    // from now, client knows this confirm makes game start or not.

    var everyoneConfirmed = true;
    for (var u in team.confirmed) {
      verbose('user: %s, confirmed: %s', u, team.confirmed[u]);
      if (!team.confirmed[u]) {
        verbose('queue/confirm, not everyone confirmed yet');
        everyoneConfirmed = false;
        break;
      }
    }

    if (everyoneConfirmed) {
      verbose('queue/confirm, everyone confirmed! start a game');
      team.move(teams.waitConfirm, null);
      // TODO: start game!
    }
  }
}

function dodge() {
  var username = this.socket.username;
  verbose('queue/dodge, username: %s', username);
  if (user[username]) {
    var team = user[username].team;
    if (team) {
      if (teams.waitConfirm.indexOf(team) < 0) {
        error('queue/dodge, abnormal state');
        return;
      }

      team.removeUser(username);
      team.move(teams.waitConfirm, teams.waitPlayer);
      updateClient(team, 'wait-player');
      verbose('queue/dodge, player(%s) did dodge.. team now goes waiting player', username);
      queue.push(username);
      verbose('queue/dodge, player(%s) goes into a queue and is gonna get a new team', username);
      assignTeam();
    }
  }
}

function disconnect() {
  var username = this.socket.username;
  verbose('disconnect. %s', username);

  var index = queue.indexOf(username);
  if (index > -1) {
    queue.splice(index, 1);
  }

  if (user[username]) {
    var team = user[username].team;
    if (team) {
      var isWaitingPlayer = teams.waitPlayer.indexOf(team) > -1;
      var isWaitingConfirm = teams.waitConfirm.indexOf(team) > -1;
      if (isWaitingPlayer || isWaitingConfirm) {
        team.removeUser(username);
        updateClient(team, 'wait-player');
        if (isWaitingConfirm) {
          verbose('user disconnected was in the team waiting confirm.. this team goes waiting players again ');
          team.move(teams.waitConfirm, teams.waitPlayer);
        }
      }
    }
  }
}

module.exports = generate({
  'queue/join': { name: 'join', function: join },
  'queue/exit': { name: 'exit', function: exit },
  'queue/confirm': { name: 'confirm', function: confirm },
  'queue/dodge': { name: 'dodge', function: dodge },
  'disconnect': { name: 'disconnect', function: disconnect }
});
<commit_msg>queue/dodge do same thing with queue/exit.<commit_after>"use strict";

var error = require('debug')('error');
var generate = require('./base');
var room = require('../room');
var verbose = require('debug')('verbose:queue');

var TEAMSIZE = 6;

var user = {};
var teams = { waitPlayer: [], waitConfirm: [] };
var queue = [];

function Team() {
  verbose('Team() constructor called');
  this.room = new room.Room();
  this.members = [];
  this.confirmed = {};
}

Team.prototype.push = function (username) {
  verbose('Team.push(%s)', username);
  var socket = user[username].socket;
  this.room.push(socket);
  this.members.push(username);
  user[username].team = this;
  this.confirmed[username] = false;
};

Team.prototype.move = function (src, dest) {
  verbose('Team.move(%s, %s)', src, dest);
  var index = src.indexOf(this);
  if (index > -1) {
    src.splice(index, 1);
    verbose('Team.move(%s, %s), remove team from src', src, dest);
    if (dest) {
      verbose('Team.move(%s, %s), push team to dest', src, dest);
      dest.push(this);
    }
  } else {
    verbose('Team.move(%s, %s), src does not contain this team.', src, dest);
  }
};

Team.prototype.removeUser = function (username) {
  verbose('Team.removeUser(%s)', username);
  var index = this.members.indexOf(username);
  if (index > -1) {
    this.members.splice(index, 1);
    this.room.remove(user[username].socket);
    user[username].team = null;
    delete this.confirmed[username];
  }
};

function updateClient(team, state) {
  if (!team instanceof Team || typeof state !== 'string') {
    error('updateClient, check arguments');
    return;
  }

  var current;
  if (state === 'wait-player') {
    current = team.members.length;
  } else if (state === 'wait-confirm') {
    current = Object.keys(team.confirmed)
      .map(function (k) {
        return team.confirmed[k];
      }).filter(function (v) {
        return v;
      }).length;
  } else {
    error('queue/update, abnormal state(%s)', state);
    return;
  }

  verbose('emit queue/update, state: %s, current: %s', state, current);
  team.room.emit('queue/update', {
    state: state,
    current: current
  });
}

function assignTeam() {
  verbose('assignTeam');
  if (queue.length && teams.waitPlayer.length === 0) {
    verbose('no team waiting player. make a new one.');
    teams.waitPlayer.push(new Team());
    verbose('teams.waitPlayer.length = %s', teams.waitPlayer.length);
  }

  for (var i = 0; i < teams.waitPlayer.length && queue.length; i++) {
    var team = teams.waitPlayer[i];
    var changed = false;
    while (team.members.length < TEAMSIZE && queue.length) {
      team.push(queue.shift());
      changed = true;
    }

    // if any changes, emit queue/update(wait-player) to every team member
    if (changed) {
      verbose('player(s) assigned. broadcast wait-player');
      updateClient(team, 'wait-player');
    }

    if (team.members.length === TEAMSIZE) {
      verbose('team is full(%s). move this team to wait-confirm list', TEAMSIZE);
      // add this team to wait-confirm list
      team.move(teams.waitPlayer, teams.waitConfirm);

      // emit wait-confirm to every team member.
      updateClient(team, 'wait-confirm');
    }
  }
}

function join() {
  verbose('queue/join, username: %s', this.socket.username);
  queue.push(this.socket.username);
  user[this.socket.username] = { socket: this.socket };
  assignTeam();
}

function exit() {
  var username = this.socket.username;
  verbose('queue/exit, username: %s', username);
  if (user[username]) {
    var team = user[username].team;
    if (team) {
      var isWaitingPlayer = teams.waitPlayer.indexOf(team) > -1;
      var isWaitingConfirm = teams.waitConfirm.indexOf(team) > -1;
      if (isWaitingPlayer || isWaitingConfirm) {
        team.removeUser(username);
        if (isWaitingConfirm) {
          team.move(teams.waitConfirm, teams.waitPlayer);
          updateClient(team, 'wait-player');
        }
      } else {
        error('queue/exit, abnormal state');
      }
    }
  }
}

function confirm() {
  var username = this.socket.username;
  verbose('queue/confirm, username: %s', username);
  if (user[username]) {
    var team = user[username].team;
    if (teams.waitConfirm.indexOf(team) < 0) {
      error('queue/confirm, abnormal state');
      return;
    }

    team.confirmed[username] = true;
    updateClient(team, 'wait-confirm');
    // from now, client knows this confirm makes game start or not.

    var everyoneConfirmed = true;
    for (var u in team.confirmed) {
      verbose('user: %s, confirmed: %s', u, team.confirmed[u]);
      if (!team.confirmed[u]) {
        verbose('queue/confirm, not everyone confirmed yet');
        everyoneConfirmed = false;
        break;
      }
    }

    if (everyoneConfirmed) {
      verbose('queue/confirm, everyone confirmed! start a game');
      team.move(teams.waitConfirm, null);
      // TODO: start game!
    }
  }
}

function disconnect() {
  var username = this.socket.username;
  verbose('disconnect. %s', username);

  var index = queue.indexOf(username);
  if (index > -1) {
    queue.splice(index, 1);
  }

  if (user[username]) {
    var team = user[username].team;
    if (team) {
      var isWaitingPlayer = teams.waitPlayer.indexOf(team) > -1;
      var isWaitingConfirm = teams.waitConfirm.indexOf(team) > -1;
      if (isWaitingPlayer || isWaitingConfirm) {
        team.removeUser(username);
        updateClient(team, 'wait-player');
        if (isWaitingConfirm) {
          verbose('user disconnected was in the team waiting confirm.. this team goes waiting players again ');
          team.move(teams.waitConfirm, teams.waitPlayer);
        }
      }
    }
  }
}

module.exports = generate({
  'queue/join': { name: 'join', function: join },
  'queue/exit': { name: 'exit', function: exit },
  'queue/confirm': { name: 'confirm', function: confirm },
  'queue/dodge': { name: 'dodge', function: exit },
  'disconnect': { name: 'disconnect', function: disconnect }
});
<|endoftext|>


<commit_before>/**
 * Copyright IBM Corp. 2016, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

import React from 'react';
import { settings } from 'carbon-components';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean, select, text } from '@storybook/addon-knobs';
import Add16 from '@carbon/icons-react/lib/add/16';
import OverflowMenu from '../OverflowMenu';
import OverflowMenuItem from '../OverflowMenuItem';

const { prefix } = settings;
const directions = {
  'Bottom of the trigger button (bottom)': 'bottom',
  'Top of the trigger button (top)': 'top',
};

const props = {
  menu: () => ({
    direction: select('Menu direction (direction)', directions, 'bottom'),
    ariaLabel: text('ARIA label (ariaLabel)', ''),
    iconDescription: text('Icon description (iconDescription)', ''),
    flipped: boolean('Flipped (flipped)', false),
    onClick: action('onClick'),
    onFocus: action('onFocus'),
    onKeyDown: action('onKeyDown'),
    onClose: action('onClose'),
    onOpen: action('onOpen'),
  }),
  menuItem: () => ({
    className: 'some-class',
    disabled: boolean('Disabled (disabled)', false),
    requireTitle: boolean(
      'Use hover over text for menu item (requireTitle)',
      false
    ),
    onClick: action('onClick'),
  }),
};

const OverflowMenuExample = ({ overflowMenuProps, overflowMenuItemProps }) => (
  <>
    <OverflowMenu {...overflowMenuProps}>
      <OverflowMenuItem
        {...overflowMenuItemProps}
        itemText="Option 1"
        primaryFocus
      />
      <OverflowMenuItem
        {...overflowMenuItemProps}
        itemText="Option 2 is an example of a really long string and how we recommend handling this"
        requireTitle
      />
      <OverflowMenuItem {...overflowMenuItemProps} itemText="Option 3" />
      <OverflowMenuItem {...overflowMenuItemProps} itemText="Option 4" />
      <OverflowMenuItem
        {...overflowMenuItemProps}
        itemText={

            <>
              <div
                className={`${prefix}--overflow-menu-options__option-content`}>
                Add

              <Add16 />
            </>

        }
      />
      <OverflowMenuItem
        {...overflowMenuItemProps}
        itemText="Danger option"
        hasDivider
        isDelete
      />
    </OverflowMenu>
    <OverflowMenu {...overflowMenuProps}>
      <OverflowMenuItem
        {...overflowMenuItemProps}
        itemText="Option 1"
        primaryFocus
      />
      <OverflowMenuItem
        {...overflowMenuItemProps}
        itemText="Option 2 is an example of a really long string and how we recommend handling this"
      />
    </OverflowMenu>
  </>
);

storiesOf('OverflowMenu', module)
  .addDecorator(withKnobs)
  .add(
    'basic',
    () => (
      <OverflowMenuExample
        overflowMenuProps={props.menu()}
        overflowMenuItemProps={props.menuItem()}
      />
    ),
    {
      info: {
        text: `
            Overflow Menu is used when additional options are available to the user and there is a space constraint.
            Create Overflow Menu Item components for each option on the menu.
          `,
      },
    }
  )
  .add(
    'with links',
    () => (
      <OverflowMenuExample
        overflowMenuProps={props.menu()}
        overflowMenuItemProps={{
          ...props.menuItem(),
          href: 'https://www.ibm.com',
        }}
      />
    ),
    {
      info: {
        text: `
            Overflow Menu is used when additional options are available to the user and there is a space constraint.
            Create Overflow Menu Item components for each option on the menu.

            When given \`href\` props, menu items render as  tags to facilitate usability.
          `,
      },
    }
  )
  .add(
    'custom trigger',
    () => (
      <OverflowMenuExample
        overflowMenuProps={{
          ...props.menu(),
          style: { width: 'auto' },
          renderIcon: () => (

Custom trigger

          ),
        }}
        overflowMenuItemProps={props.menuItem()}
      />
    ),
    {
      info: {
        text: `
            Sometimes you just want to render something other than an icon
          `,
      },
    }
  );
<commit_msg>fix(overflow-menu): remove add from menu (#2309)<commit_after>/**
 * Copyright IBM Corp. 2016, 2018
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean, select, text } from '@storybook/addon-knobs';
import OverflowMenu from '../OverflowMenu';
import OverflowMenuItem from '../OverflowMenuItem';

const directions = {
  'Bottom of the trigger button (bottom)': 'bottom',
  'Top of the trigger button (top)': 'top',
};

const props = {
  menu: () => ({
    direction: select('Menu direction (direction)', directions, 'bottom'),
    ariaLabel: text('ARIA label (ariaLabel)', ''),
    iconDescription: text('Icon description (iconDescription)', ''),
    flipped: boolean('Flipped (flipped)', false),
    onClick: action('onClick'),
    onFocus: action('onFocus'),
    onKeyDown: action('onKeyDown'),
    onClose: action('onClose'),
    onOpen: action('onOpen'),
  }),
  menuItem: () => ({
    className: 'some-class',
    disabled: boolean('Disabled (disabled)', false),
    requireTitle: boolean(
      'Use hover over text for menu item (requireTitle)',
      false
    ),
    onClick: action('onClick'),
  }),
};

const OverflowMenuExample = ({ overflowMenuProps, overflowMenuItemProps }) => (
  <>
    <OverflowMenu {...overflowMenuProps}>
      <OverflowMenuItem
        {...overflowMenuItemProps}
        itemText="Option 1"
        primaryFocus
      />
      <OverflowMenuItem
        {...overflowMenuItemProps}
        itemText="Option 2 is an example of a really long string and how we recommend handling this"
        requireTitle
      />
      <OverflowMenuItem {...overflowMenuItemProps} itemText="Option 3" />
      <OverflowMenuItem {...overflowMenuItemProps} itemText="Option 4" />
      <OverflowMenuItem
        {...overflowMenuItemProps}
        itemText="Danger option"
        hasDivider
        isDelete
      />
    </OverflowMenu>
    <OverflowMenu {...overflowMenuProps}>
      <OverflowMenuItem
        {...overflowMenuItemProps}
        itemText="Option 1"
        primaryFocus
      />
      <OverflowMenuItem
        {...overflowMenuItemProps}
        itemText="Option 2 is an example of a really long string and how we recommend handling this"
      />
    </OverflowMenu>
  </>
);

storiesOf('OverflowMenu', module)
  .addDecorator(withKnobs)
  .add(
    'basic',
    () => (
      <OverflowMenuExample
        overflowMenuProps={props.menu()}
        overflowMenuItemProps={props.menuItem()}
      />
    ),
    {
      info: {
        text: `
            Overflow Menu is used when additional options are available to the user and there is a space constraint.
            Create Overflow Menu Item components for each option on the menu.
          `,
      },
    }
  )
  .add(
    'with links',
    () => (
      <OverflowMenuExample
        overflowMenuProps={props.menu()}
        overflowMenuItemProps={{
          ...props.menuItem(),
          href: 'https://www.ibm.com',
        }}
      />
    ),
    {
      info: {
        text: `
            Overflow Menu is used when additional options are available to the user and there is a space constraint.
            Create Overflow Menu Item components for each option on the menu.

            When given \`href\` props, menu items render as  tags to facilitate usability.
          `,
      },
    }
  )
  .add(
    'custom trigger',
    () => (
      <OverflowMenuExample
        overflowMenuProps={{
          ...props.menu(),
          style: { width: 'auto' },
          renderIcon: () => (

Custom trigger

          ),
        }}
        overflowMenuItemProps={props.menuItem()}
      />
    ),
    {
      info: {
        text: `
            Sometimes you just want to render something other than an icon
          `,
      },
    }
  );
<|endoftext|>


<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;
        // if from homepage, allow automagic, if from URL, do not...
        const autoMagic = location.query.auto === 'true';
        this.setState({ queryInStore: false }); // if/def automagic here
        this.setQueryFromLocation(location, autoMagic);
      }
      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, autoName) {
        // 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.query.q.replace(/:""/g, ':"\\"').replace(/"",/g, '\\"",'), autoName);
        }
      }
      setQueryFromSearch(search, autoName) {
        // const queryAsJsonStr = search.slice(3, search.length);
        this.setQueryFromString(search, autoName);
      }
      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, autoNaming) {
        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,
          };
        } else {
          extraDefaults = { autoNaming };
        }
        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>In conjunction with #934<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;
        // if from homepage, allow automagic, if from URL, do not...
        const autoMagic = location.query.auto === 'true';
        this.setState({ queryInStore: false }); // if/def automagic here
        this.setQueryFromLocation(location, autoMagic);
      }
      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
          }
          if (nextProps.queries.filter(q => q.sources.length > 0).length === 0 && nextProps.queries.filter(q => q.collections.length > 0).length === 0) {
            this.setState({ queryInStore: true });
            updateUrl(nextProps.queries, isLoggedIn);
          }
        } else if (lastSearchTime !== nextProps.lastSearchTime) {
          updateUrl(nextProps.queries, isLoggedIn);
        } else {
          // console.log('  other change');
        }
      }
      setQueryFromLocation(location, autoName) {
        // 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.query.q.replace(/:""/g, ':"\\"').replace(/"",/g, '\\"",'), autoName);
        }
      }
      setQueryFromSearch(search, autoName) {
        // const queryAsJsonStr = search.slice(3, search.length);
        this.setQueryFromString(search, autoName);
      }
      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, autoNaming) {
        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,
          };
        } else {
          extraDefaults = { autoNaming };
        }
        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|>