<commit_before>var $ = require('jquery');
var m = require('mithril');
var waterbutler = require('js/waterbutler');

// Local requires
var utils = require('./util.js');
var FileTree = require('./tree.js');
var FileRenderer = require('./render.js');
var FileEditor = require('./editor.js');
var FileRevisionsTable = require('./revisions.js');

//Sanity
var Panel = utils.Panel;
var PanelToggler = utils.PanelToggler;

var FileViewPage = {
    controller: function(context) {
        var self = this;
        self.context = context;
        self.file = self.context.file;
        self.node = self.context.node;

        $.extend(self.file.urls, {
            delete: waterbutler.buildDeleteUrl(self.file.path, self.file.provider, self.node.id),
            content: waterbutler.buildDownloadUrl(self.file.path, self.file.provider, self.node.id),
            metadata: waterbutler.buildMetadataUrl(self.file.path, self.file.provider, self.node.id),
            revisions: waterbutler.buildRevisionsUrl(self.file.path, self.file.provider, self.node.id)
        });

        self.panels = [
            Panel('Tree', FileTree, [self.node.urls.api]),
            Panel('Edit', FileEditor, [self.file.urls.content]),
            Panel('View', FileRenderer, [self.file.urls.render], true),
            Panel('Revisions', FileRevisionsTable, [self.file, self.node], true),
        ];
    },
    view: function(ctrl) {
        return m('.file-view-page', [
            m.component(PanelToggler, ctrl.panels)
        ]);
    }
};

module.exports = function(context) {
    return m.component(FileViewPage, context);
};
<commit_msg>Updates to index.js<commit_after>var $ = require('jquery');
var m = require('mithril');
var waterbutler = require('js/waterbutler');

// Local requires
var utils = require('./util.js');
var FileTree = require('./tree.js');
var FileRenderer = require('./render.js');
var FileEditor = require('./editor.js');
var FileRevisionsTable = require('./revisions.js');

//Sanity
var Panel = utils.Panel;
var PanelToggler = utils.PanelToggler;

var FileViewPage = {
    controller: function(context) {
        var self = this;
        self.context = context;
        self.file = self.context.file;
        self.node = self.context.node;

        $.extend(self.file.urls, {
            delete: waterbutler.buildDeleteUrl(self.file.path, self.file.provider, self.node.id),
            content: waterbutler.buildDownloadUrl(self.file.path, self.file.provider, self.node.id),
            metadata: waterbutler.buildMetadataUrl(self.file.path, self.file.provider, self.node.id),
            revisions: waterbutler.buildRevisionsUrl(self.file.path, self.file.provider, self.node.id)
        });

        self.reloadFile = function() {
            self.panels.forEach(function(panel) {
                if (panel.reload) {
                    panel.reload();
                }
            });
        };

        self.panels = [
            Panel('Tree', FileTree, [self.node.urls.api]),
            Panel('Edit', FileEditor, [self.file.urls.content, self.reloadFile]),
            Panel('View', FileRenderer, [self.file.urls.render, self.file.urls.sharejs, self.context.editorMeta, self.reloadFile], true),
            Panel('Revisions', FileRevisionsTable, [self.file, self.node], true),
        ];

    },
    view: function(ctrl) {
        return m('.file-view-page', [
            m.component(PanelToggler, ctrl.panels)
        ]);
    }
};

module.exports = function(context) {
    return m.component(FileViewPage, context);
};
<|endoftext|>


<commit_before>import React from 'react';
import { assert } from 'chai';
import { createShallow, getClasses, createMount } from '../test-utils';
import MenuItem from '../MenuItem';
import Input from '../Input';
import Select from './Select';

describe('<Select />', () => {
  let shallow;
  let classes;
  let mount;
  const defaultProps = {
    input: <Input />,
    children: [<MenuItem value="1">1</MenuItem>, <MenuItem value="2">2</MenuItem>],
  };

  before(() => {
    shallow = createShallow({ dive: true });
    classes = getClasses(<Select {...defaultProps} />);
    mount = createMount();
  });

  after(() => {
    mount.cleanUp();
  });

  it('should render a correct top element', () => {
    const wrapper = shallow(<Select {...defaultProps} />);
    assert.strictEqual(wrapper.type(), Input);
  });

  it('should provide the classes to the input component', () => {
    const wrapper = shallow(<Select {...defaultProps} />);
    assert.deepEqual(wrapper.props().inputProps.classes, classes);
  });

  describe('prop: inputProps', () => {
    it('should be able to provide a custom classes property', () => {
      const wrapper = shallow(
        <Select
          {...defaultProps}
          inputProps={{
            classes: { root: 'root' },
          }}
        />,
      );
      assert.deepEqual(wrapper.props().inputProps.classes, {
        ...classes,
        root: `${classes.root} root`,
      });
    });
  });

  it('should be able to mount the component', () => {
    const wrapper = mount(
      <Select {...defaultProps} value={10}>
        <MenuItem value="">
          None
        </MenuItem>
        <MenuItem value={10}>Ten</MenuItem>
        <MenuItem value={20}>Twenty</MenuItem>
        <MenuItem value={30}>Thirty</MenuItem>
      </Select>,
    );
    assert.strictEqual(wrapper.find('input').props().value, 10);
  });
});
<commit_msg>[Select] Test if child passed to onChange handler (#12852)<commit_after>import React from 'react';
import { assert } from 'chai';
import { createShallow, getClasses, createMount } from '../test-utils';
import MenuItem from '../MenuItem';
import Input from '../Input';
import Select from './Select';
import { spy } from 'sinon';

describe('<Select />', () => {
  let shallow;
  let classes;
  let mount;
  const defaultProps = {
    input: <Input />,
    children: [<MenuItem value="1">1</MenuItem>, <MenuItem value="2">2</MenuItem>],
  };

  before(() => {
    shallow = createShallow({ dive: true });
    classes = getClasses(<Select {...defaultProps} />);
    mount = createMount();
  });

  after(() => {
    mount.cleanUp();
  });

  it('should render a correct top element', () => {
    const wrapper = shallow(<Select {...defaultProps} />);
    assert.strictEqual(wrapper.type(), Input);
  });

  it('should provide the classes to the input component', () => {
    const wrapper = shallow(<Select {...defaultProps} />);
    assert.deepEqual(wrapper.props().inputProps.classes, classes);
  });

  describe('prop: inputProps', () => {
    it('should be able to provide a custom classes property', () => {
      const wrapper = shallow(
        <Select
          {...defaultProps}
          inputProps={{
            classes: { root: 'root' },
          }}
        />,
      );
      assert.deepEqual(wrapper.props().inputProps.classes, {
        ...classes,
        root: `${classes.root} root`,
      });
    });
  });

  it('should be able to mount the component', () => {
    const wrapper = mount(
      <Select {...defaultProps} value={10}>
        <MenuItem value="">
          None
        </MenuItem>
        <MenuItem value={10}>Ten</MenuItem>
        <MenuItem value={20}>Twenty</MenuItem>
        <MenuItem value={30}>Thirty</MenuItem>
      </Select>,
    );
    assert.strictEqual(wrapper.find('input').props().value, 10);
  });
  describe('prop: onChange', () => {
    it('should get selected element from arguments', () => {
      const onChangeHandler = spy();
      const wrapper = mount(<Select onChange={onChangeHandler} {...defaultProps} value={'1'} />);
      wrapper.find('[role="button"]').simulate('click');
      wrapper
        .find(MenuItem)
        .at(1)
        .simulate('click');
      const selected = onChangeHandler.args[0][1];
      assert.strictEqual(React.isValidElement(selected), true, 'should b be a element');
    });
  });
});
<|endoftext|>


<commit_before>const http = require('http');
const url = require('url');
const fs = require('fs');
const nearestColor = require('../node_modules/nearest-color/nearestColor.js');
const colors = JSON.parse(
  fs.readFileSync(__dirname + '/../dist/colornames.json', 'utf8')
);
const port = process.env.PORT || 8080;
const baseUrl = 'v1/';

/**
 * disassembles a HEX color to its RGB components
 * @param   {string} hex hex color representatin
 * @return  {object}     {r,g,b}
 */
const hexToRgb = (hex) => {
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result ? {
      r: parseInt(result[1], 16),
      g: parseInt(result[2], 16),
      b: parseInt(result[3], 16),
  } : null;
};

// object containing the name:hex pairs for nearestColor()
const colorsObj = {};

colors.forEach((c) => {
  // populates object needed for nearestColor()
  colorsObj[c.name] = c.hex;
  // transform hex to RGB
  c.rgb = hexToRgb(c.hex);
});

const nc = nearestColor.from(colorsObj);
/**
 * validates a hex color
 * @param   {string} color hex representation of color
 * @return {boolen}
 */
const validateColor = (color) => (
  /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(color)
);

/**
 * names an array of colors
 * @param   {array} colorArr array containing hex values without the hash
 * @return  {object}         object containing all nearest colors
 */
const nameColors = (colorArr) => {
  let colors = {};
  colorArr.forEach((hex) => {
    const closestColor = nc('#' + hex);
    colors['#' + hex] = {
      name: closestColor.name,
      hex: closestColor.value,
      rgb: closestColor.rgb,
      isExactMatch: closestColor.value.substr(1) === hex,
    };
  });
  return colors;
};

const httpRespond = (response, responseObj = {}, statusCode = 200) => {
  response.writeHead(statusCode, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'POST, GET, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Credentials': false,
    'Access-Control-Max-Age': '86400',
    'Access-Control-Allow-Headers': 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept',
    'Content-Type': 'application/json',
  });

  // ends the response with the API answer
  response.end(JSON.stringify(responseObj));
};

const requestHandler = (request, response) => {
  const requestUrl = url.parse(request.url);
  const isAPI = requestUrl.pathname.indexOf(baseUrl) !== -1;
  let statusCode = 400;
  let colorQuery = request.url.toLowerCase();
      colorQuery = colorQuery.split(baseUrl)[1] || '';

  const urlColorList = colorQuery.split(',');
  const responseObj = {status: 'Someting went wrong', colors: {}};
  const invalidColors = urlColorList.filter((hex) => {
    !validateColor(hex) && hex;
  });

  if (!isAPI) {
    responseObj.status = 'invalid URL: make sure to provide the API version';
    statusCode = 400;
  } else if (!urlColorList[0]) {
    responseObj.status = 'no color(s) provided, returning all the colors';
    responseObj.colors = colors;
    statusCode = 200;
  } else if (invalidColors.length) {
    responseObj.status = `'${invalidColors.join(', ')}' is not a valid HEX color`;
    statusCode = 400;
  } else {
    responseObj.status = `names for '${urlColorList.join(', ')}' returned`;
    responseObj.colors = nameColors(urlColorList);
    statusCode = 200;
  }

  httpRespond(response, responseObj, statusCode);
};

const server = http.createServer(requestHandler);
server.listen(port, '0.0.0.0', (error) => {
  if (error) {
    return console.log(`something terrible happened: ${error}`);
  }
  console.log(`Server running and listening on port ${port}`);
  console.log(`http://localhost:${port}`);
});

<commit_msg>fix($server): makes sure server does not continue, if invalid colors are requested<commit_after>const http = require('http');
const url = require('url');
const fs = require('fs');
const nearestColor = require('../node_modules/nearest-color/nearestColor.js');
const colors = JSON.parse(
  fs.readFileSync(__dirname + '/../dist/colornames.json', 'utf8')
);
const port = process.env.PORT || 8080;
const baseUrl = 'v1/';

/**
 * disassembles a HEX color to its RGB components
 * @param   {string} hex hex color representatin
 * @return  {object}     {r,g,b}
 */
const hexToRgb = (hex) => {
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result ? {
      r: parseInt(result[1], 16),
      g: parseInt(result[2], 16),
      b: parseInt(result[3], 16),
  } : null;
};

// object containing the name:hex pairs for nearestColor()
const colorsObj = {};

colors.forEach((c) => {
  // populates object needed for nearestColor()
  colorsObj[c.name] = c.hex;
  // transform hex to RGB
  c.rgb = hexToRgb(c.hex);
});

const nc = nearestColor.from(colorsObj);
/**
 * validates a hex color
 * @param   {string} color hex representation of color
 * @return {boolen}
 */
const validateColor = (color) => (
  /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(color)
);

/**
 * names an array of colors
 * @param   {array} colorArr array containing hex values without the hash
 * @return  {object}         object containing all nearest colors
 */
const nameColors = (colorArr) => {
  let colors = {};
  colorArr.forEach((hex) => {
    const closestColor = nc('#' + hex);
    colors['#' + hex] = {
      name: closestColor.name,
      hex: closestColor.value,
      rgb: closestColor.rgb,
      isExactMatch: closestColor.value.substr(1) === hex,
    };
  });
  return colors;
};

const httpRespond = (response, responseObj = {}, statusCode = 200) => {
  response.writeHead(statusCode, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'POST, GET, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Credentials': false,
    'Access-Control-Max-Age': '86400',
    'Access-Control-Allow-Headers': 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept',
    'Content-Type': 'application/json',
  });

  // ends the response with the API answer
  response.end(JSON.stringify(responseObj));
};

const requestHandler = (request, response) => {
  const requestUrl = url.parse(request.url);
  const isAPI = requestUrl.pathname.indexOf(baseUrl) !== -1;
  let statusCode = 400;
  let colorQuery = request.url.toLowerCase();
      colorQuery = colorQuery.split(baseUrl)[1] || '';

  const urlColorList = colorQuery.split(',');
  const responseObj = {status: 'Someting went wrong', colors: {}};
  const invalidColors = urlColorList.filter((hex) => {
    !validateColor(hex) && hex;
  });

  if (!isAPI) {
    responseObj.status = 'invalid URL: make sure to provide the API version';
    statusCode = 400;
  } else if (!urlColorList[0]) {
    responseObj.status = 'no color(s) provided, returning all the colors';
    responseObj.colors = colors;
    statusCode = 200;
  } else if (invalidColors.length) {
    responseObj.status = `'${invalidColors.join(', ')}' is not a valid HEX color`;
    statusCode = 400;
  } else if (statusCode === 200) {
    responseObj.status = `names for '${urlColorList.join(', ')}' returned`;
    responseObj.colors = nameColors(urlColorList);
    statusCode = 200;
  }

  httpRespond(response, responseObj, statusCode);
};

const server = http.createServer(requestHandler);
server.listen(port, '0.0.0.0', (error) => {
  if (error) {
    return console.log(`something terrible happened: ${error}`);
  }
  console.log(`Server running and listening on port ${port}`);
  console.log(`http://localhost:${port}`);
});

<|endoftext|>


<commit_before>import Component from '@ember/component';
import { inject as service } from '@ember/service';
import EmberObject, { computed, observer } from '@ember/object';
import { A } from '@ember/array';
import { later, cancel } from '@ember/runloop';
import $ from 'jquery';

const O = EmberObject.create.bind(EmberObject);

export default Component.extend({
  store: service(),
  userAuth: service('user-auth'),
  apiCall: service('api-call'),
  internalState: service('internal-state'),
  router: service(),

  taleInstanceName: "",
  filteredSet: A(),
  filters: ['All', 'Mine', 'Published', 'Recent'],
  filter: 'All',
  numberOfModels: 0,
  pageNumber: 1,
  totalPages: 1,
  leftButtonState: "disabled",
  rightButtonState: "disabled",
  lastAnimationTime: 0,
  animationRefreshTime: 500, // min ms time between animation refreshes
  item: null,
  guid: null,
  models: A([]),
  modelsInView: A([]),
  listView: false,
  showFilter: true,
  loadingTales: true,
  selectedTale: O({}),
  message: 'Tales loading. Please, wait.',

  filterObserver: observer('filter', function () {
    this.setFilter.call(this);
  }),

  creatorObserver: observer('modelsInView', function() {
    this.modelsInView.forEach(tale => {
      if (tale && tale.id) {
        this.apiCall.fetchTaleData(tale.id, false);
      }
    });
  }),

  instancePoller: observer('modelsInView', 'instances', function() {
    const component = this;

    // When instance models change, check if we need to poll
    component.modelsInView.forEach(model => {
      // If we see an instance that is "Launching", poll until it completes
      if (model.instance && model.instance.status === 0) {
        component.apiCall.waitForInstance(model.instance);
      }
    });
  }),

  modelObserver: observer('filter', 'model', 'numberOfModels', function () {
    let guid = this.get('guid');

    if (!guid) {
      guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        let r = Math.random() * 16 | 0,
          v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
      });
      this.set('guid', guid);
    }

    let d = new Date();
    let milliseconds = d.getTime();
    let lastMSTime = Number(this.get('lastAnimationTime'));

    if (milliseconds - lastMSTime > Number(this.get('animationRefreshTime'))) {
      $('.selectable.cards.' + guid + ' .image img')
        .transition('jiggle');
      this.set('lastAnimationTime', milliseconds);
    }
  }),

  init() {
    this._super(...arguments);
    console.log("Attributes updated");

    let models = this.get("models");
    let component = this;
    component.set('loadingTales', false);
    component.set('searchView', models.tales);
    component.updateModels(component, models.tales);
    component.set('addButtonLogo', '/icons/plus-sign.png');
    component.setFilter();

    // Check if any instance is LAUNCHING
    component.get('store').findAll('instance').then(instances => {
        instances.forEach(instance => {
            // If any instance is LAUNCHING, poll until it finishes
            if (!instance.status) {
                console.log('Instance is LAUNCHING. Starting poller...');
                component.get('apiCall').waitForInstance(instance);
            }
        });
    }).catch(err => console.error(err));
  },
  didRender() {
    $('.selectable.cards .image').dimmer({
      on: 'hover'
    });
  },
  didUpdate() {

  },

  setFilter() {
    // 4 tabs public (all), mine, published, recently viewed
    const filter = this.get('filter');
    const models = this.get('models');
    this.set('loadingTales', true);

    if (filter === 'All') {
      this.set('filteredSet', models.tales);
    } else if (filter === 'Mine') {
      const userId = this.get('userAuth').getCurrentUserID();
      this.set('filteredSet', models.tales.filter(m => m.creatorId === userId));
    } else if (filter === 'Published') {
      this.set('filteredSet', models.tales.filter(m => {
        return m.publishInfo.length;
      }));
    } else if (filter === 'Recent') {
      const recentTales = this.get('internalState').getRecentTales();
      this.set('filteredSet', models.tales.filter(m => {
        return (recentTales.indexOf(m.get('id')) > -1);
      }));
    } else {
      this.set('filteredSet', A());
    }

    this.actions.searchFilter.call(this, filter);
    this.actions.toggleFiltersVisibility.call(this);
  },

  updateModels(component, models) {
    let modelsInView = A([]);
    models.forEach(tale => {
      if (tale && tale._id) {

        if (!tale.get("icon")) {
          if (tale.get("meta")) {
            let meta = tale.get("meta");
            if (meta.get('provider') !== "DataONE")
              tale.set('icon', "/icons/globus-logo-large.png");
            else
              tale.set('icon', "/icons/d1-logo-large.png");
          } else {
            tale.set('icon', "/images/whole_tale_logo.png");
          }
        }

        let description = tale.get('description');

        if (!description) {
          tale.set('tagName', "No Description ...");
        } else {
          if (description.length > 200)
            tale.set('tagName', description.substring(0, 200) + "..");
          else
            tale.set('tagName', description);
        }

        modelsInView.pushObject(O(tale));
      }
    });

    component.set("modelsInView", modelsInView);
  },

  filterRunningTales() {
    const component = this;

    // Check instances to gather running tales
    component.get('store').findAll('instance').then(instances => {
      const running = A([]);

      component.searchView.forEach(tale => {
        const instance = instances.find(i => i.taleId === tale.id);
        if (instance) {
          running.pushObject(tale);
        }
      });

      component.set('runningTales', running);
    }).catch(err => console.error(err));
  },

  actions: {
    toggleListView() {
      let newValue = !this.get('listView');
      this.set('listView', newValue);
    },
    toggleFiltersVisibility() {
      let component = this;
      later(function() {
        let newValue = !component.get('showFilter');
        component.set('showFilter', newValue);
      }, 100);
    },
    removeCurrentFilter() {
      this.set('filter', 'All');
      this.setFilter();
    },
    selectFilter(filter) {
      const component = this;
      component.set('filter', filter);
      this.setFilter();
    },
    searchFilter(filter) {
      let searchStr = this.get('searchStr');

      const filteredSet = this.get("filteredSet");
      const component = this;

      return new Promise((resolve, reject) => {
        let searchView = [];
        filteredSet.forEach(model => {
          if (model && model.id) {
            let title = model.get('title');
            if (new RegExp(searchStr, "i").test(title)) {
              searchView.push(model);
            }
          }
        });
        component.set('loadingTales', false);
        resolve(searchView);
      }).then((searchView) => {
        component.set('searchView', searchView);
        component.set('modelsInView', searchView);

        this.filterRunningTales();
      });
    },

    select(model) {
      this.set('item', model);
      this.sendAction('action', model); // sends to compose.js controller, action itemSelected, based on template spec.
    },

    attemptDeletion(tale) {
      let component = this;
      if (tale) {
        let instance = tale.get('instance');
        if (instance) {
            let message = `There is at least one running instance associated to this Tale. You must shut it down before deleting the Tale.`;
            component.set('cannotDeleteMessage', message);
            component.actions.openWarningModal.call(this);
        } else {
            component.actions.openDeleteModal.call(this, tale);
        }
      }
    },

    openWarningModal() {
      let selector = `.ui.warning-modal.modal`;
      $(selector).modal('show');
    },

    openDeleteModal(tale) {
      let component = this;
      component.set('selectedTale', tale);
      let selector = `.delete-modal-tale>.ui.delete-modal.modal`;
      later(() => {
        $(selector).modal('show');
      }, 500);
    },

    approveDelete(model) {
      let component = this;

      model.destroyRecord({
        reload: true
      }).then(() => {
        // refresh
        component.updateModels(component, component.get('models').tales);
        component.set('selectedTale', undefined);
        component.setFilter();
      });

      return false;
    },

    denyDelete() {
      this.set('selectedTale', undefined);
      return false;
    },

    addNew() {
      this.sendAction("onAddNew");
    },

    openCopyOnLaunchModal(taleToCopy) {
      const component = this;
      $('#copy-on-launch-modal').modal('show');
      component.set('taleToCopy', taleToCopy);
    },

    dismissLaunchError(tale) {
      tale.set('launchStatus', 'stopped');
      tale.set('launchError', null);
      tale.set('launchResetRequest', null);
    },

    submitCopyAndLaunch(taleToCopy) {
      const self = this;
      self.set('copyingTale', true);
      const originalTale = self.get('taleToCopy');
      if (originalTale) {
        self.get('apiCall').copyTale(originalTale).then(taleCopy => {
          self.set('copyingTale', false);
          self.actions.closeCopyOnLaunchModal.call(self);

          // Convert JSON response to an EmberObject
          let eTaleCopy = EmberObject.create(taleCopy);

          // Push to models in view, if it should be shown
          if (self.get('filter') === 'Mine') {
            const tales = self.get('modelsInView');
            tales.pushObject(eTaleCopy);
            self.set('modelsInView', A(tales));
          }

          // Launch the newly-copied tale
          eTaleCopy.set('launchStatus', 'starting');
          return self.get('apiCall').startTale(eTaleCopy).then((instance) => {
            eTaleCopy.set('instance', instance);
            self.get('apiCall').waitForInstance(instance);
            later(() => self.router.transitionTo('run.view', eTaleCopy._id), 500);
          }).catch(err => {
            console.error('Failed to launch Tale', err);
            eTaleCopy.set('launchError');
            eTaleCopy.set('launchStatus', 'error');
          });
        });
      } else {
        console.log('No tale to copy... something went wrong!');
      }
    },

    startTale(tale) {
      const self = this;
      if (tale._accessLevel < 1) {
        // Prompt for confirmation before copying and launching
        return self.actions.openCopyOnLaunchModal.call(self, tale);
      }

      tale.set('launchError', null);
      tale.set('launchStatus', 'starting');
      return self.apiCall.startTale(tale).then((instance) => {
        tale.set('instance', instance);
        self.get('apiCall').waitForInstance(instance);
        self.router.transitionTo('run.view', tale._id);
      }).catch(err => {
        console.error('Failed to launch Tale', err);
        tale.set('launchError', err.message || err);
        tale.set('launchStatus', 'error');
      });
    },

    stopTale(tale) {
      const self = this;

      // Reset state manually when re-launching
      tale.set('launchError', null);
      tale.set('launchStatus', 'stopping');
      tale.set('launchResetRequest', null);

      // TODO: Abstract this to reusable helper?
      let resetStatusAfterMs = (tale, delay) => {
        let resetRequest = later(() => {
          if (!self.isDestroyed) {
            console.log('Resetting tale status:', tale);
            tale.set('launchError', null);
            tale.set('launchStatus', null);
            tale.set('launchResetRequest', null);
          }
        }, delay);
        tale.set('launchResetRequest', resetRequest);
      };

      // TODO: Abstract this to reusable helper?
      let handleStopError = (tale, err) => {
        // deal with the failure here
        tale.set('launchStatus', 'error');
        tale.set('launchError', err.message || err);
        if (console && console.error) {
          console.error('Failed to stop Tale', err);
        } else {
          console.log('Failed to stop Tale', err);
        }

        resetStatusAfterMs(tale, 10000);
      };

      // Add an artificial delay to make the user feel good
      tale.instance.set('status', 0);
      later(() => {
        self.apiCall.stopTale(tale).then((instance) => {
          tale.set('launchError', null);
          tale.set('launchStatus', null);

          console.log('Tale is now stopped:', tale);
          resetStatusAfterMs(tale, 10000);

          self.filterRunningTales();
        }).catch((err) => handleStopError(tale, err));
      }, 1500);
    },
  }
});
<commit_msg>Filter out and hide InstanceStatus.DELETING<commit_after>import Component from '@ember/component';
import { inject as service } from '@ember/service';
import EmberObject, { computed, observer } from '@ember/object';
import { A } from '@ember/array';
import { later, cancel } from '@ember/runloop';
import $ from 'jquery';

const O = EmberObject.create.bind(EmberObject);

export default Component.extend({
  store: service(),
  userAuth: service('user-auth'),
  apiCall: service('api-call'),
  internalState: service('internal-state'),
  router: service(),

  taleInstanceName: "",
  filteredSet: A(),
  filters: ['All', 'Mine', 'Published', 'Recent'],
  filter: 'All',
  numberOfModels: 0,
  pageNumber: 1,
  totalPages: 1,
  leftButtonState: "disabled",
  rightButtonState: "disabled",
  lastAnimationTime: 0,
  animationRefreshTime: 500, // min ms time between animation refreshes
  item: null,
  guid: null,
  models: A([]),
  modelsInView: A([]),
  listView: false,
  showFilter: true,
  loadingTales: true,
  selectedTale: O({}),
  message: 'Tales loading. Please, wait.',

  filterObserver: observer('filter', function () {
    this.setFilter.call(this);
  }),

  creatorObserver: observer('modelsInView', function() {
    this.modelsInView.forEach(tale => {
      if (tale && tale.id) {
        this.apiCall.fetchTaleData(tale.id, false);
      }
    });
  }),

  instancePoller: observer('modelsInView', 'instances', function() {
    const component = this;

    // When instance models change, check if we need to poll
    component.modelsInView.forEach(model => {
      // If we see an instance that is "Launching", poll until it completes
      if (model.instance && model.instance.status === 0) {
        component.apiCall.waitForInstance(model.instance);
      }
    });
  }),

  modelObserver: observer('filter', 'model', 'numberOfModels', function () {
    let guid = this.get('guid');

    if (!guid) {
      guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        let r = Math.random() * 16 | 0,
          v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
      });
      this.set('guid', guid);
    }

    let d = new Date();
    let milliseconds = d.getTime();
    let lastMSTime = Number(this.get('lastAnimationTime'));

    if (milliseconds - lastMSTime > Number(this.get('animationRefreshTime'))) {
      $('.selectable.cards.' + guid + ' .image img')
        .transition('jiggle');
      this.set('lastAnimationTime', milliseconds);
    }
  }),

  init() {
    this._super(...arguments);
    console.log("Attributes updated");

    let models = this.get("models");
    let component = this;
    component.set('loadingTales', false);
    component.set('searchView', models.tales);
    component.updateModels(component, models.tales);
    component.set('addButtonLogo', '/icons/plus-sign.png');
    component.setFilter();

    // Check if any instance is LAUNCHING
    component.get('store').findAll('instance').then(instances => {
        instances.forEach(instance => {
            // If any instance is LAUNCHING, poll until it finishes
            if (!instance.status) {
                console.log('Instance is LAUNCHING. Starting poller...');
                component.get('apiCall').waitForInstance(instance);
            }
        });
    }).catch(err => console.error(err));
  },
  didRender() {
    $('.selectable.cards .image').dimmer({
      on: 'hover'
    });
  },
  didUpdate() {

  },

  setFilter() {
    // 4 tabs public (all), mine, published, recently viewed
    const filter = this.get('filter');
    const models = this.get('models');
    this.set('loadingTales', true);

    if (filter === 'All') {
      this.set('filteredSet', models.tales);
    } else if (filter === 'Mine') {
      const userId = this.get('userAuth').getCurrentUserID();
      this.set('filteredSet', models.tales.filter(m => m.creatorId === userId));
    } else if (filter === 'Published') {
      this.set('filteredSet', models.tales.filter(m => {
        return m.publishInfo.length;
      }));
    } else if (filter === 'Recent') {
      const recentTales = this.get('internalState').getRecentTales();
      this.set('filteredSet', models.tales.filter(m => {
        return (recentTales.indexOf(m.get('id')) > -1);
      }));
    } else {
      this.set('filteredSet', A());
    }

    this.actions.searchFilter.call(this, filter);
    this.actions.toggleFiltersVisibility.call(this);
  },

  updateModels(component, models) {
    let modelsInView = A([]);
    models.forEach(tale => {
      if (tale && tale._id) {

        if (!tale.get("icon")) {
          if (tale.get("meta")) {
            let meta = tale.get("meta");
            if (meta.get('provider') !== "DataONE")
              tale.set('icon', "/icons/globus-logo-large.png");
            else
              tale.set('icon', "/icons/d1-logo-large.png");
          } else {
            tale.set('icon', "/images/whole_tale_logo.png");
          }
        }

        let description = tale.get('description');

        if (!description) {
          tale.set('tagName', "No Description ...");
        } else {
          if (description.length > 200)
            tale.set('tagName', description.substring(0, 200) + "..");
          else
            tale.set('tagName', description);
        }

        modelsInView.pushObject(O(tale));
      }
    });

    component.set("modelsInView", modelsInView);
  },

  filterRunningTales() {
    const component = this;

    // Check instances to gather running tales
    component.get('store').findAll('instance').then(instances => {
      const running = A([]);

      component.searchView.forEach(tale => {
        const instance = instances.filter(i => i.status !== 3).find(i => i.taleId === tale.id);
        if (instance) {
          running.pushObject(tale);
        }
      });

      component.set('runningTales', running);
    }).catch(err => console.error(err));
  },

  actions: {
    toggleListView() {
      let newValue = !this.get('listView');
      this.set('listView', newValue);
    },
    toggleFiltersVisibility() {
      let component = this;
      later(function() {
        let newValue = !component.get('showFilter');
        component.set('showFilter', newValue);
      }, 100);
    },
    removeCurrentFilter() {
      this.set('filter', 'All');
      this.setFilter();
    },
    selectFilter(filter) {
      const component = this;
      component.set('filter', filter);
      this.setFilter();
    },
    searchFilter(filter) {
      let searchStr = this.get('searchStr');

      const filteredSet = this.get("filteredSet");
      const component = this;

      return new Promise((resolve, reject) => {
        let searchView = [];
        filteredSet.forEach(model => {
          if (model && model.id) {
            let title = model.get('title');
            if (new RegExp(searchStr, "i").test(title)) {
              searchView.push(model);
            }
          }
        });
        component.set('loadingTales', false);
        resolve(searchView);
      }).then((searchView) => {
        component.set('searchView', searchView);
        component.set('modelsInView', searchView);

        this.filterRunningTales();
      });
    },

    select(model) {
      this.set('item', model);
      this.sendAction('action', model); // sends to compose.js controller, action itemSelected, based on template spec.
    },

    attemptDeletion(tale) {
      let component = this;
      if (tale) {
        let instance = tale.get('instance');
        if (instance) {
            let message = `There is at least one running instance associated to this Tale. You must shut it down before deleting the Tale.`;
            component.set('cannotDeleteMessage', message);
            component.actions.openWarningModal.call(this);
        } else {
            component.actions.openDeleteModal.call(this, tale);
        }
      }
    },

    openWarningModal() {
      let selector = `.ui.warning-modal.modal`;
      $(selector).modal('show');
    },

    openDeleteModal(tale) {
      let component = this;
      component.set('selectedTale', tale);
      let selector = `.delete-modal-tale>.ui.delete-modal.modal`;
      later(() => {
        $(selector).modal('show');
      }, 500);
    },

    approveDelete(model) {
      let component = this;

      model.destroyRecord({
        reload: true
      }).then(() => {
        // refresh
        component.updateModels(component, component.get('models').tales);
        component.set('selectedTale', undefined);
        component.setFilter();
      });

      return false;
    },

    denyDelete() {
      this.set('selectedTale', undefined);
      return false;
    },

    addNew() {
      this.sendAction("onAddNew");
    },

    openCopyOnLaunchModal(taleToCopy) {
      const component = this;
      $('#copy-on-launch-modal').modal('show');
      component.set('taleToCopy', taleToCopy);
    },

    dismissLaunchError(tale) {
      tale.set('launchStatus', 'stopped');
      tale.set('launchError', null);
      tale.set('launchResetRequest', null);
    },

    submitCopyAndLaunch(taleToCopy) {
      const self = this;
      self.set('copyingTale', true);
      const originalTale = self.get('taleToCopy');
      if (originalTale) {
        self.get('apiCall').copyTale(originalTale).then(taleCopy => {
          self.set('copyingTale', false);
          self.actions.closeCopyOnLaunchModal.call(self);

          // Convert JSON response to an EmberObject
          let eTaleCopy = EmberObject.create(taleCopy);

          // Push to models in view, if it should be shown
          if (self.get('filter') === 'Mine') {
            const tales = self.get('modelsInView');
            tales.pushObject(eTaleCopy);
            self.set('modelsInView', A(tales));
          }

          // Launch the newly-copied tale
          eTaleCopy.set('launchStatus', 'starting');
          return self.get('apiCall').startTale(eTaleCopy).then((instance) => {
            eTaleCopy.set('instance', instance);
            self.get('apiCall').waitForInstance(instance);
            later(() => self.router.transitionTo('run.view', eTaleCopy._id), 500);
          }).catch(err => {
            console.error('Failed to launch Tale', err);
            eTaleCopy.set('launchError');
            eTaleCopy.set('launchStatus', 'error');
          });
        });
      } else {
        console.log('No tale to copy... something went wrong!');
      }
    },

    startTale(tale) {
      const self = this;
      if (tale._accessLevel < 1) {
        // Prompt for confirmation before copying and launching
        return self.actions.openCopyOnLaunchModal.call(self, tale);
      }

      tale.set('launchError', null);
      tale.set('launchStatus', 'starting');
      return self.apiCall.startTale(tale).then((instance) => {
        tale.set('instance', instance);
        self.get('apiCall').waitForInstance(instance);
        self.router.transitionTo('run.view', tale._id);
      }).catch(err => {
        console.error('Failed to launch Tale', err);
        tale.set('launchError', err.message || err);
        tale.set('launchStatus', 'error');
      });
    },

    stopTale(tale) {
      const self = this;

      // Reset state manually when re-launching
      tale.set('launchError', null);
      tale.set('launchStatus', 'stopping');
      tale.set('launchResetRequest', null);

      // TODO: Abstract this to reusable helper?
      let resetStatusAfterMs = (tale, delay) => {
        let resetRequest = later(() => {
          if (!self.isDestroyed) {
            console.log('Resetting tale status:', tale);
            tale.set('launchError', null);
            tale.set('launchStatus', null);
            tale.set('launchResetRequest', null);
          }
        }, delay);
        tale.set('launchResetRequest', resetRequest);
      };

      // TODO: Abstract this to reusable helper?
      let handleStopError = (tale, err) => {
        // deal with the failure here
        tale.set('launchStatus', 'error');
        tale.set('launchError', err.message || err);
        if (console && console.error) {
          console.error('Failed to stop Tale', err);
        } else {
          console.log('Failed to stop Tale', err);
        }

        resetStatusAfterMs(tale, 10000);
      };

      // Add an artificial delay to make the user feel good
      tale.instance.set('status', 0);
      later(() => {
        self.apiCall.stopTale(tale).then((instance) => {
          tale.set('launchError', null);
          tale.set('launchStatus', null);

          console.log('Tale is now stopped:', tale);
          resetStatusAfterMs(tale, 10000);

          self.filterRunningTales();
        }).catch((err) => handleStopError(tale, err));
      }, 1500);
    },
  }
});
<|endoftext|>


<commit_before>import React from 'react'
import {
  difference, filter, first, includes, isEmpty, map, some, sortBy
} from 'lodash'
import { find, get } from 'lodash/fp'
const { array, bool, func, object, string } = React.PropTypes
import cx from 'classnames'
import cheerio from 'cheerio'
import {
  humanDate, nonbreaking, present, textLength, truncate, appendInP
} from '../util/text'
import { sanitize } from 'hylo-utils/text'
import { linkifyHashtags } from '../util/linkify'
import { tagUrl } from '../routes'
import A from './A'
import Avatar from './Avatar'
import Dropdown from './Dropdown'
import { ClickCatchingSpan } from './ClickCatcher'
import { handleMouseOver } from './TagPopover'
import Comment from './Comment'
import CommentForm from './CommentForm'
import Icon from './Icon'
import LinkedPersonSentence from './LinkedPersonSentence'
import LinkPreview from './LinkPreview'
import { connect } from 'react-redux'
import { compose } from 'redux'
import {
  followPost, navigate, removePost, startPostEdit, voteOnPost, pinPost
} from '../actions'
import { same } from '../models'
import { getComments, getCommunities, isPinned } from '../models/post'
import { getCurrentCommunity } from '../models/community'
import { canEditPost, canModerate } from '../models/currentUser'
import { isMobile } from '../client/util'
import decode from 'ent/decode'

const spacer =   •

export const presentDescription = (post, community, opts = {}) =>
  present(sanitize(post.description), {slug: get('slug', community), ...opts})

class Post extends React.Component {
  static propTypes = {
    post: object,
    communities: array,
    community: object,
    comments: array,
    dispatch: func,
    expanded: bool,
    onExpand: func,
    commentId: string
  }

  static childContextTypes = {post: object}

  getChildContext () {
    return {post: this.props.post}
  }

  render () {
    const { post, communities, comments, expanded, onExpand, community } = this.props
    const { tag, media, linkPreview } = post
    const image = find(m => m.type === 'image', media)
    const classes = cx('post', tag, {image, expanded})
    const title = linkifyHashtags(decode(sanitize(post.name || '')), get('slug', community))

    return

      <a name={`post-${post.id}`}>

      {image && }

      {linkPreview && <LinkPreview {...{linkPreview}}/>}

<VoteButton/><Voters/>

      <Attachments/>
      <CommentSection {...{post, expanded, onExpand, comments}}/>

  }
}

export default compose(
  connect((state, { post }) => {
    return {
      comments: getComments(post, state),
      communities: getCommunities(post, state),
      community: getCurrentCommunity(state)
    }
  })
)(Post)

export const UndecoratedPost = Post // for testing

export const Header = ({ communities }, { post, community }) => {
  const { tag } = post
  const person = tag === 'welcome' ? post.relatedUsers[0] : post.user
  const createdAt = new Date(post.created_at)

  return

    <Menu/>
    <Avatar person={person}/>
    {tag === 'welcome'
      ? <WelcomePostHeader communities={communities}/>
      :

          <A className='name' to={`/u/${person.id}`}>{person.name}

            <A to={`/p/${post.id}`} title={createdAt}>
              {nonbreaking(humanDate(createdAt))}

            {communities && <Communities communities={communities}/>}
            {post.public && {spacer}Public}

}

}
Header.contextTypes = {post: object, community: object}

const Communities = ({ communities }, { community }) => {
  if (community) communities = sortBy(communities, c => c.id !== community.id)
  const { length } = communities
  if (communities.length === 0) return null

  const communityLink = community => <A to={`/c/${community.slug}`}>{community.name}
  return
     in {communityLink(communities[0])}
    {length > 1 && <Dropdown className='post-communities-dropdown'
      toggleChildren={ + {length - 1} other{length > 2 ? 's' : ''}}>
      {communities.map(c =>
{communityLink(c)}
)}
    </Dropdown>}

}
Communities.contextTypes = {community: object}

const getTags = text =>
  cheerio.load(text)('.hashtag').toArray().map(tag =>
    tag.children[0].data.replace(/^#/, ''))

const extractTags = (shortDesc, fullDesc) => {
  const tags = getTags(fullDesc)
  return tags.length === 0 ? [] : difference(tags, getTags(shortDesc))
}

const HashtagLink = ({ tag, slug }, { dispatch }) => {
  const onMouseOver = handleMouseOver(dispatch)
  return
    {`#${tag}`}

}
HashtagLink.contextTypes = {dispatch: func}

export const Details = ({ expanded, onExpand }, { post, community, dispatch }) => {
  const { tag } = post
  const slug = get('slug', community)
  let description = presentDescription(post, community)
  let extractedTags = []
  const truncated = !expanded && textLength(description) > 200
  if (truncated) {
    const orig = description
    description = truncate(description, 200)
    extractedTags = extractTags(description, orig)
  }
  if (description) description = appendInP(description, ' ')

  return

    <ClickCatchingSpan dangerouslySetInnerHTML={{__html: description}}/>
    {truncated &&
      <wbr/>
       onExpand(null)} className='show-more'>Show more

    }
    {extractedTags.map(tag =>
      <wbr/>
      <HashtagLink tag={tag} slug={slug}/>

    )}
    {tag && <HashtagLink tag={tag} slug={slug}/>}

}
Details.contextTypes = {post: object, community: object, dispatch: func}

const Attachments = (props, { post }) => {
  const attachments = filter(post.media, m => m.type === 'gdoc')
  if (isEmpty(attachments)) return

  return

    {attachments.map((file, i) =>

        {truncate(file.name, 40).text}
      )}

}
Attachments.contextTypes = {post: object}

const WelcomePostHeader = ({ communities }, { post }) => {
  let person = post.relatedUsers[0]
  let community = communities[0]
  return

    <A to={`/u/${person.id}`}>{person.name} joined

    {community
      ?
          <A to={`/c/${community.slug}`}>{community.name}.

            Welcome them!

      :
          a community that is no longer active.
        }

}
WelcomePostHeader.contextTypes = {post: object}

export const Menu = (props, { dispatch, post, currentUser, community }) => {
  const canEdit = canEditPost(currentUser, post)
  const following = some(post.followers, same('id', currentUser))
  const pinned = isPinned(post, community)
  const edit = () => isMobile()
    ? dispatch(navigate(`/p/${post.id}/edit`))
    : dispatch(startPostEdit(post))
  const remove = () => window.confirm('Are you sure? This cannot be undone.') &&
    dispatch(removePost(post.id))
  const pin = () => dispatch(pinPost(get('slug', community), post.id))

  const toggleChildren = pinned
    ? Pinned
    :

  return <Dropdown className='post-menu' alignRight {...{toggleChildren}}>
    {canModerate(currentUser, community) &&

      {pinned ? 'Unpin post' : 'Pin post'}

}
    {canEdit &&
Edit
}
    {canEdit &&
Remove
}

       dispatch(followPost(post.id, currentUser))}>
        Turn {following ? 'off' : 'on'} notifications for this post

       window.alert('TODO')}>Report objectionable content

  </Dropdown>
}
Menu.contextTypes = {post: object, currentUser: object, dispatch: func, community: object}

export class CommentSection extends React.Component {
  static propTypes = {
    comments: array,
    onExpand: func,
    post: object,
    expanded: bool
  }

  static contextTypes = {
    community: object,
    currentUser: object,
    isProjectRequest: bool
  }

  render () {
    let { post, comments, onExpand, expanded } = this.props
    const truncate = !expanded
    const { currentUser, community, isProjectRequest } = this.context
    const placeholder = isProjectRequest ? 'How can you help?' : null

    if (!comments) comments = []
    comments = sortBy(comments, c => c.created_at)
    if (truncate) comments = comments.slice(-3)

    return

      {truncate && post.numComments > comments.length &&

         onExpand()}>Show all {post.numComments} comments

}
      {comments.map(c => <Comment comment={{...c, post_id: post.id}}
        truncate={truncate}
        expand={() => onExpand(c.id)}
        community={community}
        expanded={expanded}
        key={c.id}/>)}
      {currentUser && <CommentForm postId={post.id} {...{placeholder}}/>}

  }
}

export const VoteButton = (props, { post, currentUser, dispatch }) => {
  let vote = () => dispatch(voteOnPost(post, currentUser))
  let myVote = includes(map(post.voters, 'id'), (currentUser || {}).id)
  return
    {myVote ? <Icon name='Heart2' /> : <Icon name='Heart' />}
    {myVote ? 'Liked' : 'Like'}

}
VoteButton.contextTypes = {post: object, currentUser: object, dispatch: func}

export const Voters = (props, { post, currentUser }) => {
  const voters = post.voters || []

  let onlyAuthorIsVoting = voters.length === 1 && same('id', first(voters), post.user)
  return voters.length > 0 && !onlyAuthorIsVoting
    ? <LinkedPersonSentence people={voters} className='voters meta'>
        liked this.
      </LinkedPersonSentence>
    :
}
Voters.contextTypes = {post: object, currentUser: object}
<commit_msg>lazy load post images<commit_after>import React from 'react'
import {
  difference, filter, first, includes, isEmpty, map, some, sortBy
} from 'lodash'
import { find, get } from 'lodash/fp'
const { array, bool, func, object, string } = React.PropTypes
import cx from 'classnames'
import cheerio from 'cheerio'
import {
  humanDate, nonbreaking, present, textLength, truncate, appendInP
} from '../util/text'
import { sanitize } from 'hylo-utils/text'
import { linkifyHashtags } from '../util/linkify'
import { tagUrl } from '../routes'
import A from './A'
import Avatar from './Avatar'
import Dropdown from './Dropdown'
import { ClickCatchingSpan } from './ClickCatcher'
import { handleMouseOver } from './TagPopover'
import Comment from './Comment'
import CommentForm from './CommentForm'
import LazyLoader from './LazyLoader'
import Icon from './Icon'
import LinkedPersonSentence from './LinkedPersonSentence'
import LinkPreview from './LinkPreview'
import { connect } from 'react-redux'
import { compose } from 'redux'
import {
  followPost, navigate, removePost, startPostEdit, voteOnPost, pinPost
} from '../actions'
import { same } from '../models'
import { getComments, getCommunities, isPinned } from '../models/post'
import { getCurrentCommunity } from '../models/community'
import { canEditPost, canModerate } from '../models/currentUser'
import { isMobile } from '../client/util'
import decode from 'ent/decode'

const spacer =   •

export const presentDescription = (post, community, opts = {}) =>
  present(sanitize(post.description), {slug: get('slug', community), ...opts})

class Post extends React.Component {
  static propTypes = {
    post: object,
    communities: array,
    community: object,
    comments: array,
    dispatch: func,
    expanded: bool,
    onExpand: func,
    commentId: string
  }

  static childContextTypes = {post: object}

  getChildContext () {
    return {post: this.props.post}
  }

  render () {
    const { post, communities, comments, expanded, onExpand, community } = this.props
    const { tag, media, linkPreview } = post
    const image = find(m => m.type === 'image', media)
    const classes = cx('post', tag, {image, expanded})
    const title = linkifyHashtags(decode(sanitize(post.name || '')), get('slug', community))

    return

      <a name={`post-${post.id}`}>

      {image && <LazyLoader>

      </LazyLoader>}

      {linkPreview && <LinkPreview {...{linkPreview}}/>}

<VoteButton/><Voters/>

      <Attachments/>
      <CommentSection {...{post, expanded, onExpand, comments}}/>

  }
}

export default compose(
  connect((state, { post }) => {
    return {
      comments: getComments(post, state),
      communities: getCommunities(post, state),
      community: getCurrentCommunity(state)
    }
  })
)(Post)

export const UndecoratedPost = Post // for testing

export const Header = ({ communities }, { post, community }) => {
  const { tag } = post
  const person = tag === 'welcome' ? post.relatedUsers[0] : post.user
  const createdAt = new Date(post.created_at)

  return

    <Menu/>
    <Avatar person={person}/>
    {tag === 'welcome'
      ? <WelcomePostHeader communities={communities}/>
      :

          <A className='name' to={`/u/${person.id}`}>{person.name}

            <A to={`/p/${post.id}`} title={createdAt}>
              {nonbreaking(humanDate(createdAt))}

            {communities && <Communities communities={communities}/>}
            {post.public && {spacer}Public}

}

}
Header.contextTypes = {post: object, community: object}

const Communities = ({ communities }, { community }) => {
  if (community) communities = sortBy(communities, c => c.id !== community.id)
  const { length } = communities
  if (communities.length === 0) return null

  const communityLink = community => <A to={`/c/${community.slug}`}>{community.name}
  return
     in {communityLink(communities[0])}
    {length > 1 && <Dropdown className='post-communities-dropdown'
      toggleChildren={ + {length - 1} other{length > 2 ? 's' : ''}}>
      {communities.map(c =>
{communityLink(c)}
)}
    </Dropdown>}

}
Communities.contextTypes = {community: object}

const getTags = text =>
  cheerio.load(text)('.hashtag').toArray().map(tag =>
    tag.children[0].data.replace(/^#/, ''))

const extractTags = (shortDesc, fullDesc) => {
  const tags = getTags(fullDesc)
  return tags.length === 0 ? [] : difference(tags, getTags(shortDesc))
}

const HashtagLink = ({ tag, slug }, { dispatch }) => {
  const onMouseOver = handleMouseOver(dispatch)
  return
    {`#${tag}`}

}
HashtagLink.contextTypes = {dispatch: func}

export const Details = ({ expanded, onExpand }, { post, community, dispatch }) => {
  const { tag } = post
  const slug = get('slug', community)
  let description = presentDescription(post, community)
  let extractedTags = []
  const truncated = !expanded && textLength(description) > 200
  if (truncated) {
    const orig = description
    description = truncate(description, 200)
    extractedTags = extractTags(description, orig)
  }
  if (description) description = appendInP(description, ' ')

  return

    <ClickCatchingSpan dangerouslySetInnerHTML={{__html: description}}/>
    {truncated &&
      <wbr/>
       onExpand(null)} className='show-more'>Show more

    }
    {extractedTags.map(tag =>
      <wbr/>
      <HashtagLink tag={tag} slug={slug}/>

    )}
    {tag && <HashtagLink tag={tag} slug={slug}/>}

}
Details.contextTypes = {post: object, community: object, dispatch: func}

const Attachments = (props, { post }) => {
  const attachments = filter(post.media, m => m.type === 'gdoc')
  if (isEmpty(attachments)) return

  return

    {attachments.map((file, i) =>

        {truncate(file.name, 40).text}
      )}

}
Attachments.contextTypes = {post: object}

const WelcomePostHeader = ({ communities }, { post }) => {
  let person = post.relatedUsers[0]
  let community = communities[0]
  return

    <A to={`/u/${person.id}`}>{person.name} joined

    {community
      ?
          <A to={`/c/${community.slug}`}>{community.name}.

            Welcome them!

      :
          a community that is no longer active.
        }

}
WelcomePostHeader.contextTypes = {post: object}

export const Menu = (props, { dispatch, post, currentUser, community }) => {
  const canEdit = canEditPost(currentUser, post)
  const following = some(post.followers, same('id', currentUser))
  const pinned = isPinned(post, community)
  const edit = () => isMobile()
    ? dispatch(navigate(`/p/${post.id}/edit`))
    : dispatch(startPostEdit(post))
  const remove = () => window.confirm('Are you sure? This cannot be undone.') &&
    dispatch(removePost(post.id))
  const pin = () => dispatch(pinPost(get('slug', community), post.id))

  const toggleChildren = pinned
    ? Pinned
    :

  return <Dropdown className='post-menu' alignRight {...{toggleChildren}}>
    {canModerate(currentUser, community) &&

      {pinned ? 'Unpin post' : 'Pin post'}

}
    {canEdit &&
Edit
}
    {canEdit &&
Remove
}

       dispatch(followPost(post.id, currentUser))}>
        Turn {following ? 'off' : 'on'} notifications for this post

       window.alert('TODO')}>Report objectionable content

  </Dropdown>
}
Menu.contextTypes = {post: object, currentUser: object, dispatch: func, community: object}

export class CommentSection extends React.Component {
  static propTypes = {
    comments: array,
    onExpand: func,
    post: object,
    expanded: bool
  }

  static contextTypes = {
    community: object,
    currentUser: object,
    isProjectRequest: bool
  }

  render () {
    let { post, comments, onExpand, expanded } = this.props
    const truncate = !expanded
    const { currentUser, community, isProjectRequest } = this.context
    const placeholder = isProjectRequest ? 'How can you help?' : null

    if (!comments) comments = []
    comments = sortBy(comments, c => c.created_at)
    if (truncate) comments = comments.slice(-3)

    return

      {truncate && post.numComments > comments.length &&

         onExpand()}>Show all {post.numComments} comments

}
      {comments.map(c => <Comment comment={{...c, post_id: post.id}}
        truncate={truncate}
        expand={() => onExpand(c.id)}
        community={community}
        expanded={expanded}
        key={c.id}/>)}
      {currentUser && <CommentForm postId={post.id} {...{placeholder}}/>}

  }
}

export const VoteButton = (props, { post, currentUser, dispatch }) => {
  let vote = () => dispatch(voteOnPost(post, currentUser))
  let myVote = includes(map(post.voters, 'id'), (currentUser || {}).id)
  return
    {myVote ? <Icon name='Heart2' /> : <Icon name='Heart' />}
    {myVote ? 'Liked' : 'Like'}

}
VoteButton.contextTypes = {post: object, currentUser: object, dispatch: func}

export const Voters = (props, { post, currentUser }) => {
  const voters = post.voters || []

  let onlyAuthorIsVoting = voters.length === 1 && same('id', first(voters), post.user)
  return voters.length > 0 && !onlyAuthorIsVoting
    ? <LinkedPersonSentence people={voters} className='voters meta'>
        liked this.
      </LinkedPersonSentence>
    :
}
Voters.contextTypes = {post: object, currentUser: object}
<|endoftext|>


<commit_before>(function() {

  // variable declaration
  var streaming = false,
      video        = document.querySelector('#vdo'),
      canvas       = document.querySelector('#cnvs'),
      photo        = document.querySelector('#photo'),
      startbutton  = document.querySelector('button#snap'),
      burst_switch = document.querySelector('#burst-switch'),
      burst        = false,
      frames       = 12,
      frame_delay  = 250,
      snap_delay   = 500,
      width = 320,
      height = 0,
      count = -1,
      needsReset = false,
      isGiffing = false,

      // instantiates a new gif object
      gif = new GIF({
        workers: 2,
        quality: 10,
        width: 320,
        height: 240,
        workerScript: '/static/gif.js/dist/gif.worker.js'
      });

  // Access to browser camera
  navigator.getMedia = ( navigator.getUserMedia ||
                         navigator.webkitGetUserMedia ||
                         navigator.mozGetUserMedia ||
                         navigator.msGetUserMedia);

  navigator.getMedia(
    {
      video: true,
      audio: false
    },
    function(stream) {
      if (navigator.mozGetUserMedia) {
        video.mozSrcObject = stream;
      } else {
        var vendorURL = window.URL || window.webkitURL;
        video.src = vendorURL.createObjectURL(stream);
      }
      video.play();
    },
    function(err) {
      console.log("An error occured! " + err);
    }
  );

  video.addEventListener('canplay', function(ev){
    if (!streaming) {
      height = video.videoHeight / (video.videoWidth/width);
      video.setAttribute('width', width);
      video.setAttribute('height', height);
      canvas.setAttribute('width', width);
      canvas.setAttribute('height', height);
      streaming = true;
    }
  }, false);

  // Takes picture, draws image from canvas, sets img src attribute
  // adds img to gif frame
  // (could benefit from getting refactored into multiple single responsibility functions)
  function takepicture(){
    canvas.width = width;
    canvas.height = height;
    canvas.getContext('2d').drawImage(video, 0, 0, width, height);
    var data = canvas.toDataURL('image/png');
    // flash after every frame
    document.querySelector('.overlay-flash').classList.remove('is-hidden');
    setTimeout(function(){
      document.querySelector('.overlay-flash').classList.add('is-hidden');
    }, 75);

    count++;
    if ( count === frames ) {
      document.querySelector('#jif').src = '/static/gif.js/site/contents/images/loading.gif'
      gif.render();
    } else if ( burst ) {
      snapPhoto();
    }

    // Quick fix for weird count
    if (count <= 11) {
      document.querySelector('.photo' + count).setAttribute('src', data);
    }

    gif.addFrame(document.querySelector('.photo' + count), {delay: frame_delay});
    count = count % frames;
  }

  // Timer to call takepicture() when app is in burst mode
  function snapPhoto(){
    if (count < frames ){
      console.log(count)
      setTimeout(takepicture, snap_delay);
    }
  }

  // gif event listener to generate url blob
  gif.on('finished', function(blob) {
    document.querySelector('#jif').src = URL.createObjectURL(blob);
    count = -1;
    gif.frames = [];
    gif.running = false;
    uploadGIF(blob);
    needsReset = true;
    // show finished gif and tip
    document.querySelector('.finished-jif').classList.remove('is-hidden');
    document.querySelector('.tip').classList.remove('is-hidden');

    isGiffing = false;

    setTimeout(removeGif, 5000);

  });

  function removeGif(){
    document.querySelector('.finished-jif').classList.add('is-hidden');
  }

  // listens to checkbox for burst mode
  burst_switch.addEventListener('change', function(){
    burst = this.checked;
    isGiffing = false;
  });

  // event listener for the startbutton to take a picture
  startbutton.addEventListener('click', function(ev){
    if (!needsReset) {
      prepCapture();
      ev.preventDefault();
    } else {
      // this is a reset click
      needsReset = false;
      var i = 0;
      for (i = 0; i < frames; i++) {
        document.querySelector('.photo' + i).src = '/static/img/placeholder_frame.png';
      }

      // hide gif and tip
      document.querySelector('.finished-jif').classList.add('is-hidden');
      document.querySelector('.tip').classList.add('is-hidden');
    }
  }, false);

  document.addEventListener('keyup', function(ev){
    if ( ev.keyCode == 32 ){
      // spacebar to trigger capture
      prepCapture();
    } else if ( ev.keyCode == 90 ){
      // "z" key to check burst_switch
      burst_switch.checked = true;
      burst = true;
    } else if ( ev.keyCode == 88 ) {
      // "x" key to uncheck burst_switch
      burst_switch.checked = false;
      burst = false;
    }
  });

  function prepCapture(){
    if (!burst || !isGiffing) {
      isGiffing = true;
      burst == true ? countdown() : takepicture();
    }
  }

  var count_down = 5

  function countdown(){
    var msg = document.querySelector('.video-msg');
    msg.style.fontSize="26px";
    msg.style.padding="10px";
    msg.style.zIndex="999";
    msg.style.backgroundColor="black";

    if (count_down > 0 ) {
      msg.innerHTML = count_down;
      count_down--;
      setTimeout(countdown, 700)
    } else {
      count_down = 5;
      msg.style.fontSize="16px";
      msg.style.padding="75px";
      msg.style.zIndex="1";
      msg.style.backgroundColor="rgba(0,0,0,0.5)";
      snapPhoto();
    }
  }

  // creates the img frames
  function createFrameEls(){
    for (var i = 0; i < frames; i++){
      var img = document.createElement('img');
      img.setAttribute('src', '/static/img/placeholder_frame.png');
      img.setAttribute('class', 'photo' + i );
      document.querySelector('.snaps').appendChild(img);
    }
  }

  function applySettings(){
    var request = new XMLHttpRequest();
    request.open('GET', '/get-settings', true);
    request.send();
    request.onload = function() {
      if (request.status >= 200 && request.status < 400){
        // Success!
        data = JSON.parse(request.responseText);
        frames = data.frames;
        frame_delay = data.frame_delay;
        snap_delay = data.snap_delay;
      } else {
        console.log("hmm something isn't right")
      }
      createFrameEls();
    }
  }

  function uploadGIF(blob){

    var formData = new FormData();
    formData.append('giffile', blob, 'jif.gif');

    var request = new XMLHttpRequest();
    request.open('POST', '/giffed', true);
    request.send(formData);
    request.onload = function() {
      if (request.status === 200) {
        // done
      } else {
        // an error occured
      }
    }

  }

  applySettings();

})();<commit_msg>I wanna be done<commit_after>(function() {

  // variable declaration
  var streaming = false,
      video        = document.querySelector('#vdo'),
      canvas       = document.querySelector('#cnvs'),
      photo        = document.querySelector('#photo'),
      startbutton  = document.querySelector('button#snap'),
      burst_switch = document.querySelector('#burst-switch'),
      burst        = false,
      frames       = 12,
      frame_delay  = 250,
      snap_delay   = 500,
      width = 320,
      height = 0,
      count = -1,
      needsReset = false,
      isGiffing = false,

      // instantiates a new gif object
      gif = new GIF({
        workers: 2,
        quality: 10,
        width: 320,
        height: 240,
        workerScript: '/static/gif.js/dist/gif.worker.js'
      });

  // Access to browser camera
  navigator.getMedia = ( navigator.getUserMedia ||
                         navigator.webkitGetUserMedia ||
                         navigator.mozGetUserMedia ||
                         navigator.msGetUserMedia);

  navigator.getMedia(
    {
      video: true,
      audio: false
    },
    function(stream) {
      if (navigator.mozGetUserMedia) {
        video.mozSrcObject = stream;
      } else {
        var vendorURL = window.URL || window.webkitURL;
        video.src = vendorURL.createObjectURL(stream);
      }
      video.play();
    },
    function(err) {
      console.log("An error occured! " + err);
    }
  );

  video.addEventListener('canplay', function(ev){
    if (!streaming) {
      height = video.videoHeight / (video.videoWidth/width);
      video.setAttribute('width', width);
      video.setAttribute('height', height);
      canvas.setAttribute('width', width);
      canvas.setAttribute('height', height);
      streaming = true;
    }
  }, false);

  // Takes picture, draws image from canvas, sets img src attribute
  // adds img to gif frame
  // (could benefit from getting refactored into multiple single responsibility functions)
  function takepicture(){
    canvas.width = width;
    canvas.height = height;
    canvas.getContext('2d').drawImage(video, 0, 0, width, height);
    var data = canvas.toDataURL('image/png');
    // flash after every frame
    document.querySelector('.overlay-flash').classList.remove('is-hidden');
    setTimeout(function(){
      document.querySelector('.overlay-flash').classList.add('is-hidden');
    }, 75);

    count++;
    if ( count === frames ) {
      document.querySelector('#jif').src = '/static/gif.js/site/contents/images/loading.gif'
      gif.render();
    } else if ( burst ) {
      snapPhoto();
    }

    // Quick fix for weird count
    if (count <= frames) {
      document.querySelector('.photo' + count).setAttribute('src', data);
    }

    gif.addFrame(document.querySelector('.photo' + count), {delay: frame_delay});
    count = count % frames;
  }

  // Timer to call takepicture() when app is in burst mode
  function snapPhoto(){
    if (count < frames ){
      console.log(count)
      setTimeout(takepicture, snap_delay);
    }
  }

  // gif event listener to generate url blob
  gif.on('finished', function(blob) {
    document.querySelector('#jif').src = URL.createObjectURL(blob);
    count = -1;
    gif.frames = [];
    gif.running = false;
    uploadGIF(blob);
    needsReset = true;
    // show finished gif and tip
    document.querySelector('.finished-jif').classList.remove('is-hidden');
    document.querySelector('.tip').classList.remove('is-hidden');

    isGiffing = false;

    setTimeout(removeGif, 5000);

  });

  function removeGif(){
    document.querySelector('.finished-jif').classList.add('is-hidden');
  }

  // listens to checkbox for burst mode
  burst_switch.addEventListener('change', function(){
    burst = this.checked;
    isGiffing = false;
  });

  // event listener for the startbutton to take a picture
  startbutton.addEventListener('click', function(ev){
    if (!needsReset) {
      prepCapture();
      ev.preventDefault();
    } else {
      // this is a reset click
      needsReset = false;
      var i = 0;
      for (i = 0; i < frames; i++) {
        document.querySelector('.photo' + i).src = '/static/img/placeholder_frame.png';
      }

      // hide gif and tip
      document.querySelector('.finished-jif').classList.add('is-hidden');
      document.querySelector('.tip').classList.add('is-hidden');
    }
  }, false);

  document.addEventListener('keyup', function(ev){
    if ( ev.keyCode == 32 ){
      // spacebar to trigger capture
      prepCapture();
    } else if ( ev.keyCode == 90 ){
      // "z" key to check burst_switch
      burst_switch.checked = true;
      burst = true;
    } else if ( ev.keyCode == 88 ) {
      // "x" key to uncheck burst_switch
      burst_switch.checked = false;
      burst = false;
    }
  });

  function prepCapture(){
    if (!burst || !isGiffing) {
      isGiffing = true;
      burst == true ? countdown() : takepicture();
    }
  }

  var count_down = 5

  function countdown(){
    var msg = document.querySelector('.video-msg');
    msg.style.fontSize="26px";
    msg.style.padding="10px";
    msg.style.zIndex="999";
    msg.style.backgroundColor="black";

    if (count_down > 0 ) {
      msg.innerHTML = count_down;
      count_down--;
      setTimeout(countdown, 700)
    } else {
      count_down = 5;
      msg.style.fontSize="16px";
      msg.style.padding="75px";
      msg.style.zIndex="1";
      msg.style.backgroundColor="rgba(0,0,0,0.5)";
      snapPhoto();
    }
  }

  // creates the img frames
  function createFrameEls(){
    for (var i = 0; i < frames; i++){
      var img = document.createElement('img');
      img.setAttribute('src', '/static/img/placeholder_frame.png');
      img.setAttribute('class', 'photo' + i );
      document.querySelector('.snaps').appendChild(img);
    }
  }

  function applySettings(){
    var request = new XMLHttpRequest();
    request.open('GET', '/get-settings', true);
    request.send();
    request.onload = function() {
      if (request.status >= 200 && request.status < 400){
        // Success!
        data = JSON.parse(request.responseText);
        frames = data.frames;
        frame_delay = data.frame_delay;
        snap_delay = data.snap_delay;
      } else {
        console.log("hmm something isn't right")
      }
      createFrameEls();
    }
  }

  function uploadGIF(blob){

    var formData = new FormData();
    formData.append('giffile', blob, 'jif.gif');

    var request = new XMLHttpRequest();
    request.open('POST', '/giffed', true);
    request.send(formData);
    request.onload = function() {
      if (request.status === 200) {
        // done
      } else {
        // an error occured
      }
    }

  }

  applySettings();

})();<|endoftext|>


<commit_before><commit_msg>removed evil char<commit_after><|endoftext|>


<commit_before>#!/usr/bin/env node

'use strict'

require('./splash')()

const path = require('path')
const flags = require('commander')

const markserv = require(path.join(__dirname, 'server'))
const pkg = require(path.join('..', 'package.json'))

const cwd = process.cwd()

flags.dir = cwd

flags.version(pkg.version)
	.usage('<file/dir>')
	.option('-p, --port [type]', 'HTTP port [port]', 8642)
	.option('-l, --livereloadport [type]', 'LiveReload port [livereloadport]', 35729)
	.option('-i, --silent [type]', 'Silent (no logs to CLI)', false)
	.option('-a, --address [type]', 'Serve on ip/address [address]', 'localhost')
	.option('-v, --verbose', 'verbose output')
	.action(serverPath => {
		flags.$pathProvided = true
		if (flags.dir[0] !== '/') {
			flags.dir = path.normalize(path.join(cwd, serverPath))
		}
	}).parse(process.argv)

markserv.init(flags)
<commit_msg>Allow serving from absolute paths.<commit_after>#!/usr/bin/env node

'use strict'

require('./splash')()

const path = require('path')
const flags = require('commander')

const markserv = require(path.join(__dirname, 'server'))
const pkg = require(path.join('..', 'package.json'))

const cwd = process.cwd()

flags.dir = cwd

flags.version(pkg.version)
	.usage('<file/dir>')
	.option('-p, --port [type]', 'HTTP port [port]', 8642)
	.option('-l, --livereloadport [type]', 'LiveReload port [livereloadport]', 35729)
	.option('-i, --silent [type]', 'Silent (no logs to CLI)', false)
	.option('-a, --address [type]', 'Serve on ip/address [address]', 'localhost')
	.option('-v, --verbose', 'verbose output')
	.action(serverPath => {
		flags.$pathProvided = true
		if (flags.dir[0] === '/') {
			flags.dir = serverPath
		} else {
			flags.dir = path.normalize(path.join(cwd, serverPath))
		}
	}).parse(process.argv)

markserv.init(flags)
<|endoftext|>


<commit_before>import { exec } from 'mz/child_process';
import { exists, readdir, readFile, stat } from 'mz/fs';
import readline from 'mz/readline';

import getCoffeeFilesFromPathFile from './getCoffeeFilesFromPathFile';
import getCoffeeFilesUnderPath from './getCoffeeFilesUnderPath';
import CLIError from '../util/CLIError';
import execLive from '../util/execLive';

/**
 * Resolve the configuration from a number of sources: any number of config
 * files and CLI options. Then "canonicalize" the config, e.g. by resolving the
 * list of files to process and inferring any paths if necessary.
 */
export default async function resolveConfig(commander) {
  let config = {};

  let currentDirFiles = await readdir('.');
  currentDirFiles.sort();
  for (let filename of currentDirFiles) {
    if (filename.startsWith('bulk-decaffeinate')
        && filename.endsWith('.json')
        && !(await stat(filename)).isDirectory()) {
      let newConfig = JSON.parse(await readFile(filename));
      config = Object.assign(config, newConfig);
    }
  }
  config = Object.assign(config, getCLIParamsConfig(commander));
  return {
    filesToProcess: await resolveFilesToProcess(config),
    decaffeinatePath: await resolveDecaffeinatePath(config),
    eslintPath: await resolveEslintPath(config),
  };
}

/**
 * Fill in a configuration from the CLI arguments.
 */
function getCLIParamsConfig(commander) {
  let {pathFile, dir, decaffeinatePath, eslintPath} = commander;
  let config = {};
  if (dir) {
    config.searchDirectory = dir;
  }
  if (pathFile) {
    config.pathFile = pathFile;
  }
  if (decaffeinatePath) {
    config.decaffeinatePath = decaffeinatePath;
  }
  if (eslintPath) {
    config.eslintPath = eslintPath;
  }
  return config;
}

async function resolveFilesToProcess(config) {
  let {filesToProcess, pathFile, searchDirectory} = config;
  if (filesToProcess) {
    return filesToProcess;
  }
  if (pathFile) {
    return await getCoffeeFilesFromPathFile(pathFile);
  }
  if (searchDirectory) {
    return await getCoffeeFilesUnderPath(searchDirectory);
  }
  return await getCoffeeFilesUnderPath('.');
}

/**
 * Determine the decaffeinate path (the shell command) to use for running
 * decaffeinate.
 */
async function resolveDecaffeinatePath(config) {
  if (config.decaffeinatePath) {
    return config.decaffeinatePath;
  }
  return await resolveBinary('decaffeinate');
}

/**
 * Determine the decaffeinate path (the shell command) to use for running
 * decaffeinate.
 */
async function resolveEslintPath(config) {
  if (config.eslintPath) {
    return config.eslintPath;
  }
  return await resolveBinary('eslint');
}

async function resolveBinary(binaryName) {
  let nodeModulesPath = `./node_modules/.bin/${binaryName}`;
  if (await exists(nodeModulesPath)) {
    return nodeModulesPath;
  } else {
    try {
      await exec(`which ${binaryName}`);
      return binaryName;
    } catch (e) {
      console.log(`${binaryName} binary not found on the PATH or in node_modules.`);
      let rl = readline.createInterface(process.stdin, process.stdout);
      let answer = await rl.question(`Run "npm install -g ${binaryName}"? [Y/n] `);
      rl.close();
      if (answer.toLowerCase().startsWith('n')) {
        throw new CLIError(`${binaryName} must be installed.`);
      }
      console.log(`Installing ${binaryName} globally...`);
      await execLive(`npm install -g ${binaryName}`);
      console.log(`Successfully installed ${binaryName}\n`);
      return binaryName;
    }
  }
}
<commit_msg>fix: improve error handling when a config file is invalid json (#11)<commit_after>import { exec } from 'mz/child_process';
import { exists, readdir, readFile, stat } from 'mz/fs';
import readline from 'mz/readline';

import getCoffeeFilesFromPathFile from './getCoffeeFilesFromPathFile';
import getCoffeeFilesUnderPath from './getCoffeeFilesUnderPath';
import CLIError from '../util/CLIError';
import execLive from '../util/execLive';

/**
 * Resolve the configuration from a number of sources: any number of config
 * files and CLI options. Then "canonicalize" the config, e.g. by resolving the
 * list of files to process and inferring any paths if necessary.
 */
export default async function resolveConfig(commander) {
  let config = {};

  let currentDirFiles = await readdir('.');
  currentDirFiles.sort();
  for (let filename of currentDirFiles) {
    if (filename.startsWith('bulk-decaffeinate')
        && filename.endsWith('.json')
        && !(await stat(filename)).isDirectory()) {
      try {
        let newConfig = JSON.parse(await readFile(filename));
        config = Object.assign(config, newConfig);
      } catch (e) {
        throw new CLIError(
          `Error reading file ${filename}. Make sure it is a valid JSON file.`);
      }
    }
  }
  config = Object.assign(config, getCLIParamsConfig(commander));
  return {
    filesToProcess: await resolveFilesToProcess(config),
    decaffeinatePath: await resolveDecaffeinatePath(config),
    eslintPath: await resolveEslintPath(config),
  };
}

/**
 * Fill in a configuration from the CLI arguments.
 */
function getCLIParamsConfig(commander) {
  let {pathFile, dir, decaffeinatePath, eslintPath} = commander;
  let config = {};
  if (dir) {
    config.searchDirectory = dir;
  }
  if (pathFile) {
    config.pathFile = pathFile;
  }
  if (decaffeinatePath) {
    config.decaffeinatePath = decaffeinatePath;
  }
  if (eslintPath) {
    config.eslintPath = eslintPath;
  }
  return config;
}

async function resolveFilesToProcess(config) {
  let {filesToProcess, pathFile, searchDirectory} = config;
  if (filesToProcess) {
    return filesToProcess;
  }
  if (pathFile) {
    return await getCoffeeFilesFromPathFile(pathFile);
  }
  if (searchDirectory) {
    return await getCoffeeFilesUnderPath(searchDirectory);
  }
  return await getCoffeeFilesUnderPath('.');
}

/**
 * Determine the decaffeinate path (the shell command) to use for running
 * decaffeinate.
 */
async function resolveDecaffeinatePath(config) {
  if (config.decaffeinatePath) {
    return config.decaffeinatePath;
  }
  return await resolveBinary('decaffeinate');
}

/**
 * Determine the decaffeinate path (the shell command) to use for running
 * decaffeinate.
 */
async function resolveEslintPath(config) {
  if (config.eslintPath) {
    return config.eslintPath;
  }
  return await resolveBinary('eslint');
}

async function resolveBinary(binaryName) {
  let nodeModulesPath = `./node_modules/.bin/${binaryName}`;
  if (await exists(nodeModulesPath)) {
    return nodeModulesPath;
  } else {
    try {
      await exec(`which ${binaryName}`);
      return binaryName;
    } catch (e) {
      console.log(`${binaryName} binary not found on the PATH or in node_modules.`);
      let rl = readline.createInterface(process.stdin, process.stdout);
      let answer = await rl.question(`Run "npm install -g ${binaryName}"? [Y/n] `);
      rl.close();
      if (answer.toLowerCase().startsWith('n')) {
        throw new CLIError(`${binaryName} must be installed.`);
      }
      console.log(`Installing ${binaryName} globally...`);
      await execLive(`npm install -g ${binaryName}`);
      console.log(`Successfully installed ${binaryName}\n`);
      return binaryName;
    }
  }
}
<|endoftext|>


<commit_before>// select(options)
// select(value[, options])
//
// When the model is part of a collection and you want to select a single
// or multiple items from a collection. If a model is selected
// `model.selected` will be set `true`, otherwise it will be `false`.
//
// If you pass `true` or `false` as the first paramater to `select` it will
// select the model if true, or unselect if it is false.
//
// By default any other models in the collection with be unselected. To
// prevent other models in the collection from being unselected you can
// pass `{multiple: true}` as an option.
//
// The `selected` and `unselected` events are fired when appropriate.
Viking.Model.prototype.select = function(value, options) {

    // Handle both `value[, options]` and `options` -style arguments.
    if (value === undefined || typeof value === 'object') {
      options = value;
      value = true;
    }

    if (value === true) {
        if (this.collection) {
            this.collection.select(this, options);
        } else {
            this.selected = true;
        }
    } else {
        if (this.selected) {
            this.selected = false;
            this.trigger('unselected', this);
        }
    }
};<commit_msg>select method minor fix (#33)<commit_after>// select(options)
// select(value[, options])
//
// When the model is part of a collection and you want to select a single
// or multiple items from a collection. If a model is selected
// `model.selected` will be set `true`, otherwise it will be `false`.
//
// If you pass `true` or `false` as the first paramater to `select` it will
// select the model if true, or unselect if it is false.
//
// By default any other models in the collection with be unselected. To
// prevent other models in the collection from being unselected you can
// pass `{multiple: true}` as an option.
//
// The `selected` and `unselected` events are fired when appropriate.
Viking.Model.prototype.select = function(value, options) {

    // Handle both `value[, options]` and `options` -style arguments.
    if (value === undefined || typeof value === 'object') {
      options = value;
      value = true;
    }

    if (value === true) {
        if (this.collection) {
            this.collection.select(this, options);
        } else {
            this.selected = true;
        }
    } else {
        if (this.selected) {
            this.selected = false;
            this.trigger('unselected', this);
            if (this.collection) this.collection.trigger('unselected', this);
        }
    }
};<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';

import Field from './Field';

class CreateRuleForm extends PureComponent {
  constructor(props) {
    super();
    this.state = {
      id: '',
      title: '',
      body: '',
      idIfTrue: '',
      idIfFalse: ''
    };
    this.onCreateRule = this.onCreateRule.bind(this);
    this.onFieldChange = this.onFieldChange.bind(this);
  }

  onCreateRule(event) {
    event.preventDefault();
    this.form.reset();
    this.props.onCreateRule(this.state);
  }

  onFieldChange(event) {
    this.setState({ [event.target.name]: event.target.value });
  }

  render() {
    return (

1. Create flow

        <form action="#" onSubmit={this.onCreateRule} ref={(form) => { this.form = form; }}>
          <fieldset>
            <legend>New Rule</legend>

            <Field name="title" placeholder="A nice title" onChange={this.onFieldChange} />
            <Field name="id" placeholder="ex. 1" onChange={this.onFieldChange} />
            <Field name="body" placeholder="ex. 1">
              <textarea name="body" placeholder="// a nice an clean javascript function here" defaultValue="" onChange={this.onFieldChange} />
            </Field>
            <Field name="idIfTrue" label="Id if it passes" placeholder="ex. 2" onChange={this.onFieldChange} />
            <Field name="idIfFalse" label="Id if it fails" placeholder="ex. 3" onChange={this.onFieldChange} />

            <input type="submit" name="create" value="Add new rule" />
          </fieldset>
        </form>

    );
  }
}

export default CreateRuleForm;
<commit_msg>fix code style on CreateRuleForm<commit_after>import React, { PureComponent } from 'react';

import Field from './Field';

class CreateRuleForm extends PureComponent {
  constructor(props) {
    super();
    this.state = {
      id: '',
      title: '',
      body: '',
      idIfTrue: '',
      idIfFalse: ''
    };
    this.onCreateRule = this.onCreateRule.bind(this);
    this.onFieldChange = this.onFieldChange.bind(this);
  }

  onCreateRule(event) {
    event.preventDefault();
    this.form.reset();
    this.props.onCreateRule(this.state);
  }

  onFieldChange(event) {
    this.setState({ [event.target.name]: event.target.value });
  }

  render() {
    return (

1. Create flow

        <form action="#" onSubmit={this.onCreateRule} ref={(form) => { this.form = form; }}>
          <fieldset>
            <legend>New Rule</legend>

            <Field name="title" placeholder="A nice title" onChange={this.onFieldChange} />
            <Field name="id" placeholder="ex. 1" onChange={this.onFieldChange} />
            <Field name="body">
              <textarea
                name="body"
                placeholder="// a nice an clean javascript function here"
                defaultValue=""
                onChange={this.onFieldChange}
              />
            </Field>
            <Field name="idIfTrue" label="Id if it passes" placeholder="ex. 2" onChange={this.onFieldChange} />
            <Field name="idIfFalse" label="Id if it fails" placeholder="ex. 3" onChange={this.onFieldChange} />

            <input type="submit" name="create" value="Add new rule" />
          </fieldset>
        </form>

    );
  }
}

export default CreateRuleForm;
<|endoftext|>