<commit_before>function Words(arr) {
  this.arr = arr;
}

function words () {
  return new Words(Array.prototype.slice.call(arguments));
}

Words.prototype.scrunched = function() {return false;};

// ____ ScrunchedWords are Words that don't insert spaces
//      between arguments as agressively as regular words

function ScrunchedWords(arr) {
  Words.call(this, arr);
}

ScrunchedWords.prototype = Object.create(Words.prototype);
ScrunchedWords.prototype.constructor = ScrunchedWords;

ScrunchedWords.prototype.scrunched = function() {return true;};

// ____ ScrunchedWords end

var wants_no_space_before = [false, null, ',', '.', ';'];

var FLATTEN_FORMAT_MARKDOWN = 1001;
var FLATTEN_FORMAT_HTML = 1002;
var FLATTEN_FORMAT_UNSAFE_TEXT = 1003;
var FLATTEN_FORMAT_SAFE_TEXT = 1004;

// returns *sanitized* html
Words.prototype.toHtml = function() {
  return this.flatten(FLATTEN_FORMAT_HTML);
};

// returns *unsanitized* string with Tags (see below) lobotimized into text.
// Maydo whitespace dialation ala html, but at least it preserves newlines.
Words.prototype.toUnsafeText = function() {
  return this.flatten(FLATTEN_FORMAT_UNSAFE_TEXT);
};

// returns *sanitized* e.g. pb&j → <b>pb&a<b>
// but is different from toHtml because any Tags (see below) are
// flattened with no bracketing tags, they're all body.
Words.prototype.toSaveText = function() {
  return this.flatten(FLATTEN_FORMAT_SAFE_TEXT);
};

// returns *unsanitized* string with Tags (see below) rendered as html (not Markdown)
// This preserves newlines, unlike toHtml.
// It's assumed to be headed for a markdown parser that takes care of that.
Words.prototype.toMarkdown = function() {
  return this.flatten(FLATTEN_FORMAT_MARKDOWN);
};

Words.prototype.flatten = function(format) {
  var arr = this.arr;
  var acc = [];
  var space_before = false;
  for (var i=0; i<arr.length; i++) {
    var wants_space_before = (!this.scrunched()) && (-1 === wants_no_space_before.indexOf(peek(arr[i])));
    if (wants_space_before) {
      acc.push(' ');
    }
    acc.push(flattenWordNode(arr[i], format));
  }
  return trimButLeaveNewlines(acc.join(''));
}

Words.prototype.peek = function() {
  for (var i = 0; i < this.arr.length; i++) {
    var p = peek(this.arr[i]);
    if (p) {
      return p;
    }
  }
  return null;
}

function Tag(tag, attrs, body) {
  this.tag = tag;
  this.attrs = attrs;
  this.body = body;
}

function tag(tag, body) {
  return new Tag(tag, {}, body);
}

// function tag_attrs(tag, attrs, body) {
//   return new Tag(tag, attrs, body);
// }

Tag.prototype.flatten = function (format) {
  if (format === FLATTEN_FORMAT_UNSAFE_TEXT || format === FLATTEN_FORMAT_SAFE_TEXT) {
    return flattenWordNode(this.body, format);
  } else if (format === FLATTEN_FORMAT_MARKDOWN || format === FLATTEN_FORMAT_HTML) {
    // TODO: simply sanitize and print attrs
    return '<' + this.tag + '>' + flattenWordNode(this.body, format) + '</' + this.tag + '>';
  } else {
    throw_up('unexpected word flatten format :'+format.toString());
  }
}

Tag.prototype.peek = function () {
  return peek(this.body);
}

var sanitizationMap = {
  '<': '<',
  '>': '>',
  '&': '&',
  '&': '&'
};

function flattenWordNode(s, format) {
  if (s.flatten) {
    return s.flatten(format);
  } else if ('string' === typeof s) {
    if (format === FLATTEN_FORMAT_HTML || format === FLATTEN_FORMAT_SAFE_TEXT) {
      var replacer = function(match) {
        return sanitizationMap[match] || throw_up('Unexpected match during flatten sanitize');
      };
      return s.replace(/&|&|<|>/g, replacer);
    } else if (format === FLATTEN_FORMAT_MARKDOWN || format === FLATTEN_FORMAT_UNSAFE_TEXT) {
      return s;
    } else {
      throw_up('unexpected flatten format: '+format.toString());
    }
  } else if (comma === s) {
    return ',';
  } else if (false === s) {
    return '';
  } else {
    return ''+s;
  }
}

// returns first non-whitespace character
function peek(thing) {
  var m;
  if (thing.peek) {
    return thing.peek();
  } else if ((typeof thing === 'string') && (m = thing.match(/[\S\n]/))) {
    return m[0];
  } else if (thing == comma) {
    return ',';
  } else if (thing == false) {
    return null;
  } else {
    return null;
  }
}

function trimButLeaveNewlines(s) {
  var start;
  var end;
  for (start=0; start<s.length; start++) {
    if (s[start].match(/[\S\n]/)) {
      break;
    }
  }
  for (end=s.length-1; end>=start; end--) {
    if (s[end].match(/[\S\n]/)) {
      break;
    }
  }
  return s.slice(start, end+1);
}

////////////////////////////////////////////////////////////////

var comma = ['comma'];

////////////////////////////////////////////////////////////////

// clamp words in <s> or  - string is already in-dialect at the point when this proceess it
function lingoLineWords(string, dialect) {
  // lookbehind doesn't work in all versions of js, so we've got to use capture groups for word boundaries, sigh
  var underlines_and_strikes = underlinesAndStrikes(dialect);
  var all_lingo_lines = underlines_and_strikes.underlines.concat(underlines_and_strikes.strikes).sort(longestFirstSortFn);
  var regex = new RegExp('(\\s|'+PUNCTUATION_CHARSET_STRING+'|^)(' + all_lingo_lines.map(regExpEscape).join('|') + ')(\\s|'+PUNCTUATION_CHARSET_STRING+'|$)','ig');
  var buffer = [];
  var last_match_ended_at;
  while (true) {
    last_match_ended_at = regex.lastIndex;
    var match_info = regex.exec(string);
    if (!match_info) break;
    buffer.push(string.slice(last_match_ended_at, match_info.index));
    var is_strike = underlines_and_strikes.strikes.indexOf(match_info[2].toLowerCase()) >= 0;
    buffer.push(match_info[1]); // its whitespace, but it might be a newline
    buffer.push(tag(is_strike ? 's' : 'u', match_info[2]));
    regex.lastIndex = regex.lastIndex - match_info[3].length; // put back trailing whitespace
  }
  buffer.push(string.slice(last_match_ended_at));
  return new ScrunchedWords(buffer);
}

var bogusTerms = ['men', 'women', 'man', 'woman', 'gentlemen', 'gentleman',
                  'gents', 'gent', 'ladies', 'lady',
                  'leads', 'lead', 'follows', 'follow',
                  'larks', 'lark', 'ravens', 'raven',
                  'gypsy', 'yearn', "rory o'moore", 'rollaway', 'roll-away',
                  'nn', 'n', 'p', 'l', 'g', 'm', 'w', 'n.', 'p.', 'l.', 'g.', 'm.', 'w.', 'g1', 'g2', 'l1', 'l2'];

var terms_for_uands;
// NB on return value: it is freshly allocated each time
function underlinesAndStrikes(dialect) {
  if (!terms_for_uands) {terms_for_uands = moves().concat(dancers());}
  var underlines = terms_for_uands.map(function(term) {
    var substitution = dialect.dancers[term] || dialect.moves[term];
    return (substitution ? stripPercentS(substitution) : term).toLowerCase();
  });
  var strikes = terms_for_uands.concat(bogusTerms).filter(function(s) {return -1 === underlines.indexOf(s.toLowerCase());});
  return {underlines: underlines, strikes: strikes};
}

function stripPercentS(str) {
  return str.replace(/%S/g, '').trim();
}
<commit_msg>add the word 'sex' to lingo line bogusTerms - resolves #481<commit_after>function Words(arr) {
  this.arr = arr;
}

function words () {
  return new Words(Array.prototype.slice.call(arguments));
}

Words.prototype.scrunched = function() {return false;};

// ____ ScrunchedWords are Words that don't insert spaces
//      between arguments as agressively as regular words

function ScrunchedWords(arr) {
  Words.call(this, arr);
}

ScrunchedWords.prototype = Object.create(Words.prototype);
ScrunchedWords.prototype.constructor = ScrunchedWords;

ScrunchedWords.prototype.scrunched = function() {return true;};

// ____ ScrunchedWords end

var wants_no_space_before = [false, null, ',', '.', ';'];

var FLATTEN_FORMAT_MARKDOWN = 1001;
var FLATTEN_FORMAT_HTML = 1002;
var FLATTEN_FORMAT_UNSAFE_TEXT = 1003;
var FLATTEN_FORMAT_SAFE_TEXT = 1004;

// returns *sanitized* html
Words.prototype.toHtml = function() {
  return this.flatten(FLATTEN_FORMAT_HTML);
};

// returns *unsanitized* string with Tags (see below) lobotimized into text.
// Maydo whitespace dialation ala html, but at least it preserves newlines.
Words.prototype.toUnsafeText = function() {
  return this.flatten(FLATTEN_FORMAT_UNSAFE_TEXT);
};

// returns *sanitized* e.g. pb&j → <b>pb&a<b>
// but is different from toHtml because any Tags (see below) are
// flattened with no bracketing tags, they're all body.
Words.prototype.toSaveText = function() {
  return this.flatten(FLATTEN_FORMAT_SAFE_TEXT);
};

// returns *unsanitized* string with Tags (see below) rendered as html (not Markdown)
// This preserves newlines, unlike toHtml.
// It's assumed to be headed for a markdown parser that takes care of that.
Words.prototype.toMarkdown = function() {
  return this.flatten(FLATTEN_FORMAT_MARKDOWN);
};

Words.prototype.flatten = function(format) {
  var arr = this.arr;
  var acc = [];
  var space_before = false;
  for (var i=0; i<arr.length; i++) {
    var wants_space_before = (!this.scrunched()) && (-1 === wants_no_space_before.indexOf(peek(arr[i])));
    if (wants_space_before) {
      acc.push(' ');
    }
    acc.push(flattenWordNode(arr[i], format));
  }
  return trimButLeaveNewlines(acc.join(''));
}

Words.prototype.peek = function() {
  for (var i = 0; i < this.arr.length; i++) {
    var p = peek(this.arr[i]);
    if (p) {
      return p;
    }
  }
  return null;
}

function Tag(tag, attrs, body) {
  this.tag = tag;
  this.attrs = attrs;
  this.body = body;
}

function tag(tag, body) {
  return new Tag(tag, {}, body);
}

// function tag_attrs(tag, attrs, body) {
//   return new Tag(tag, attrs, body);
// }

Tag.prototype.flatten = function (format) {
  if (format === FLATTEN_FORMAT_UNSAFE_TEXT || format === FLATTEN_FORMAT_SAFE_TEXT) {
    return flattenWordNode(this.body, format);
  } else if (format === FLATTEN_FORMAT_MARKDOWN || format === FLATTEN_FORMAT_HTML) {
    // TODO: simply sanitize and print attrs
    return '<' + this.tag + '>' + flattenWordNode(this.body, format) + '</' + this.tag + '>';
  } else {
    throw_up('unexpected word flatten format :'+format.toString());
  }
}

Tag.prototype.peek = function () {
  return peek(this.body);
}

var sanitizationMap = {
  '<': '<',
  '>': '>',
  '&': '&',
  '&': '&'
};

function flattenWordNode(s, format) {
  if (s.flatten) {
    return s.flatten(format);
  } else if ('string' === typeof s) {
    if (format === FLATTEN_FORMAT_HTML || format === FLATTEN_FORMAT_SAFE_TEXT) {
      var replacer = function(match) {
        return sanitizationMap[match] || throw_up('Unexpected match during flatten sanitize');
      };
      return s.replace(/&|&|<|>/g, replacer);
    } else if (format === FLATTEN_FORMAT_MARKDOWN || format === FLATTEN_FORMAT_UNSAFE_TEXT) {
      return s;
    } else {
      throw_up('unexpected flatten format: '+format.toString());
    }
  } else if (comma === s) {
    return ',';
  } else if (false === s) {
    return '';
  } else {
    return ''+s;
  }
}

// returns first non-whitespace character
function peek(thing) {
  var m;
  if (thing.peek) {
    return thing.peek();
  } else if ((typeof thing === 'string') && (m = thing.match(/[\S\n]/))) {
    return m[0];
  } else if (thing == comma) {
    return ',';
  } else if (thing == false) {
    return null;
  } else {
    return null;
  }
}

function trimButLeaveNewlines(s) {
  var start;
  var end;
  for (start=0; start<s.length; start++) {
    if (s[start].match(/[\S\n]/)) {
      break;
    }
  }
  for (end=s.length-1; end>=start; end--) {
    if (s[end].match(/[\S\n]/)) {
      break;
    }
  }
  return s.slice(start, end+1);
}

////////////////////////////////////////////////////////////////

var comma = ['comma'];

////////////////////////////////////////////////////////////////

// clamp words in <s> or  - string is already in-dialect at the point when this proceess it
function lingoLineWords(string, dialect) {
  // lookbehind doesn't work in all versions of js, so we've got to use capture groups for word boundaries, sigh
  var underlines_and_strikes = underlinesAndStrikes(dialect);
  var all_lingo_lines = underlines_and_strikes.underlines.concat(underlines_and_strikes.strikes).sort(longestFirstSortFn);
  var regex = new RegExp('(\\s|'+PUNCTUATION_CHARSET_STRING+'|^)(' + all_lingo_lines.map(regExpEscape).join('|') + ')(\\s|'+PUNCTUATION_CHARSET_STRING+'|$)','ig');
  var buffer = [];
  var last_match_ended_at;
  while (true) {
    last_match_ended_at = regex.lastIndex;
    var match_info = regex.exec(string);
    if (!match_info) break;
    buffer.push(string.slice(last_match_ended_at, match_info.index));
    var is_strike = underlines_and_strikes.strikes.indexOf(match_info[2].toLowerCase()) >= 0;
    buffer.push(match_info[1]); // its whitespace, but it might be a newline
    buffer.push(tag(is_strike ? 's' : 'u', match_info[2]));
    regex.lastIndex = regex.lastIndex - match_info[3].length; // put back trailing whitespace
  }
  buffer.push(string.slice(last_match_ended_at));
  return new ScrunchedWords(buffer);
}

var bogusTerms = ['men', 'women', 'man', 'woman', 'gentlemen', 'gentleman',
                  'gents', 'gent', 'ladies', 'lady',
                  'leads', 'lead', 'follows', 'follow',
                  'larks', 'lark', 'ravens', 'raven',
                  'sex',
                  'gypsy', 'yearn', "rory o'moore", 'rollaway', 'roll-away',
                  'nn', 'n', 'p', 'l', 'g', 'm', 'w', 'n.', 'p.', 'l.', 'g.', 'm.', 'w.', 'g1', 'g2', 'l1', 'l2'];

var terms_for_uands;
// NB on return value: it is freshly allocated each time
function underlinesAndStrikes(dialect) {
  if (!terms_for_uands) {terms_for_uands = moves().concat(dancers());}
  var underlines = terms_for_uands.map(function(term) {
    var substitution = dialect.dancers[term] || dialect.moves[term];
    return (substitution ? stripPercentS(substitution) : term).toLowerCase();
  });
  var strikes = terms_for_uands.concat(bogusTerms).filter(function(s) {return -1 === underlines.indexOf(s.toLowerCase());});
  return {underlines: underlines, strikes: strikes};
}

function stripPercentS(str) {
  return str.replace(/%S/g, '').trim();
}
<|endoftext|>


<commit_before>import I18n from 'i18n-js';
import {
  RECEIVE_WIZARD_ASSIGNMENT_OPTIONS,
  RECEIVE_WIZARD_PANELS,
  SELECT_WIZARD_OPTION,
  // EXPAND_WIZARD_OPTION,
  WIZARD_ADVANCE,
  WIZARD_REWIND,
  WIZARD_GOTO,
  WIZARD_ENABLE_SUMMARY_MODE,
  WIZARD_DISABLE_SUMMARY_MODE,
  SINGLE_CHOICE_PANEL
  // WIZARD_RESET,
  // WIZARD_SUBMITTED
} from '../constants';

const assignmentsPanel = (options = []) => {
  return {
    title: I18n.t('wizard.assignment_type'),
    description: I18n.t('wizard.select_assignment'),
    options,
    type: 1,
    minimum: 1,
    key: 'index'
  };
};

const datesPanel = {
  title: I18n.t('wizard.course_dates'),
  description: '',
  options: [],
  type: -1,
  minimum: 0,
  key: 'dates'
};

const summaryPanel = {
  title: I18n.t('wizard.summary'),
  description: I18n.t('wizard.review_selections'),
  options: [],
  type: -1,
  minimum: 0,
  key: 'summary'
};

const panels = (assignmentOptions = [], extraPanels = []) => {
  return [
    datesPanel,
    assignmentsPanel(assignmentOptions),
    ...extraPanels,
    summaryPanel
  ];
};

const updatedPanelSelections = (oldPanels, panelIndex, optionIndex) => {
  const newPanels = [...oldPanels];
  const updatedPanel = { ...newPanels[panelIndex] };
  const updatedOptions = updatedPanel.options.map((option, index) => {
    const updatedOption = { ...option };
    if (index === optionIndex) {
      updatedOption.selected = !option.selected;
    } else if (updatedPanel.type === SINGLE_CHOICE_PANEL) {
      updatedOption.selected = false;
    }
    return updatedOption;
  });

  updatedPanel.options = updatedOptions;
  newPanels[panelIndex] = updatedPanel;
  return newPanels;
};

const initialState = {
  activeIndex: 0,
  summary: false,
  wizardKey: null,
  assignmentOptions: [],
  panels: panels()
};

export default function wizard(state = initialState, action) {
  switch (action.type) {
    case RECEIVE_WIZARD_ASSIGNMENT_OPTIONS: {
      const assignmentOptions = action.assignmentOptions;
      return { ...state, assignmentOptions, panels: panels(assignmentOptions) };
    }
    case RECEIVE_WIZARD_PANELS: {
      const extraPanels = action.extraPanels;
      return { ...state, panels: panels(state.assignmentOptions, extraPanels) };
    }
    case SELECT_WIZARD_OPTION: {
      return { ...state, panels: updatedPanelSelections(state.panels, action.panelIndex, action.optionIndex) };
    }
    case WIZARD_ADVANCE: {
      return { ...state, activeIndex: state.activeIndex + 1 };
    }
    case WIZARD_REWIND: {
      return { ...state, activeIndex: state.activeIndex - 1 };
    }
    case WIZARD_GOTO: {
      return { ...state, activeIndex: action.toPanelIndex };
    }
    case WIZARD_ENABLE_SUMMARY_MODE:
      return { ...state, summary: true };
    case WIZARD_DISABLE_SUMMARY_MODE:
      return { ...state, summary: false };
    default:
      return state;
  }
}
<commit_msg>Don't deselect re-clicked options for single-choice panels<commit_after>import I18n from 'i18n-js';
import {
  RECEIVE_WIZARD_ASSIGNMENT_OPTIONS,
  RECEIVE_WIZARD_PANELS,
  SELECT_WIZARD_OPTION,
  // EXPAND_WIZARD_OPTION,
  WIZARD_ADVANCE,
  WIZARD_REWIND,
  WIZARD_GOTO,
  WIZARD_ENABLE_SUMMARY_MODE,
  WIZARD_DISABLE_SUMMARY_MODE,
  SINGLE_CHOICE_PANEL
  // WIZARD_RESET,
  // WIZARD_SUBMITTED
} from '../constants';

const assignmentsPanel = (options = []) => {
  return {
    title: I18n.t('wizard.assignment_type'),
    description: I18n.t('wizard.select_assignment'),
    options,
    type: 1,
    minimum: 1,
    key: 'index'
  };
};

const datesPanel = {
  title: I18n.t('wizard.course_dates'),
  description: '',
  options: [],
  type: -1,
  minimum: 0,
  key: 'dates'
};

const summaryPanel = {
  title: I18n.t('wizard.summary'),
  description: I18n.t('wizard.review_selections'),
  options: [],
  type: -1,
  minimum: 0,
  key: 'summary'
};

const panels = (assignmentOptions = [], extraPanels = []) => {
  return [
    datesPanel,
    assignmentsPanel(assignmentOptions),
    ...extraPanels,
    summaryPanel
  ];
};

const updatedPanelSelections = (oldPanels, panelIndex, optionIndex) => {
  const newPanels = [...oldPanels];
  const updatedPanel = { ...newPanels[panelIndex] };
  const updatedOptions = updatedPanel.options.map((option, index) => {
    const updatedOption = { ...option };
    if (updatedPanel.type === SINGLE_CHOICE_PANEL) {
      updatedOption.selected = false;
    }
    if (index === optionIndex) {
      updatedOption.selected = !updatedOption.selected;
    }
    return updatedOption;
  });

  updatedPanel.options = updatedOptions;
  newPanels[panelIndex] = updatedPanel;
  return newPanels;
};

const initialState = {
  activeIndex: 0,
  summary: false,
  wizardKey: null,
  assignmentOptions: [],
  panels: panels()
};

export default function wizard(state = initialState, action) {
  switch (action.type) {
    case RECEIVE_WIZARD_ASSIGNMENT_OPTIONS: {
      const assignmentOptions = action.assignmentOptions;
      return { ...state, assignmentOptions, panels: panels(assignmentOptions) };
    }
    case RECEIVE_WIZARD_PANELS: {
      const extraPanels = action.extraPanels;
      return { ...state, panels: panels(state.assignmentOptions, extraPanels) };
    }
    case SELECT_WIZARD_OPTION: {
      return { ...state, panels: updatedPanelSelections(state.panels, action.panelIndex, action.optionIndex) };
    }
    case WIZARD_ADVANCE: {
      return { ...state, activeIndex: state.activeIndex + 1 };
    }
    case WIZARD_REWIND: {
      return { ...state, activeIndex: state.activeIndex - 1 };
    }
    case WIZARD_GOTO: {
      return { ...state, activeIndex: action.toPanelIndex };
    }
    case WIZARD_ENABLE_SUMMARY_MODE:
      return { ...state, summary: true };
    case WIZARD_DISABLE_SUMMARY_MODE:
      return { ...state, summary: false };
    default:
      return state;
  }
}
<|endoftext|>


<commit_before><commit_msg>Prevent double submitting of web forms<commit_after><|endoftext|>


<commit_before>var Story = function(meteorite) {
  this.meteorite = meteorite;
}

Story.prototype.generateTree = function(grammar) {
  var trace = tracery.createGrammar(grammar);
  this.storyTree = trace.expand("#origin#");
};

Story.prototype.renderStory = function() {
  // add case logic
  return this.storyTree.finalText;
}

Story.prototype.confusedGrammar = function() {return {
    "hello": ["Hello!", "Whaddup whaddup!", "Salve!", "Konnichiwa!", "<stares at you>", "Bonjourno."],
    "greeting": [`#hello# My name is ${this.meteorite.name}.`],
    "introduction": ["I have fallen from the sky.", "I've never been so still.", "WHAT ARE YOU?"],
    "confusedStatement": ["Help me!", "I've lost my fam.", "Where am I?", "Huhwhahuh?!"],
    "origin": ["#greeting#  #introduction#  #confusedStatement#"]
  };
}

Story.prototype.coherentGrammar = function() { return {
  "thanks": ["THANKS", "Wowzers, you're great!", "Domo Arigato.", "Thank you, but our princess is in another castle."],
  "timeframe": ["year", "day", "century", "light-year", "parsec"],
  "spaceThing": ["the hubble telescope", "andromeda", "the kuiper belt", "Reno, NV"],
  "expandedOrigin": ["It feels like just yester#timeframe# that I was flying past #spaceThing#.", "It wasn't an accident... it was SABOTAGE", `There are other ${this.recclass} meteorites just like me who survived... I am sure of it.`],
  "relative": ["my cousin", "my brother", "my sister", "my neighbor", "my dear friend"],
  "hint": [`along ${this.meteorite.nextMeteorite.getLat()} latitude`, `along ${this.meteorite.nextMeteorite.getLong()} longitude`, `in ${this.meteorite.nextMeteorite.getYear()} AD`],
  "clue": ["Please find #relative#!  Look #hint#."],
  "origin": ["#thanks# #expandedOrigin# #clue#"]
  }

// var warStory = {
//   "timeunit": ["year", "day", "century", "light-year", "parsec"],
//   "timeframe": ["50000 #timeunit#s", "a #timeunit#", "42 #timeunit#s"],
//   "activity": ["taking a high-orbit stroll around Sirius"],
//   "initialSetting": ["About #timeframe# ago my family was #activity# when #pointOfConflict#." ],
//   "pointOfConflict": ["The steel swarm tore most of us into spacedust.", "One of the steel beings "],
//   "origin": []
//   }

}

<commit_msg>minor typo fixing<commit_after>var Story = function(meteorite) {
  this.meteorite = meteorite;
}

Story.prototype.generateTree = function(grammar) {
  var trace = tracery.createGrammar(grammar);
  this.storyTree = trace.expand("#origin#");
};

Story.prototype.renderStory = function() {
  // add case logic
  return this.storyTree.finalText;
}

Story.prototype.confusedGrammar = function() {return {
    "hello": ["Hello!", "Whaddup whaddup!", "Salve!", "Konnichiwa!", "<stares at you>", "Bonjourno."],
    "greeting": [`#hello# My name is ${this.meteorite.name}.`],
    "introduction": ["I have fallen from the sky.", "I've never been so still.", "WHAT ARE YOU?"],
    "confusedStatement": ["Help me!", "I've lost my fam.", "Where am I?", "Huhwhahuh?!"],
    "origin": ["#greeting#  #introduction#  #confusedStatement#"]
  };
}

Story.prototype.coherentGrammar = function() { return {
  "thanks": ["THANKS", "Wowzers, you're great!", "Domo Arigato.", "Thank you, but our princess is in another castle."],
  "timeframe": ["year", "day", "century", "light-year", "parsec"],
  "spaceThing": ["the Hubble Telescope", "Andromeda", "the Kuiper Belt", "Reno, NV"],
  "expandedOrigin": ["It feels like just yester#timeframe# that I was flying past #spaceThing#.", "It wasn't an accident... it was SABOTAGE!", `There are other ${this.recclass} meteorites just like me who survived... I am sure of it.`],
  "relative": ["my cousin", "my brother", "my sister", "my neighbor", "my dear friend"],
  "hint": [`along ${this.meteorite.nextMeteorite.getLat()} latitude`, `along ${this.meteorite.nextMeteorite.getLong()} longitude`, `in ${this.meteorite.nextMeteorite.getYear()} AD`],
  "clue": ["Please find #relative#!  Look #hint#."],
  "origin": ["#thanks# #expandedOrigin# #clue#"]
  }

// var warStory = {
//   "timeunit": ["year", "day", "century", "light-year", "parsec"],
//   "timeframe": ["50000 #timeunit#s", "a #timeunit#", "42 #timeunit#s"],
//   "activity": ["taking a high-orbit stroll around Sirius"],
//   "initialSetting": ["About #timeframe# ago my family was #activity# when #pointOfConflict#." ],
//   "pointOfConflict": ["The steel swarm tore most of us into spacedust.", "One of the steel beings "],
//   "origin": []
//   }

}

<|endoftext|>


<commit_before>var request = require("request");
var cheerio = require("cheerio");
var url = require("url");

// we prepend a zero-width char because of a weird fucking
// bug on mobile safari where if the embed is the first child,
// the video player will not show. This causes issues with
// inline elements displaying (adds extra space) solution needed
// that doesn't disrupt page layout...
function template(url, width, height) {
  return (
    '

'
  );
}

var FAIL = "Could not retrieve song properties";

module.exports = function (href, callback) {
  // Before loading anything, check that the path starts with
  // /album or /track
  var path, paths;
  try {
    path = url.parse(href).pathname;
  } catch (e) {
    return callback(new Error(FAIL));
  }

  // Trim trailing slash if applicable
  if (path.slice(-1) === "/") path = path.slice(0, -1);

  paths = path.split("/");

  if (paths.length < 1) {
    return callback(new Error(FAIL));
  }

  var category = paths[1];
  if (category !== "track" && category !== "album") {
    return callback(new Error(FAIL));
  }

  request(href, function (error, response, body) {
    if (error) return callback(error);

    var $, height, width, html;

    try {
      $ = cheerio.load(body);
      width = Number($('meta[property="og:video:width"]').attr("content"));
      height = Number($('meta[property="og:video:height"]').attr("content"));
      html = $('meta[property="og:video"]').attr("content");
    } catch (error) {
      return callback(new Error(FAIL));
    }

    if (!html || isNaN(height) || isNaN(width)) {
      return callback(new Error(FAIL));
    }

    return callback(null, template(html, width, height));
  });
};
<commit_msg>Use px instead of % for padding on bandcamp embeds<commit_after>var request = require("request");
var cheerio = require("cheerio");
var url = require("url");

// we prepend a zero-width char because of a weird fucking
// bug on mobile safari where if the embed is the first child,
// the video player will not show. This causes issues with
// inline elements displaying (adds extra space) solution needed
// that doesn't disrupt page layout...
function template(url, width, height) {
  return (
    '

'
  );
}

var FAIL = "Could not retrieve song properties";

module.exports = function (href, callback) {
  // Before loading anything, check that the path starts with
  // /album or /track
  var path, paths;
  try {
    path = url.parse(href).pathname;
  } catch (e) {
    return callback(new Error(FAIL));
  }

  // Trim trailing slash if applicable
  if (path.slice(-1) === "/") path = path.slice(0, -1);

  paths = path.split("/");

  if (paths.length < 1) {
    return callback(new Error(FAIL));
  }

  var category = paths[1];
  if (category !== "track" && category !== "album") {
    return callback(new Error(FAIL));
  }

  request(href, function (error, response, body) {
    if (error) return callback(error);

    var $, height, width, html;

    try {
      $ = cheerio.load(body);
      width = Number($('meta[property="og:video:width"]').attr("content"));
      height = Number($('meta[property="og:video:height"]').attr("content"));
      html = $('meta[property="og:video"]').attr("content");
    } catch (error) {
      return callback(new Error(FAIL));
    }

    if (!html || isNaN(height) || isNaN(width)) {
      return callback(new Error(FAIL));
    }

    return callback(null, template(html, width, height));
  });
};
<|endoftext|>


<commit_before>import React from 'react';
import Relay from 'react-relay';
import shallowCompare from 'react-addons-shallow-compare';
import classNames from 'classnames';

import UserAvatar from '../../shared/UserAvatar';
import Dropdown from '../../shared/Dropdown';
import Badge from '../../shared/Badge';
import Icon from '../../shared/Icon';
import SectionLoader from '../../shared/SectionLoader';
import AgentsCount from '../../organization/AgentsCount';
import NewChangelogsBadge from '../../user/NewChangelogsBadge';
import permissions from '../../../lib/permissions';

import NavigationButton from './navigation-button';
import DropdownButton from './dropdown-button';
import SupportDialog from './support-dialog';
import MyBuilds from './MyBuilds';

class Navigation extends React.Component {
  static propTypes = {
    organization: React.PropTypes.object,
    viewer: React.PropTypes.object,
    relay: React.PropTypes.object.isRequired
  };

  componentDidMount() {
    this.props.relay.setVariables({ isMounted: true });
  }

  state = {
    showingOrgDropdown: false,
    showingUserDropdown: false,
    showingSupportDialog: false,
    warning: window['_navigation'] && window['_navigation']['warning']
  };

  handleOrgDropdownToggle = (visible) => {
    this.setState({ showingOrgDropdown: visible });
  };

  handleUserDropdownToggle = (visible) => {
    this.setState({ showingUserDropdown: visible });
  };

  handleLogoutClick = (evt) => {
    evt.preventDefault();
    this.logoutFormNode.submit();
  };

  handleSupportClick = () => {
    // close the support dropdown if it's open
    if (this.state.showingUserDropdown) {
      this.userDropdown.setShowing(false);
    }

    this.setState({ showingSupportDialog: true });
  };

  handleSupportDialogClose = () => {
    this.setState({ showingSupportDialog: false });
  };

  shouldComponentUpdate = (nextProps, nextState) => {
    return shallowCompare(this, nextProps, nextState);
  };

  renderTopOrganizationMenu() {
    if (this.props.organization) {
      return (

          {this.renderOrganizationMenu()}

      );
    }
  }

  renderBottomOrganizationMenu() {
    if (this.props.organization) {
      return (

            {this.renderOrganizationMenu({ paddingLeft: 0 })}

      );
    }
  }

  renderMyBuilds() {
    return (
      <MyBuilds viewer={this.props.viewer} />
    );
  }

  renderOrganizationsList() {
    if (!this.props.viewer.organizations) {
      return <SectionLoader />;
    }

    const nodes = [];

    this.props.viewer.organizations.edges.forEach((org) => {
      // Don't show the active organization in the selector
      if (!this.props.organization || (org.node.slug !== this.props.organization.slug)) {
        nodes.push(
          <NavigationButton
            key={org.node.slug}
            href={`/${org.node.slug}`}
            className="block"
          >
            {org.node.name}
          </NavigationButton>
        );
      }
    });

    nodes.push(
      <NavigationButton
        key="newOrganization"
        href="/organizations/new"
        className="block"
      >
        <Icon icon="plus-circle" className="icon-mr" style={{ width: 12, height: 12 }} />Create New Organization
      </NavigationButton>
    );

    if (nodes.length > 0) {
      return nodes;
    }
  }

  renderOrganizationMenu(options = {}) {
    const organization = this.props.organization;
    const paddingLeft = typeof options.paddingLeft === "number" ? options.paddingLeft : 15;

    if (organization) {
      return (

          <NavigationButton className="py0" style={{ paddingLeft: paddingLeft }} href={`/${organization.slug}`} linkIf={Features.NewPipelineList}>Pipelines</NavigationButton>
          <NavigationButton className="py0" href={`/organizations/${organization.slug}/agents`} linkIf={Features.NewAgentList}>
            {'Agents'}
            <Badge className="hover-lime-child"><AgentsCount organization={organization} /></Badge>
          </NavigationButton>

          {this.renderOrganizationSettingsButton()}

      );
    }
  }

  renderOrganizationSettingsButton() {
    const organization = this.props.organization;

    // The settings page will redirect to the first section the user has access
    // to. If they _just_ have teams admin enabled, skip the redirect and go
    // straight to the teams page.
    return permissions(organization.permissions).first(
      {
        only: "teamAdmin",
        render: () => <NavigationButton className="py0" href={`/organizations/${organization.slug}/teams`}>Settings</NavigationButton>
      },
      {
        any: true,
        render: () => {
          return [
            <NavigationButton key={1} className="py0" href={`/organizations/${organization.slug}/users`}>Users</NavigationButton>,
            <NavigationButton key={2} className="py0" href={`/organizations/${organization.slug}/settings`}>Settings</NavigationButton>
          ];
        }
      }
    );
  }

  render() {
    return (

              <NavigationButton href="/" className="px3 hover-faded-children" style={{ paddingLeft: 0 }}>

              </NavigationButton>
              <NewChangelogsBadge
                className="mr2 relative"
                style={{
                  top: -5,
                  marginLeft: -8
                }}
                viewer={this.props.viewer}
                isMounted={this.props.relay.variables.isMounted}
              />

            <Dropdown width={250} className="flex" style={{ minWidth: "5em" }} onToggle={this.handleOrgDropdownToggle}>
              <DropdownButton
                className={classNames("py0", { "lime": this.state.showingOrgDropdown })}
                style={{
                  backgroundImage: 'url(' + require('./nav-button-right-arrow.svg') + ')',
                  backgroundRepeat: 'no-repeat',
                  backgroundPosition: 'center right',
                  paddingRight: 20
                }}
              >

                  {this.props.organization ? this.props.organization.name : 'Organizations'}

                <Icon icon="down-triangle" style={{ width: 7, height: 7, marginLeft: '.5em' }} />
              </DropdownButton>
              {this.renderOrganizationsList()}
            </Dropdown>

            {this.renderTopOrganizationMenu()}

            {this.renderMyBuilds()}
            <NavigationButton className="py0 xs-hide sm-hide" href={`/docs`}>Documentation</NavigationButton>
            <NavigationButton className="py0 xs-hide sm-hide" onClick={this.handleSupportClick}>Support</NavigationButton>

            <Dropdown width={170} className="flex" ref={(userDropdown) => this.userDropdown = userDropdown} onToggle={this.handleUserDropdownToggle}>
              <DropdownButton className={classNames("py0", { "lime": this.state.showingUserDropdown })}
                style={{ paddingRight: 0 }}
              >
                <UserAvatar user={this.props.viewer.user} className="flex-none flex items-center" style={{ width: 26, height: 26 }} />
                {this.props.viewer.user.name}

                  <Icon icon="down-triangle" style={{ width: 7, height: 7, marginLeft: '.5em' }} />

              </DropdownButton>

              <NavigationButton href="/user/settings">Personal Settings</NavigationButton>

                <NavigationButton className="md-hide lg-hide" href={`/docs`}>Documentation</NavigationButton>
                <NavigationButton className="md-hide lg-hide" onClick={this.handleSupportClick}>Support</NavigationButton>

              <form action="/logout" method="post" ref={(logoutFormNode) => this.logoutFormNode = logoutFormNode}>
                <input type="hidden" name="_method" value={"delete"} />
                <input type="hidden" name={window._csrf.param} value={window._csrf.token} />
                <NavigationButton href="#" onClick={this.handleLogoutClick}>
                  {this.state.warning ? 'Unassume User' : 'Logout'}
                </NavigationButton>
              </form>
            </Dropdown>

        {this.renderBottomOrganizationMenu()}

        <SupportDialog isOpen={this.state.showingSupportDialog} onRequestClose={this.handleSupportDialogClose} />

    );
  }
}

export default Relay.createContainer(Navigation, {
  initialVariables: {
    isMounted: false
  },

  fragments: {
    organization: () => Relay.QL`
        fragment on Organization {
          ${AgentsCount.getFragment('organization')}
          name
          id
          slug
          permissions {
            organizationUpdate {
              allowed
            }
            organizationMemberCreate {
              allowed
            }
            notificationServiceUpdate {
              allowed
            }
            organizationBillingUpdate {
              allowed
            }
            teamAdmin {
              allowed
            }
          }
        }
      `,
    viewer: (variables) => Relay.QL`
        fragment on Viewer {
          ${MyBuilds.getFragment('viewer')}
          ${NewChangelogsBadge.getFragment('viewer', variables)}
          user {
            name
            avatar {
              url
            }
          }
          organizations(first: 500) @include(if: $isMounted) {
            edges {
              node {
                slug
                name
              }
            }
          }
        }
      `
  }
});
<commit_msg>Make use of SessionHashStore in the Navigation<commit_after>import React from 'react';
import Relay from 'react-relay';
import shallowCompare from 'react-addons-shallow-compare';
import classNames from 'classnames';

import UserAvatar from '../../shared/UserAvatar';
import Dropdown from '../../shared/Dropdown';
import Badge from '../../shared/Badge';
import Icon from '../../shared/Icon';
import SectionLoader from '../../shared/SectionLoader';
import AgentsCount from '../../organization/AgentsCount';
import NewChangelogsBadge from '../../user/NewChangelogsBadge';
import permissions from '../../../lib/permissions';

import SessionHashStore from '../../../stores/SessionHashStore';

import NavigationButton from './navigation-button';
import DropdownButton from './dropdown-button';
import SupportDialog from './support-dialog';
import MyBuilds from './MyBuilds';

class Navigation extends React.Component {
  static propTypes = {
    organization: React.PropTypes.object,
    viewer: React.PropTypes.object,
    relay: React.PropTypes.object.isRequired
  };

  componentDidMount() {
    this.props.relay.setVariables({ isMounted: true });
    SessionHashStore.on('change', this.handleSessionDataChange);
  }

  componentWillMount() {
    if (this.props.organization && this.props.organization.slug) {
      this.setState({
        lastDefaultTeam: SessionHashStore.get(`organization-default-team:${this.props.organization.slug}`)
      });
    }
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.organization && nextProps.organization.slug && nextProps.organization.slug !== this.props.organization.slug) {
      this.setState({
        lastDefaultTeam: SessionHashStore.get(`organization-default-team:${nextProps.organization.slug}`)
      });
    }
  }

  componentWillUnmount() {
    SessionHashStore.off('change', this.handleSessionDataChange);
  }

  state = {
    showingOrgDropdown: false,
    showingUserDropdown: false,
    showingSupportDialog: false,
    warning: window['_navigation'] && window['_navigation']['warning'],
    lastDefaultTeam: null
  };

  handleSessionDataChange = ({ key, newValue }) => {
    if (key === `organization-default-team:${this.props.organization.slug}`) {
      this.setState({
        lastDefaultTeam: newValue
      });
    }
  };

  handleOrgDropdownToggle = (visible) => {
    this.setState({ showingOrgDropdown: visible });
  };

  handleUserDropdownToggle = (visible) => {
    this.setState({ showingUserDropdown: visible });
  };

  handleLogoutClick = (evt) => {
    evt.preventDefault();
    this.logoutFormNode.submit();
  };

  handleSupportClick = () => {
    // close the support dropdown if it's open
    if (this.state.showingUserDropdown) {
      this.userDropdown.setShowing(false);
    }

    this.setState({ showingSupportDialog: true });
  };

  handleSupportDialogClose = () => {
    this.setState({ showingSupportDialog: false });
  };

  shouldComponentUpdate = (nextProps, nextState) => {
    return shallowCompare(this, nextProps, nextState);
  };

  renderTopOrganizationMenu() {
    if (this.props.organization) {
      return (

          {this.renderOrganizationMenu()}

      );
    }
  }

  renderBottomOrganizationMenu() {
    if (this.props.organization) {
      return (

            {this.renderOrganizationMenu({ paddingLeft: 0 })}

      );
    }
  }

  renderMyBuilds() {
    return (
      <MyBuilds viewer={this.props.viewer} />
    );
  }

  renderOrganizationsList() {
    if (!this.props.viewer.organizations) {
      return <SectionLoader />;
    }

    const nodes = [];

    this.props.viewer.organizations.edges.forEach((org) => {
      // Don't show the active organization in the selector
      if (!this.props.organization || (org.node.slug !== this.props.organization.slug)) {
        nodes.push(
          <NavigationButton
            key={org.node.slug}
            href={`/${org.node.slug}`}
            className="block"
          >
            {org.node.name}
          </NavigationButton>
        );
      }
    });

    nodes.push(
      <NavigationButton
        key="newOrganization"
        href="/organizations/new"
        className="block"
      >
        <Icon icon="plus-circle" className="icon-mr" style={{ width: 12, height: 12 }} />Create New Organization
      </NavigationButton>
    );

    if (nodes.length > 0) {
      return nodes;
    }
  }

  getOrganizationPipelinesUrl(organization) {
    let link = `/${organization.slug}`;

    if (this.state.lastDefaultTeam) {
      link = `${link}?team=${this.state.lastDefaultTeam}`;
    }

    return link;
  }

  renderOrganizationMenu(options = {}) {
    const organization = this.props.organization;
    const paddingLeft = typeof options.paddingLeft === "number" ? options.paddingLeft : 15;

    if (organization) {
      return (

          <NavigationButton className="py0" style={{ paddingLeft: paddingLeft }} href={this.getOrganizationPipelinesUrl(organization)} linkIf={Features.NewPipelineList}>Pipelines</NavigationButton>
          <NavigationButton className="py0" href={`/organizations/${organization.slug}/agents`} linkIf={Features.NewAgentList}>
            {'Agents'}
            <Badge className="hover-lime-child"><AgentsCount organization={organization} /></Badge>
          </NavigationButton>

          {this.renderOrganizationSettingsButton()}

      );
    }
  }

  renderOrganizationSettingsButton() {
    const organization = this.props.organization;

    // The settings page will redirect to the first section the user has access
    // to. If they _just_ have teams admin enabled, skip the redirect and go
    // straight to the teams page.
    return permissions(organization.permissions).first(
      {
        only: "teamAdmin",
        render: () => <NavigationButton className="py0" href={`/organizations/${organization.slug}/teams`}>Settings</NavigationButton>
      },
      {
        any: true,
        render: () => {
          return [
            <NavigationButton key={1} className="py0" href={`/organizations/${organization.slug}/users`}>Users</NavigationButton>,
            <NavigationButton key={2} className="py0" href={`/organizations/${organization.slug}/settings`}>Settings</NavigationButton>
          ];
        }
      }
    );
  }

  render() {
    return (

              <NavigationButton href="/" className="px3 hover-faded-children" style={{ paddingLeft: 0 }}>

              </NavigationButton>
              <NewChangelogsBadge
                className="mr2 relative"
                style={{
                  top: -5,
                  marginLeft: -8
                }}
                viewer={this.props.viewer}
                isMounted={this.props.relay.variables.isMounted}
              />

            <Dropdown width={250} className="flex" style={{ minWidth: "5em" }} onToggle={this.handleOrgDropdownToggle}>
              <DropdownButton
                className={classNames("py0", { "lime": this.state.showingOrgDropdown })}
                style={{
                  backgroundImage: 'url(' + require('./nav-button-right-arrow.svg') + ')',
                  backgroundRepeat: 'no-repeat',
                  backgroundPosition: 'center right',
                  paddingRight: 20
                }}
              >

                  {this.props.organization ? this.props.organization.name : 'Organizations'}

                <Icon icon="down-triangle" style={{ width: 7, height: 7, marginLeft: '.5em' }} />
              </DropdownButton>
              {this.renderOrganizationsList()}
            </Dropdown>

            {this.renderTopOrganizationMenu()}

            {this.renderMyBuilds()}
            <NavigationButton className="py0 xs-hide sm-hide" href={`/docs`}>Documentation</NavigationButton>
            <NavigationButton className="py0 xs-hide sm-hide" onClick={this.handleSupportClick}>Support</NavigationButton>

            <Dropdown width={170} className="flex" ref={(userDropdown) => this.userDropdown = userDropdown} onToggle={this.handleUserDropdownToggle}>
              <DropdownButton className={classNames("py0", { "lime": this.state.showingUserDropdown })}
                style={{ paddingRight: 0 }}
              >
                <UserAvatar user={this.props.viewer.user} className="flex-none flex items-center" style={{ width: 26, height: 26 }} />
                {this.props.viewer.user.name}

                  <Icon icon="down-triangle" style={{ width: 7, height: 7, marginLeft: '.5em' }} />

              </DropdownButton>

              <NavigationButton href="/user/settings">Personal Settings</NavigationButton>

                <NavigationButton className="md-hide lg-hide" href={`/docs`}>Documentation</NavigationButton>
                <NavigationButton className="md-hide lg-hide" onClick={this.handleSupportClick}>Support</NavigationButton>

              <form action="/logout" method="post" ref={(logoutFormNode) => this.logoutFormNode = logoutFormNode}>
                <input type="hidden" name="_method" value={"delete"} />
                <input type="hidden" name={window._csrf.param} value={window._csrf.token} />
                <NavigationButton href="#" onClick={this.handleLogoutClick}>
                  {this.state.warning ? 'Unassume User' : 'Logout'}
                </NavigationButton>
              </form>
            </Dropdown>

        {this.renderBottomOrganizationMenu()}

        <SupportDialog isOpen={this.state.showingSupportDialog} onRequestClose={this.handleSupportDialogClose} />

    );
  }
}

export default Relay.createContainer(Navigation, {
  initialVariables: {
    isMounted: false
  },

  fragments: {
    organization: () => Relay.QL`
        fragment on Organization {
          ${AgentsCount.getFragment('organization')}
          name
          id
          slug
          permissions {
            organizationUpdate {
              allowed
            }
            organizationMemberCreate {
              allowed
            }
            notificationServiceUpdate {
              allowed
            }
            organizationBillingUpdate {
              allowed
            }
            teamAdmin {
              allowed
            }
          }
        }
      `,
    viewer: (variables) => Relay.QL`
        fragment on Viewer {
          ${MyBuilds.getFragment('viewer')}
          ${NewChangelogsBadge.getFragment('viewer', variables)}
          user {
            name
            avatar {
              url
            }
          }
          organizations(first: 500) @include(if: $isMounted) {
            edges {
              node {
                slug
                name
              }
            }
          }
        }
      `
  }
});
<|endoftext|>


<commit_before>/* Edwin The Assistant

   Copyright 2017 Nate Lewis

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   dstributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

   Phillips Hue interface for Edwin

 */

'use strict';

const HueAPI = require('node-hue-api');
const convict = require('convict');
const fs = require('fs');

const configFile = './hue.json';

/**
 * Phillips Hue interface
 *
 * @example
 * // turn off the bar lights
 * const Hue = require('../../lib/client/hue');
 * const hue = new Hue();
 * hue.connectToBridge()
 *  .then(() => hue.getGroups() )
 *  .then(() => hue.getIdByName('Bar') )
 *  .then((id) => hue.switchGroupLights(id, false) )
 *  .catch((response) => console.log(response));;
 *
 * @class Hue
 */
class Hue {
  /**
   * @constructs Hue
   * @param {Object} options  Hue configuration options
   */
  constructor(options) {
    const defaults = {
      appDescription: 'Node app',
    };

    // extend defaults
    options = Object.assign({}, defaults, options);

    // Peristent vars
    this.appDescription = options.appDescription;

    // load config
    this.config = convict({
      bridge: null,
      user: null,
      groups: [],
    });
    if (fs.existsSync(configFile)) {
      this.config.loadFile(configFile);
    }
  }

  /**
   * Api Getter
   *
   * @return {Object} Hue API object
   */
  get api() {
    /* eslint new-cap: ["error", { "newIsCap": false }] */
    return new HueAPI.api(this.config.get('bridge'), this.config.get('user'));
  }

  /**
   * Save the current Hue config
   */
  saveConfig() {
    fs.writeFileSync(configFile, JSON.stringify(this.config.get(), null, 2));
  }

  /**
   * Connect to the bridge.  This must be called before anything else in the
   * promise chain.
   *
   * @return {Promise} User or string error
   */
  connectToBridge() {
    if (this.config.get('bridge') && this.config.get('user')) {
      return Promise.resolve(this.config.get('user'));
    }
    return HueAPI
      .nupnpSearch()
      .then((bridges) => this.getBridgeIp(bridges))
      .then((bridge) => new HueAPI.api()
        .registerUser(bridge, this.appDescription)
        .then((user) => {
          this.config.set('user', user);
          this.saveConfig();
          return Promise.resolve(user);
        })
        .catch(() => {
          return Promise.reject('Cannot link to your hue bridge, press the '
            + 'button on the bridge your trying connect and try again');
        })
      );
  }

  /**
   * Set the brightness of a group of lights, and turn them on if they are not
   * already on
   * @param  {number} [id=null]        Light group number from getIdByName()
   * @param  {number} [brightness=100] Brightness percent ( 0 is not off)
   *
   * @return {Promise} State of light
   */
  setGroupLightsBrightness(id = null, brightness = 100) {
    if ( !id ) {
      return Promise.reject('Not sure what lights those are');
    }
    return this.api.setGroupLightState(id,
      {'on': true, 'bri': 255*(brightness/100)}
    );
  }

  /**
   * Get the brightness of a group of lights
   * @param  {number} [id=null]        Light group number from getIdByName()
   *
   * @return {Promise} Brightness of light
   */
  getGroupLightsBrightnessAsync(id = null) {
    if ( !id ) {
      return Promise.reject('Not sure what lights those are');
    }
    return this.config.get('groups')[id].action.bri/255*100;
  }

  /**
   * Switch a group of lights on or off
   * @param  {number}  [id=null]  Number of the light group from getIdByName()
   * @param  {Boolean} [on=false] On = true, Off = false
   *
   * @return {Promise} State of light
   */
  switchGroupLights(id = null, on = false) {
    if ( !id ) {
      return Promise.reject('Not sure what lights those are');
    }
    return this.api.setGroupLightState(id, {'on': on});
  }

  /**
   * Get the list of groups available from the connected brid
   *
   * @return {Object} Array of light groups or string error
   */
  getGroups() {
    return this.api
      .groups()
      .then((groupData) => {
        this.config.set('groups', groupData);
        this.saveConfig();
        return groupData;
      })
      .catch(() => {
        return Promise.reject('Unable to reach your Hue bridge');
      });
  }

  /**
   * Look up what group number by the name of the group. getGroups must be
   * called before calling this function.
   * @param  {string} name Non case-sensitve name of group
   *
   * @return {number}      Number of light group or string error
   */
  getGroupIdByName(name) {
    const group = this.config.get('groups');
    for (const k in group) {
      if (group.hasOwnProperty(k)) {
        if (group[k].name.toLowerCase() == name.toLowerCase()) {
          return Promise.resolve(group[k].id);
        }
      }
    }
    // no match!
    return Promise.reject('Could not find lights named ' + name.toLowerCase());
  }

  /**
   * Find the bridges
   *
   * @return {Promise} list of bridges or string error
   */
  listBridges() {
    return hue
      .nupnpSearch()
      .then((bridges) => {
        return bridges;
      })
      .catch(() => {
        return Promise.reject('No bridge found on network');
      });
  }

  /**
   * [getBridgeIp description]
   * @param  {string} [bridges=null] Return the IP of the first bridge found
   *
   * @return {Promise}               Ip address or string error
   */
  getBridgeIp(bridges = null) {
    const self = this;
    return new Promise(function(resolve, reject) {
      // currently only support 1 bridge, the first one it finds
      const bridge = bridges[0];
      if (bridge) {
        self.config.set('bridge', bridge.ipaddress);
        self.saveConfig();
        return resolve(bridge.ipaddress);
      }
      return reject(`Can't find a Hue bridge on your network`);
    });
  }
}

module.exports = Hue;
<commit_msg>update to group sync with small delay and cleanup<commit_after>/* Edwin The Assistant

   Copyright 2017 Nate Lewis

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   dstributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

   Phillips Hue interface for Edwin

 */

'use strict';

const HueAPI = require('node-hue-api');
const convict = require('convict');
const fs = require('fs');

const HUE_CONFIG_FILE = './hue.json';

/**
 * Phillips Hue interface
 *
 * @example
 * // turn off the bar lights
 * const Hue = require('../../lib/client/hue');
 * const hue = new Hue();
 * hue.connectToBridge()
 *  .then(() => hue.syncGroups() )
 *  .then(() => hue.getIdByName('Bar') )
 *  .then((id) => hue.switchGroupLights(id, false) )
 *  .catch((response) => console.log(response));;
 *
 * @class Hue
 */
class Hue {
  /**
   * @constructs Hue
   * @param {Object} options  Hue configuration options
   */
  constructor(options) {
    const defaults = {
      appDescription: 'Node app',
    };

    // extend defaults
    options = Object.assign({}, defaults, options);

    // Peristent vars
    this.appDescription = options.appDescription;

    // load config
    this.config = convict({
      bridge: null,
      user: null,
      groups: [],
    });
    if (fs.existsSync(HUE_CONFIG_FILE)) {
      this.config.loadFile(HUE_CONFIG_FILE);
    }
  }

  /**
   * Api Getter
   *
   * @return {Object} Hue API object
   */
  get api() {
    /* eslint new-cap: ["error", { "newIsCap": false }] */
    return new HueAPI.api(this.config.get('bridge'), this.config.get('user'));
  }

  /**
   * Save the current Hue config
   */
  saveConfig() {
    fs.writeFileSync(HUE_CONFIG_FILE, JSON.stringify(this.config.get(), null, 2));
  }

  /**
   * Connect to the bridge.  This must be called before anything else in the
   * promise chain.
   *
   * @return {Promise} User or string error
   */
  connectToBridge() {
    if (this.config.get('bridge') && this.config.get('user')) {
      return Promise.resolve(this.config.get('user'));
    }
    return HueAPI
      .nupnpSearch()
      .then((bridges) => this.getBridgeIp(bridges))
      .then((bridge) => new HueAPI.api()
        .registerUser(bridge, this.appDescription)
        .then((user) => {
          this.config.set('user', user);
          this.saveConfig();
          return Promise.resolve(user);
        })
        .catch(() => {
          return Promise.reject('Cannot link to your hue bridge, press the '
            + 'button on the bridge your trying connect and try again');
        })
      );
  }

  /**
   * Set the brightness of a group of lights, and turn them on if they are not
   * already on
   * @param  {number} [id=null]        Light group number from getIdByName()
   * @param  {number} [brightness=100] Brightness percent ( 0 is not off)
   *
   * @return {Promise} State of light
   */
  setGroupLightsBrightness(id = null, brightness = 100) {
    if ( !id ) {
      return Promise.reject('Not sure what lights those are');
    }

    const self = this;
    const bri = 255*(brightness/100);
    return this.api.setGroupLightState(id, {'on': true, 'bri': bri})
      .then(() => {
        setTimeout(function() {
          self.syncGroups();
        }, 500);
      });
  }

  /**
   * Get the brightness of a group of lights
   * @param  {number} [id=null]        Light group number from getIdByName()
   *
   * @return {Promise} Brightness of light
   */
  getGroupLightsBrightnessAsync(id = null) {
    if ( !id ) {
      return Promise.reject('Not sure what lights those are');
    }
    return this.config.get('groups')[id].action.bri/255*100;
  }

  /**
   * Switch a group of lights on or off
   * @param  {number}  [id=null]  Number of the light group from getIdByName()
   * @param  {Boolean} [on=false] On = true, Off = false
   *
   * @return {Promise} State of light
   */
  switchGroupLights(id = null, on = false) {
    if ( !id ) {
      return Promise.reject('Not sure what lights those are');
    }
    const self = this;
    return this.api.setGroupLightState(id, {'on': on})
      .then((result) => {
        setTimeout(function() {
          self.syncGroups();
        }, 1000);
      });
  }

  /**
   * Get the list of groups available from the connected brid
   * @param {number}  delay How long to delay before making call
   *
   * @return {Object} Array of light groups or string error
   */
  syncGroups() {
    return this.api
      .groups()
      .then((groupData) => {
        this.config.set('groups', groupData);
        this.saveConfig();
        return groupData;
      })
      .catch(() => {
        return Promise.reject('Unable to reach your Hue bridge');
      });
  }

  /**
   * Look up what group number by the name of the group. syncGroups must be
   * called before calling this function.
   * @param  {string} name Non case-sensitve name of group
   *
   * @return {Promise}     Number of light group or string error
   */
  getGroupIdByName(name) {
    const group = this.config.get('groups');
    for (const k in group) {
      if (group.hasOwnProperty(k)) {
        if (group[k].name.toLowerCase() == name.toLowerCase()) {
          return Promise.resolve(group[k].id);
        }
      }
    }
    // no match!
    return Promise.reject('Could not find lights named ' + name.toLowerCase());
  }

  /**
   * Look up what group number by the name of the group. syncGroups must be
   * called before calling this function.
   * @param  {string} name Non case-sensitve name of group
   *
   * @return {object}      Object of light group or string error
   */
  getGroupByNameAsync(name) {
    if (fs.existsSync(HUE_CONFIG_FILE)) {
      this.config.loadFile(HUE_CONFIG_FILE);
    }
    const group = this.config.get('groups');
    for (const k in group) {
      if (group.hasOwnProperty(k)) {
        if (group[k].name.toLowerCase() == name.toLowerCase()) {
          return group[k];
        }
      }
    }
    // no match!
    return undefined;
  }

  /**
   * Find the bridges
   *
   * @return {Promise} list of bridges or string error
   */
  listBridges() {
    return hue
      .nupnpSearch()
      .then((bridges) => {
        return bridges;
      })
      .catch(() => {
        return Promise.reject('No bridge found on network');
      });
  }

  /**
   * [getBridgeIp description]
   * @param  {string} [bridges=null] Return the IP of the first bridge found
   *
   * @return {Promise}               Ip address or string error
   */
  getBridgeIp(bridges = null) {
    const self = this;
    return new Promise(function(resolve, reject) {
      // currently only support 1 bridge, the first one it finds
      const bridge = bridges[0];
      if (bridge) {
        self.config.set('bridge', bridge.ipaddress);
        self.saveConfig();
        return resolve(bridge.ipaddress);
      }
      return reject(`Can't find a Hue bridge on your network`);
    });
  }
}

module.exports = Hue;
<|endoftext|>


<commit_before>var CloudCmd, Util, DOM;
(function(CloudCmd, Util, DOM){
    'use strict';

    var KEY = {
        TAB         : 9,
        ENTER       : 13,
        ESC         : 27,

        SPACE       : 32,
        PAGE_UP     : 33,
        PAGE_DOWN   : 34,
        END         : 35,
        HOME        : 36,
        UP          : 38,
        DOWN        : 40,

        INSERT      : 45,
        DELETE      : 46,

        A           : 65,

        D           : 68,

        G           : 71,

        O           : 79,
        Q           : 81,
        R           : 82,
        S           : 83,
        T           : 84,

        F1          : 112,
        F2          : 113,
        F3          : 114,
        F4          : 115,
        F5          : 116,
        F6          : 117,
        F7          : 118,
        F8          : 119,
        F9          : 120,

        TRA         : 192 /* Typewritten Reverse Apostrophe (`) */
    };

    KeyProto.prototype = KEY;
    CloudCmd.Key = new KeyProto(CloudCmd, Util, DOM);

    function KeyProto(CloudCmd, Util, DOM){
        var Key = this,
            Binded,
            lTabPanel   = {
                left        : 0,
                right       : 0

            };

        this.isBind     = function(){return Binded;};

        this.setBind    = function(){Binded = true;};

        this.unsetBind  = function(){Binded = false;};

        this.bind   = function(){
            DOM.Events.addKey(listener);

            /* клавиши назначены*/
            Binded = true;
        };

        function listener(pEvent){
            /* получаем выдленный файл*/
            var i, n, lCurrent  = DOM.getCurrentFile(),
                lKeyCode        = pEvent.keyCode,
                lShift          = pEvent.shiftKey,
                lAlt            = pEvent.altKey,
                lCtrl           = pEvent.ctrlKey;
            /* если клавиши можно обрабатывать*/
            if(Binded){
                switch(lKeyCode){
                case Key.TAB:
                    /* changing parent panel of curent-file */
                    var lPanel = DOM.getPanel(),
                        lId = lPanel.id;

                    lTabPanel[lId] = lCurrent;

                    lPanel = DOM.getPanel({active:false});
                    lId = lPanel.id;

                    if(lTabPanel[lId])
                        DOM.setCurrentFile(lTabPanel[lId]);
                    else{
                        var lFirstFileOnList = DOM.getByTag('li', lPanel)[2];

                        DOM.setCurrentFile(lFirstFileOnList);
                    }

                    DOM.preventDefault(pEvent);//запрет на дальнейшее действие
                    break;

                case Key.INSERT:
                    DOM.setSelectedFile( lCurrent );
                    DOM.setCurrentFile( lCurrent.nextSibling );
                    break;

                case Key.DELETE:
                    if(lShift){
                        var lUrl = DOM.getCurrentPath(lCurrent);

                        if( DOM.isCurrentIsDir(lCurrent) )
                            lUrl += '?dir';

                        DOM.RESTfull.delete(lUrl, function(){
                            DOM.deleteCurrent(lCurrent);
                        });
                    }
                    else
                        DOM.promptDeleteSelected(lCurrent);
                    break;

                case Key.F1:
                    Util.exec(CloudCmd.Help);
                    DOM.preventDefault(pEvent);
                    break;

                case Key.F2:
                    DOM.renameCurrent(lCurrent);
                    break;

                case Key.F3:
                    Util.exec(CloudCmd.View);
                    DOM.preventDefault(pEvent);
                    break;

                case Key.F4:
                    Util.exec(CloudCmd.Edit);
                    DOM.preventDefault(pEvent);
                    break;

                case Key.F5:
                    DOM.copyCurrent(lCurrent);
                    DOM.preventDefault(pEvent);
                    break;

                case Key.F6:
                    DOM.moveCurrent(lCurrent);
                    DOM.preventDefault(pEvent);
                    break;

                case Key.F7:
                    DOM.promptNewDir();
                    break;

                case Key.F8:
                    DOM.promptDeleteSelected(lCurrent);
                    break;

                case Key.F:
                    DOM.promptDeleteCurrent(lCurrent);
                    break;

                case Key.F9:
                    Util.exec(CloudCmd.Menu);
                    DOM.preventDefault(pEvent);

                    break;

                case Key.TRA:
                    DOM.Images.showLoad({top: true});
                    Util.exec(CloudCmd.Console);

                    break;

                case Key.SPACE:
                    var lSelected   = DOM.isSelected(lCurrent),
                        lDir        = DOM.isCurrentIsDir(lCurrent),
                        lName       = DOM.getCurrentName(lCurrent);

                    if (!lDir || lName === '..')
                        lSelected = true;

                    Util.ifExec(lSelected, function() {
                        DOM.setSelectedFile(lCurrent);
                    }, function(pCallBack) {
                        DOM.loadCurrentSize(pCallBack, lCurrent);
                    });

                    DOM.preventDefault(pEvent);
                    break;

                /* навигация по таблице файлов  *
                 * если нажали клавишу вверх    *
                 * выделяем предыдущую строку   */
                case Key.UP:
                    if (lShift)
                         DOM.setSelectedFile(lCurrent);

                    DOM.setCurrentFile( lCurrent.previousSibling );
                    DOM.preventDefault( pEvent );
                    break;

                /* если нажали клавишу в низ - выделяем следующую строку    */
                case Key.DOWN:
                    if (lShift)
                         DOM.setSelectedFile(lCurrent);

                    DOM.setCurrentFile( lCurrent.nextSibling );
                    DOM.preventDefault( pEvent );
                    break;

                /* если нажали клавишу Home     *
                 * переходим к самому верхнему  *
                 * элементу                     */
                case Key.HOME:
                    DOM.setCurrentFile( lCurrent.parentElement.firstChild );
                    DOM.preventDefault(pEvent);
                    break;

                /* если нажали клавишу End выделяем последний элемент   */
                case Key.END:
                    DOM.setCurrentFile( lCurrent.parentElement.lastElementChild );
                    DOM.preventDefault( pEvent );
                    break;

                /* если нажали клавишу page down проматываем экран */
                case Key.PAGE_DOWN:
                    DOM.scrollByPages( DOM.getPanel(), 1 );

                    for (i = 0; i < 30; i++) {

                        if (!lCurrent.nextSibling)
                            break;

                        lCurrent = lCurrent.nextSibling;
                    }
                    DOM.setCurrentFile(lCurrent);
                    DOM.preventDefault(pEvent);
                    break;

                /* если нажали клавишу page up проматываем экран */
                case Key.PAGE_UP:
                    DOM.scrollByPages(DOM.getPanel(), -1);

                    var lC          = lCurrent,
                        tryCatch    = function(pCurrentFile){
                            Util.tryCatch(function(){
                                return pCurrentFile
                                        .previousSibling
                                            .previousSibling
                                                .previousSibling
                                                    .previousSibling;
                            });
                        };

                    for (i = 0; i < 30; i++){
                        if (!lC.previousSibling || tryCatch(lC) ) break;

                        lC = lC.previousSibling;
                    }

                    DOM.setCurrentFile(lC);
                    DOM.preventDefault(pEvent);
                    break;

                /* открываем папку*/
                case Key.ENTER:
                    if (DOM.isCurrentIsDir())
                        Util.exec( CloudCmd.loadDir() );
                    break;

                case Key.A:
                    if (pEvent.ctrlKey) {
                        var lParent = lCurrent.parentElement,
                            lNodes = lParent.childNodes;

                        /* not path and fm_header */
                        for (i = 2, n = lNodes.length; i < n; i++)
                            DOM.setSelectedFile( lNodes[i] );

                        DOM.preventDefault(pEvent);
                    }
                    break;

                /*
                 * обновляем страницу,
                 * загружаем содержимое каталога
                 * при этом данные берём всегда с
                 * сервера, а не из кэша
                 * (обновляем кэш)
                 */
                case Key.R:
                    if (lCtrl) {
                        Util.log('<ctrl>+r pressed\n' +
                                'reloading page...\n' +
                                'press <alt>+q to remove all key-handlers');

                        CloudCmd.refresh();
                        DOM.preventDefault(pEvent);
                    }
                    break;

                /* чистим кэш */
                case Key.D:
                    if (lCtrl) {
                        Util.log('<ctrl>+d pressed\n'  +
                            'clearing cache...\n' +
                            'press <alt>+q to remove all key-handlers');

                        DOM.Cache.clear();
                        DOM.preventDefault();
                    }
                    break;

                /* убираем все обработчики нажатий клавиш */
                case Key.Q:
                    if (lAlt) {
                        Util.log('<alt>+q pressed\n'                         +
                                    '<ctrl>+r reload key-handerl - removed'     +
                                    '<ctrl>+s clear cache key-handler - removed'+
                                    'press <alt>+s to to set them');

                        Binded = false;
                        DOM.preventDefault(pEvent);
                    }
                    break;
                }
            }

            /* устанавливаем все обработчики
             * нажатий клавиш
             */
            else if(lKeyCode === Key.S && lAlt){
                /* обрабатываем нажатия на клавиши*/
                Binded = true;
                Util.log('<alt>+s pressed                           \n' +
                            '<ctrl>+r reload key-handerl - set      \n' +
                            '<ctrl>+s clear cache key-handler - set \n' +
                            'press <alt>+q to remove them');

                DOM.preventDefault(pEvent);
            }
        }
    }

})(CloudCmd, Util, DOM);<commit_msg>chore(key) "){" -> ") {"<commit_after>var CloudCmd, Util, DOM;
(function(CloudCmd, Util, DOM) {
    'use strict';

    var KEY = {
        TAB         : 9,
        ENTER       : 13,
        ESC         : 27,

        SPACE       : 32,
        PAGE_UP     : 33,
        PAGE_DOWN   : 34,
        END         : 35,
        HOME        : 36,
        UP          : 38,
        DOWN        : 40,

        INSERT      : 45,
        DELETE      : 46,

        A           : 65,

        D           : 68,

        G           : 71,

        O           : 79,
        Q           : 81,
        R           : 82,
        S           : 83,
        T           : 84,

        F1          : 112,
        F2          : 113,
        F3          : 114,
        F4          : 115,
        F5          : 116,
        F6          : 117,
        F7          : 118,
        F8          : 119,
        F9          : 120,

        TRA         : 192 /* Typewritten Reverse Apostrophe (`) */
    };

    KeyProto.prototype = KEY;
    CloudCmd.Key = new KeyProto(CloudCmd, Util, DOM);

    function KeyProto(CloudCmd, Util, DOM) {
        var Key = this,
            Binded,
            lTabPanel   = {
                left        : 0,
                right       : 0

            };

        this.isBind     = function() {return Binded;};

        this.setBind    = function() {Binded = true;};

        this.unsetBind  = function() {Binded = false;};

        this.bind   = function() {
            DOM.Events.addKey(listener);

            /* клавиши назначены*/
            Binded = true;
        };

        function listener(pEvent) {
            /* получаем выдленный файл*/
            var i, n, lCurrent  = DOM.getCurrentFile(),
                lKeyCode        = pEvent.keyCode,
                lShift          = pEvent.shiftKey,
                lAlt            = pEvent.altKey,
                lCtrl           = pEvent.ctrlKey;
            /* если клавиши можно обрабатывать*/
            if (Binded) {
                switch (lKeyCode) {
                case Key.TAB:
                    /* changing parent panel of curent-file */
                    var lPanel = DOM.getPanel(),
                        lId = lPanel.id;

                    lTabPanel[lId] = lCurrent;

                    lPanel = DOM.getPanel({active:false});
                    lId = lPanel.id;

                    if (lTabPanel[lId])
                        DOM.setCurrentFile(lTabPanel[lId]);
                    else{
                        var lFirstFileOnList = DOM.getByTag('li', lPanel)[2];

                        DOM.setCurrentFile(lFirstFileOnList);
                    }

                    DOM.preventDefault(pEvent);//запрет на дальнейшее действие
                    break;

                case Key.INSERT:
                    DOM.setSelectedFile( lCurrent );
                    DOM.setCurrentFile( lCurrent.nextSibling );
                    break;

                case Key.DELETE:
                    if (lShift) {
                        var lUrl = DOM.getCurrentPath(lCurrent);

                        if ( DOM.isCurrentIsDir(lCurrent) )
                            lUrl += '?dir';

                        DOM.RESTfull.delete(lUrl, function() {
                            DOM.deleteCurrent(lCurrent);
                        });
                    }
                    else
                        DOM.promptDeleteSelected(lCurrent);
                    break;

                case Key.F1:
                    Util.exec(CloudCmd.Help);
                    DOM.preventDefault(pEvent);
                    break;

                case Key.F2:
                    DOM.renameCurrent(lCurrent);
                    break;

                case Key.F3:
                    Util.exec(CloudCmd.View);
                    DOM.preventDefault(pEvent);
                    break;

                case Key.F4:
                    Util.exec(CloudCmd.Edit);
                    DOM.preventDefault(pEvent);
                    break;

                case Key.F5:
                    DOM.copyCurrent(lCurrent);
                    DOM.preventDefault(pEvent);
                    break;

                case Key.F6:
                    DOM.moveCurrent(lCurrent);
                    DOM.preventDefault(pEvent);
                    break;

                case Key.F7:
                    DOM.promptNewDir();
                    break;

                case Key.F8:
                    DOM.promptDeleteSelected(lCurrent);
                    break;

                case Key.F:
                    DOM.promptDeleteCurrent(lCurrent);
                    break;

                case Key.F9:
                    Util.exec(CloudCmd.Menu);
                    DOM.preventDefault(pEvent);

                    break;

                case Key.TRA:
                    DOM.Images.showLoad({top: true});
                    Util.exec(CloudCmd.Console);

                    break;

                case Key.SPACE:
                    var lSelected   = DOM.isSelected(lCurrent),
                        lDir        = DOM.isCurrentIsDir(lCurrent),
                        lName       = DOM.getCurrentName(lCurrent);

                    if (!lDir || lName === '..')
                        lSelected = true;

                    Util.ifExec(lSelected, function() {
                        DOM.setSelectedFile(lCurrent);
                    }, function(pCallBack) {
                        DOM.loadCurrentSize(pCallBack, lCurrent);
                    });

                    DOM.preventDefault(pEvent);
                    break;

                /* навигация по таблице файлов  *
                 * если нажали клавишу вверх    *
                 * выделяем предыдущую строку   */
                case Key.UP:
                    if (lShift)
                         DOM.setSelectedFile(lCurrent);

                    DOM.setCurrentFile( lCurrent.previousSibling );
                    DOM.preventDefault( pEvent );
                    break;

                /* если нажали клавишу в низ - выделяем следующую строку    */
                case Key.DOWN:
                    if (lShift)
                         DOM.setSelectedFile(lCurrent);

                    DOM.setCurrentFile( lCurrent.nextSibling );
                    DOM.preventDefault( pEvent );
                    break;

                /* если нажали клавишу Home     *
                 * переходим к самому верхнему  *
                 * элементу                     */
                case Key.HOME:
                    DOM.setCurrentFile( lCurrent.parentElement.firstChild );
                    DOM.preventDefault(pEvent);
                    break;

                /* если нажали клавишу End выделяем последний элемент   */
                case Key.END:
                    DOM.setCurrentFile( lCurrent.parentElement.lastElementChild );
                    DOM.preventDefault( pEvent );
                    break;

                /* если нажали клавишу page down проматываем экран */
                case Key.PAGE_DOWN:
                    DOM.scrollByPages( DOM.getPanel(), 1 );

                    for (i = 0; i < 30; i++) {

                        if (!lCurrent.nextSibling)
                            break;

                        lCurrent = lCurrent.nextSibling;
                    }
                    DOM.setCurrentFile(lCurrent);
                    DOM.preventDefault(pEvent);
                    break;

                /* если нажали клавишу page up проматываем экран */
                case Key.PAGE_UP:
                    DOM.scrollByPages(DOM.getPanel(), -1);

                    var lC          = lCurrent,
                        tryCatch    = function(pCurrentFile) {
                            Util.tryCatch(function() {
                                return pCurrentFile
                                        .previousSibling
                                            .previousSibling
                                                .previousSibling
                                                    .previousSibling;
                            });
                        };

                    for (i = 0; i < 30; i++) {
                        if (!lC.previousSibling || tryCatch(lC) ) break;

                        lC = lC.previousSibling;
                    }

                    DOM.setCurrentFile(lC);
                    DOM.preventDefault(pEvent);
                    break;

                /* открываем папку*/
                case Key.ENTER:
                    if (DOM.isCurrentIsDir())
                        Util.exec( CloudCmd.loadDir() );
                    break;

                case Key.A:
                    if (pEvent.ctrlKey) {
                        var lParent = lCurrent.parentElement,
                            lNodes = lParent.childNodes;

                        /* not path and fm_header */
                        for (i = 2, n = lNodes.length; i < n; i++)
                            DOM.setSelectedFile( lNodes[i] );

                        DOM.preventDefault(pEvent);
                    }
                    break;

                /*
                 * обновляем страницу,
                 * загружаем содержимое каталога
                 * при этом данные берём всегда с
                 * сервера, а не из кэша
                 * (обновляем кэш)
                 */
                case Key.R:
                    if (lCtrl) {
                        Util.log('<ctrl>+r pressed\n' +
                                'reloading page...\n' +
                                'press <alt>+q to remove all key-handlers');

                        CloudCmd.refresh();
                        DOM.preventDefault(pEvent);
                    }
                    break;

                /* чистим кэш */
                case Key.D:
                    if (lCtrl) {
                        Util.log('<ctrl>+d pressed\n'  +
                            'clearing cache...\n' +
                            'press <alt>+q to remove all key-handlers');

                        DOM.Cache.clear();
                        DOM.preventDefault();
                    }
                    break;

                /* убираем все обработчики нажатий клавиш */
                case Key.Q:
                    if (lAlt) {
                        Util.log('<alt>+q pressed\n'                         +
                                    '<ctrl>+r reload key-handerl - removed'     +
                                    '<ctrl>+s clear cache key-handler - removed'+
                                    'press <alt>+s to to set them');

                        Binded = false;
                        DOM.preventDefault(pEvent);
                    }
                    break;
                }
            }

            /* устанавливаем все обработчики
             * нажатий клавиш
             */
            else if (lKeyCode === Key.S && lAlt) {
                /* обрабатываем нажатия на клавиши*/
                Binded = true;
                Util.log('<alt>+s pressed                           \n' +
                            '<ctrl>+r reload key-handerl - set      \n' +
                            '<ctrl>+s clear cache key-handler - set \n' +
                            'press <alt>+q to remove them');

                DOM.preventDefault(pEvent);
            }
        }
    }

})(CloudCmd, Util, DOM);<|endoftext|>