<commit_before>import closestHead from './utils/closestHead';
import intersectOrb from './utils/intersectcenter';
import viewer from './viewer';
import InstancedItem from './instanced-item';
import Room from './room';
import layout from './room/layout';
import settings from './settings';
import controllers from './controllers';
import audio from './audio';

import analytics from './utils/analytics';

export default function create({ rooms, orb, offset = 0 }) {
  const { holeHeight } = settings;
  let pointerX;
  let pointerY;
  let hoverPerformance;
  let hoverOrb;

  const onMouseMove = ({ clientX, clientY }) => {
    if (viewer.vrEffect.isPresenting) return;
    pointerX = clientX;
    pointerY = clientY;
  };

  const onMouseDown = ({ clientX, clientY, touches }) => {
    if (viewer.vrEffect.isPresenting) return;
    let x = clientX;
    let y = clientY;
    if (touches && touches.length > 0) {
      x = touches[0].pageX;
      y = touches[0].pageY;
    }

    hoverOrb = intersectOrb(x, y);
    hoverPerformance = closestHead(x, y, rooms);

    if (hoverPerformance || hoverOrb) {
      viewer.switchCamera('default');
      InstancedItem.group.add(viewer.camera);
      if (hoverPerformance) analytics.recordHeadSelectStart();
      else if (hoverOrb) analytics.recordOrbSelectStart();
    }
  };

  const onMouseUp = () => {
    if (viewer.vrEffect.isPresenting) return;
    if (hoverPerformance) analytics.recordHeadSelectStop();
    else if (hoverOrb) analytics.recordOrbSelectStop();
    hoverPerformance = null;
    hoverOrb = false;
    viewer.switchCamera('orthographic');
  };

  const clearHighlights = () => {
    hoverPerformance = null;
    hoverOrb = false;
    orb.unhighlight();
    Room.setHighlight();
  };

  // Whenever the audio loops, move to the next head if necessary:
  const onLoop = (loopIndex) => {
    if (!hoverPerformance) return;
    const [index, headIndex] = hoverPerformance;
    if (
      (index % 2 === 0 && loopIndex % 2 === 1) ||
      (index % 2 === 1 && loopIndex % 2 === 0)
    ) {
      hoverPerformance[1] = (headIndex + 1) % rooms[index].frame.count;
      Room.setHighlight(hoverPerformance);
    }
  };

  window.addEventListener('vrdisplaypresentchange', clearHighlights, false);

  audio.on('loop', onLoop);

  const POV = {
    update: (progress = 0, fixedControllers = false) => {
      if (!viewer.vrEffect.isPresenting) {
        if (intersectOrb(pointerX, pointerY)) {
          orb.highlight();
          Room.setHighlight();
        } else {
          orb.unhighlight();
          if (!hoverPerformance && !hoverOrb) {
            Room.setHighlight(
              closestHead(
                pointerX,
                pointerY,
                rooms
              )
            );
          }
        }
      }

      const position = layout.getPosition(progress + offset + 0.5);
      position.y += holeHeight;
      position.z *= -1;

      if (fixedControllers) {
        controllers.fixToPosition(position);
      }
      viewer.camera.position.copy(position);
      if (hoverOrb) {
        // Move camera into orb:
        viewer.camera.position.z *= -1;
        viewer.camera.rotation.set(0, Math.PI, 0);
      } else if (hoverPerformance) {
        // Move camera into head of performance:
        const [index, headIndex] = hoverPerformance;
        rooms[index].transformToHead(viewer.camera, headIndex);
      }
    },

    setupInput: () => {
      window.addEventListener('mousemove', onMouseMove);
      window.addEventListener('mousedown', onMouseDown);
      window.addEventListener('mouseup', onMouseUp);
      window.addEventListener('touchstart', onMouseDown, false);
      window.addEventListener('touchend', onMouseUp, false);
    },

    removeInput: () => {
      window.removeEventListener('mousemove', onMouseMove);
      window.removeEventListener('mousedown', onMouseDown);
      window.removeEventListener('mouseup', onMouseUp);
      window.removeEventListener('vrdisplaypresentchange', clearHighlights);
      audio.off('loop', onLoop);
    },

    clearHighlights,
  };

  return POV;
}
<commit_msg>fixes #343<commit_after>import closestHead from './utils/closestHead';
import intersectOrb from './utils/intersectcenter';
import viewer from './viewer';
import InstancedItem from './instanced-item';
import Room from './room';
import layout from './room/layout';
import settings from './settings';
import controllers from './controllers';
import audio from './audio';

import analytics from './utils/analytics';

export default function create({ rooms, orb, offset = 0 }) {
  const { holeHeight } = settings;
  let pointerX;
  let pointerY;
  let hoverPerformance;
  let hoverOrb;

  const onMouseMove = ({ clientX, clientY }) => {
    if (viewer.vrEffect.isPresenting) return;
    pointerX = clientX;
    pointerY = clientY;
  };

  const onMouseDown = ({ clientX, clientY, touches }) => {
    if (viewer.vrEffect.isPresenting) return;
    if (hoverPerformance || hoverOrb) {
      return;
    }

    let x = clientX;
    let y = clientY;
    if (touches && touches.length > 0) {
      x = touches[0].pageX;
      y = touches[0].pageY;
    }

    hoverOrb = intersectOrb(x, y);
    hoverPerformance = closestHead(x, y, rooms);

    if (hoverPerformance || hoverOrb) {
      viewer.switchCamera('default');
      InstancedItem.group.add(viewer.camera);
      if (hoverPerformance) analytics.recordHeadSelectStart();
      else if (hoverOrb) analytics.recordOrbSelectStart();
    }
  };

  const onMouseUp = ({ touches }) => {
    if (viewer.vrEffect.isPresenting) return;

    if (touches && touches.length >= 1) {
      return;
    }

    if (hoverPerformance) analytics.recordHeadSelectStop();
    else if (hoverOrb) analytics.recordOrbSelectStop();
    hoverPerformance = null;
    hoverOrb = false;
    viewer.switchCamera('orthographic');
  };

  const clearHighlights = () => {
    hoverPerformance = null;
    hoverOrb = false;
    orb.unhighlight();
    Room.setHighlight();
  };

  // Whenever the audio loops, move to the next head if necessary:
  const onLoop = (loopIndex) => {
    if (!hoverPerformance) return;
    const [index, headIndex] = hoverPerformance;
    if (
      (index % 2 === 0 && loopIndex % 2 === 1) ||
      (index % 2 === 1 && loopIndex % 2 === 0)
    ) {
      hoverPerformance[1] = (headIndex + 1) % rooms[index].frame.count;
      Room.setHighlight(hoverPerformance);
    }
  };

  window.addEventListener('vrdisplaypresentchange', clearHighlights, false);

  audio.on('loop', onLoop);

  const POV = {
    update: (progress = 0, fixedControllers = false) => {
      if (!viewer.vrEffect.isPresenting) {
        if (intersectOrb(pointerX, pointerY)) {
          orb.highlight();
          Room.setHighlight();
        } else {
          orb.unhighlight();
          if (!hoverPerformance && !hoverOrb) {
            Room.setHighlight(
              closestHead(
                pointerX,
                pointerY,
                rooms
              )
            );
          }
        }
      }

      const position = layout.getPosition(progress + offset + 0.5);
      position.y += holeHeight;
      position.z *= -1;

      if (fixedControllers) {
        controllers.fixToPosition(position);
      }
      viewer.camera.position.copy(position);
      if (hoverOrb) {
        // Move camera into orb:
        viewer.camera.position.z *= -1;
        viewer.camera.rotation.set(0, Math.PI, 0);
      } else if (hoverPerformance) {
        // Move camera into head of performance:
        const [index, headIndex] = hoverPerformance;
        rooms[index].transformToHead(viewer.camera, headIndex);
      }
    },

    setupInput: () => {
      window.addEventListener('mousemove', onMouseMove);
      window.addEventListener('mousedown', onMouseDown);
      window.addEventListener('mouseup', onMouseUp);
      window.addEventListener('touchstart', onMouseDown, false);
      window.addEventListener('touchend', onMouseUp, false);
    },

    removeInput: () => {
      window.removeEventListener('mousemove', onMouseMove);
      window.removeEventListener('mousedown', onMouseDown);
      window.removeEventListener('mouseup', onMouseUp);
      window.removeEventListener('vrdisplaypresentchange', clearHighlights);
      audio.off('loop', onLoop);
    },

    clearHighlights,
  };

  return POV;
}
<|endoftext|>


<commit_before>/*
 * site.js: An entire blacksmith site with layouts, partials, pages, content, and metadata
 *
 * (C) 2011, Nodejitsu Inc.
 *
 */

var events = require('events'),
    fs = require('fs'),
    path = require('path'),
    timespan = require('timespan'),
    utile = require('flatiron').common,
    async = utile.async,
    cpr = utile.cpr,
    inflect = utile.inflect,
    mkdirp = utile.mkdirp,
    common = require('./common'),
    content = require('./content'),
    Page = require('./page'),
    Feed = require('./rss'),
    feed = new Feed();

//
// ### function Site (options)
// #### @options {Object} Options for rendering this site.
// ####   @dir   {string} Root directory for this site
// ####   @since {string} Only pages modified after this date will be written to public
//
// Constructor function for the Site object representing an entire blacksmith
// site with layouts, partials, pages, content, and metadata.
//
var Site = module.exports = function (options) {
  if (!options || (typeof options !== 'string' && !options.dir)) {
    throw new Error('Directory of the site is required');
  }
  else if (typeof options === 'string') {
    options = { dir: options };
  }

  events.EventEmitter.call(this);

  this.dir   = options.dir;
  this.since = options.since
    ? timespan.parseDate(options.since)
    : timespan.parseDate('NOW-10YEARS');
};

//
// Inherit from `events.EventEmitter`.
//
utile.inherits(Site, events.EventEmitter);

//
// ### function load (callback)
// #### @callback {function} Continuation to respond to when complete.
//
// Loads all relevant files for this site using the following workflow:
//
// 1. Attempt to read `.blacksmith`
// 2. Read all layouts.
// 3. Read all partials.
// 4. Read all pages.
//
Site.prototype.load = function (callback) {
  var dir = path.normalize(this.dir),
      self = this;

  //
  // Load all site files.
  //
  this.emit('load:start', this.dir);
  common.loadSite(dir, function (err, results) {
    if (err) {
      return callback(err);
    }

    //
    // Merge results back onto this instance
    //
    self.layout = results.options.layout || self.layout || 'default';
    ['layouts', 'pages', 'partials'].forEach(function (key) {
      //
      // If there are JSON page files in `/[component]` and `{ [component]: {} }`
      // are specified in `.blacksmith` merge each page options.
      //
      self[key] = results.options[key];
      if (results[key] && self[key]) {
        Object.keys(results[key]).forEach(function (item) {
          self[key][item] = utile.mixin(
            self[key][item] || {},
            results[key][item]
          );
        });
      }
      else {
        self[key] = results[key] || {};
      }
    });

    self.html       = results.html;
    self.references = results.references || {};

    self.emit('load:end', self.dir);

    callback(null, {
      dir:        self.dir,
      html:       self.html,
      pages:      self.pages,
      layout:     self.layout,
      layouts:    self.layouts,
      partials:   self.partials,
      references: self.references
    });
  });
};

//
// ### function render (callback)
// #### @callback {function} Continuation to respond to when complete.
//
// Renders everything associated with this site instance using the following workflow:
//
// 1. Load all HTML and metadata
// 2. Render all content in `/content`.
// 3. Render all pages in `this.pages`.
// 4. Write all content to disk.
//
Site.prototype.render = function (callback) {
  var dir = path.normalize(this.dir),
      self = this;

  async.waterfall([
    //
    // 1. Load all HTML and metadata in:
    //   * /.blacksmith
    //   * /layouts
    //   * /pages
    //   * /partials
    //
    function loadSite(next) {
      self.load(function (err) {
        return err ? next(err) : next();
      });
    },
    //
    // 2. Render all content in `/content`. For each file:
    //   a. Parse the Markdown using `marked`
    //   b. Read all metadata into a JSON object, looking up
    //      references as necessary (e.g. author)
    //   c. Generate HTML from Markdown
    //
    function renderContent(next) {
      self.renderContent(path.join(dir, 'content'), next);
    },
    //
    // 3. Render all pages in `self.pages`
    //
    function renderPages(content, next) {
      var rendered = {};

      Object.keys(self.pages).forEach(
        function renderPage(name) {
          //
          // Create a page with all relevant rendering metadata
          //
          // TODO: Support shared layout templates!!!
          //
          var page = new Page({
            name:       name,
            dir:        self.dir,
            layout:     self.pages[name].layout || self.layout,
            options:    self.pages[name],
            html:       self.html,
            content:    content,
            references: self.references,
            partials:   self.partials
          });

          //
          // Fully render all files associated with this page
          //
          //
          // If the `content` has a pluralized key to the page `name`,
          // then mixin the rendered page results. e.g.
          //
          // name = 'post'
          // content = { posts: { ... } }
          //
          // Conversely if no such convention exists just set the key
          // `name` in the fully rendered page to the results. e.g.
          //
          // name = 'index'
          // content = { posts: { ... } }
          //
          if (typeof content[inflect.pluralize(name)] === 'object') {
            utile.mixin(rendered, page.renderAll());
          }
          else {
            rendered[name] = page.renderAll();
          }
        }
      );

      next(null, rendered);
    },
    //
    // 4. Write all rendered content to /public
    //
    function writeFiles(rendered, next) {
      self.writeFiles(rendered, next);
    }
  ], function (err, _) {
    return err ? callback(err) : callback();
  });

  return this;
};

//
// ### function renderContent (dir, callback)
// #### @dir {string} Directory to render content files in.
// #### @callback {function} Continuation to respond to when complete.
//
// Renders all content files and directories in the specified `dir` searching
// recursively if necessary. The data structure returned is relatively simple:
//
// {
//   posts: {
//     "a-post.md": { _content: { html: "..", markdown: "..", metadata: { ... } } }
//     "a-folder": {
//       _content: {
//         { html: "..", markdown: "..", metadata: { ... } }
//       },
//       _files: ['file1.js', 'img1.png' ...]
//     }
//   }
//   ...
// }
//
Site.prototype.renderContent = function (dir, callback) {
  var rendered = {},
      self = this;

  //
  // List files in the directory.
  //
  fs.readdir(dir, function (err, files) {
    if (err) {
      return callback(err);
    }

    var supporting = [],
        markdown = [],
        dirs = [];

    //
    // Separate all markdown (.md, .markdown), supporting (non-markdown),
    // and directories ignoring all dotfiles.
    //
    files = files.forEach(function (file) {
      if (file[0] === '.') {
        return;
      }

      var ext = path.extname(file);

      if (ext === '.md' || ext === '.markdown') {
        markdown.push(file);
      }
      else if (ext.length) {
        supporting.push(file);
      }
      else {
        dirs.push(file);
      }
    });

    if (!supporting.length && !markdown.length && !dirs.length) {
      return callback(new Error('No files found to render'));
    }

    //
    // In parallel over all files and directories found with a concurrency
    // of 10.
    //
    async.parallel([
      //
      // * For Markdown content render them and place the results
      //   in the content hierarchy.
      //
      function renderMarkdowns(done) {
        async.forEachLimit(
          markdown,
          10,
          function renderMarkdown(file, next) {
            var fullpath = path.join(dir, file),
                ext = path.extname(file),
                name = path.basename(file, ext);

            fs.stat(fullpath, function (err, stats) {
              if (err) {
                return next(err);
              }

              content.render({
                source: fullpath,
                marked: self.marked,
                files: supporting,
                dir: dir
              }, function onMarkdown(err, results) {
                if (err) {
                  return next(err);
                }

                rendered[name] = {
                  _content: results,
                  _date: results.metadata.date
                    ? new Date(results.metadata.date)
                    : new Date(stats.ctime)
                };

                common.addDetails(rendered[name]._content, {
                  dir: path.join(self.dir, 'content'),
                  fullpath: fullpath,
                  files: supporting,
                  stats: stats
                });

                // create an item in the RSS feed
                feed.item({
                  title: results.metadata['page-details'].title,
                  description: results.truncated.html,
                  url: results.metadata['page-details'].href,
                  author: results.metadata.author,
                  date: rendered[name]._date
                })

                if (name === 'index') {
                  //
                  // Hoist the date associated with `index.md` files to
                  // the directory itself.
                  //
                  rendered._date = rendered[name]._date;
                }

                next();
              });
            });
          },
          done
        );
      },
      //
      // * For directories continue to search recursively for more files.
      //
      function renderDirs(done) {
        async.forEachLimit(
          dirs,
          10,
          function renderDir(file, next) {
            var fullpath = path.join(dir, file);

            self.renderContent(fullpath, function onDir(err, results) {
              if (err) {
                return next(err);
              }

              rendered[file] = results;
              next();
            });
          },
          done
        );
      }
    ], function (err) {
      if (err) {
        return callback(err);
      }

      //
      // If there are any supporting files set them to the
      // `_files` property on the rendered content.
      //
      if (supporting.length) {
        if (rendered.index) {
          rendered.index._files = supporting;
        }
        else {
          rendered._files = supporting;
        }
      }

      callback(null, rendered);
    });
  });
};

//
// ### function writeFiles (rendered, callback)
// #### @rendered {Object} Fully rendered site structure.
// #### @callback {function} Continuation to respond to when complete.
//
// Writes all files for the `rendered` content associated with this instance.
//
Site.prototype.writeFiles = function (rendered, callback) {
  var publicDir = path.join(this.dir, 'public'),
      written = {},
      self = this;

  function onMkdir(next) {
    return function (err) {
      return err && err.code !== 'EEXIST'
        ? next(err)
        : next();
    };
  }

  async.series([
    //
    // 1. Make `/path/to/site/public`
    //
    function mkdir(next) {
      fs.mkdir(publicDir, onMkdir(next));
    },
    function writeRSS(next){
      fs.writeFile(path.join(publicDir, 'feed.xml'), feed.xml(), 'utf8', next);
    },
    //
    // 2. Write the HTML for all keys and all associated
    //    files into a matching directory structure.
    //
    function writeAll(done) {
      async.forEachSeries(
        self.targetFiles(rendered),
        function write(target, next) {
          //
          // There are three operations that take place here:
          //
          if (target.contents === 'directory') {
            //
            // 1. Create directory: `{ fullpath: /path/to/public/file.html, contents: "directory" }`
            //
            fs.mkdir(target.fullpath, onMkdir(next));
          }
          else if (target.contents) {
            //
            // 2. Write html `{ fullpath: /path/to/public/file.html, contents: "..." }`
            //
            fs.writeFile(target.fullpath, target.contents, 'utf8', next);
          }
          else if (target.source) {
            //
            // 3. Copy supporting files `{ fullpath: /path/to/public/file.html, source: "..." }`
            //
            cpr(target.source, target.fullpath, function (err) {
              return err ? next(err) : next();
            });
          }
        },
        function (err) {
          return err instanceof Error
            ? done(err)
            : done();
        }
      );
    }
  ], function (err) {
    return err ? callback(err) : callback (null, written);
  });
};

//
// ### function targetFiles (rendered)
// #### @rendered {Object} Fully rendered site structure.
//
// Returns a list of objects representing:
// 1. Directories to create in /public
// 2. Write rendered HTML content
// 3. Copy supporting files
//
Site.prototype.targetFiles = function (rendered, pathTo) {
  var contentDir = path.join(this.dir, 'content'),
      publicDir = path.join(this.dir, 'public'),
      self = this;

  pathTo = pathTo || [];

  return Object.keys(rendered).reduce(function (all, key) {
    //
    // The set of child keys under this `key` in the rendering
    // hierarchy.
    //
    var fullpath = [publicDir].concat(pathTo),
        childKeys;

    childKeys = Object.keys(rendered[key]).filter(function (childKey) {
      return childKey[0] !== '_';
    });

    if (!rendered[key]._content) {
      //
      // 1. If there is no `_content` property then this is
      //    a directory that needs creation
      //
      all = all.concat([{
        fullpath: path.join.apply(path, fullpath.concat([key])),
        contents: 'directory'
      }]);
    }
    else if (!rendered[key]._content.date
      || self.since < rendered[key]._content.date) {
      //
      // 2. If there is a `_content` property then write
      //    a file with the HTML contents.
      //
      // TODO: We should have some indicator if a page in the
      // content tree is compiled from other pages or not.
      //
      all = all.concat({
        fullpath: path.join.apply(path, fullpath.concat([key + '.html'])),
        contents: rendered[key]._content.html
      });

      //
      // 3. If this file has any supporting files then copy
      //    those as well.
      //
      if (rendered[key]._content.files) {
        all = all.concat(rendered[key]._content.files.map(function (file) {
          return {
            fullpath: path.join.apply(path, fullpath.concat([path.basename(file)])),
            source: path.join(contentDir, rendered[key].dir || '', file)
          };
        }));
      }
    }

    //
    // 4. If there are any `childKeys` then get the files that should
    //    be created from them
    //
    if (childKeys.length) {
      all = all.concat(self.targetFiles(rendered[key], pathTo.concat([key])));
    }

    return all;
  }, []);
};
<commit_msg>hardcode site url… b/c I’m lazy<commit_after>/*
 * site.js: An entire blacksmith site with layouts, partials, pages, content, and metadata
 *
 * (C) 2011, Nodejitsu Inc.
 *
 */

var events = require('events'),
    fs = require('fs'),
    path = require('path'),
    timespan = require('timespan'),
    utile = require('flatiron').common,
    async = utile.async,
    cpr = utile.cpr,
    inflect = utile.inflect,
    mkdirp = utile.mkdirp,
    common = require('./common'),
    content = require('./content'),
    Page = require('./page'),
    Feed = require('./rss'),
    , feed = new Feed({
      title: 'Joey Baker'
      , 'site_url': 'https://byjoeybaker.com'
    , });

//
// ### function Site (options)
// #### @options {Object} Options for rendering this site.
// ####   @dir   {string} Root directory for this site
// ####   @since {string} Only pages modified after this date will be written to public
//
// Constructor function for the Site object representing an entire blacksmith
// site with layouts, partials, pages, content, and metadata.
//
var Site = module.exports = function (options) {
  if (!options || (typeof options !== 'string' && !options.dir)) {
    throw new Error('Directory of the site is required');
  }
  else if (typeof options === 'string') {
    options = { dir: options };
  }

  events.EventEmitter.call(this);

  this.dir   = options.dir;
  this.since = options.since
    ? timespan.parseDate(options.since)
    : timespan.parseDate('NOW-10YEARS');
};

//
// Inherit from `events.EventEmitter`.
//
utile.inherits(Site, events.EventEmitter);

//
// ### function load (callback)
// #### @callback {function} Continuation to respond to when complete.
//
// Loads all relevant files for this site using the following workflow:
//
// 1. Attempt to read `.blacksmith`
// 2. Read all layouts.
// 3. Read all partials.
// 4. Read all pages.
//
Site.prototype.load = function (callback) {
  var dir = path.normalize(this.dir),
      self = this;

  //
  // Load all site files.
  //
  this.emit('load:start', this.dir);
  common.loadSite(dir, function (err, results) {
    if (err) {
      return callback(err);
    }

    //
    // Merge results back onto this instance
    //
    self.layout = results.options.layout || self.layout || 'default';
    ['layouts', 'pages', 'partials'].forEach(function (key) {
      //
      // If there are JSON page files in `/[component]` and `{ [component]: {} }`
      // are specified in `.blacksmith` merge each page options.
      //
      self[key] = results.options[key];
      if (results[key] && self[key]) {
        Object.keys(results[key]).forEach(function (item) {
          self[key][item] = utile.mixin(
            self[key][item] || {},
            results[key][item]
          );
        });
      }
      else {
        self[key] = results[key] || {};
      }
    });

    self.html       = results.html;
    self.references = results.references || {};

    self.emit('load:end', self.dir);

    callback(null, {
      dir:        self.dir,
      html:       self.html,
      pages:      self.pages,
      layout:     self.layout,
      layouts:    self.layouts,
      partials:   self.partials,
      references: self.references
    });
  });
};

//
// ### function render (callback)
// #### @callback {function} Continuation to respond to when complete.
//
// Renders everything associated with this site instance using the following workflow:
//
// 1. Load all HTML and metadata
// 2. Render all content in `/content`.
// 3. Render all pages in `this.pages`.
// 4. Write all content to disk.
//
Site.prototype.render = function (callback) {
  var dir = path.normalize(this.dir),
      self = this;

  async.waterfall([
    //
    // 1. Load all HTML and metadata in:
    //   * /.blacksmith
    //   * /layouts
    //   * /pages
    //   * /partials
    //
    function loadSite(next) {
      self.load(function (err) {
        return err ? next(err) : next();
      });
    },
    //
    // 2. Render all content in `/content`. For each file:
    //   a. Parse the Markdown using `marked`
    //   b. Read all metadata into a JSON object, looking up
    //      references as necessary (e.g. author)
    //   c. Generate HTML from Markdown
    //
    function renderContent(next) {
      self.renderContent(path.join(dir, 'content'), next);
    },
    //
    // 3. Render all pages in `self.pages`
    //
    function renderPages(content, next) {
      var rendered = {};

      Object.keys(self.pages).forEach(
        function renderPage(name) {
          //
          // Create a page with all relevant rendering metadata
          //
          // TODO: Support shared layout templates!!!
          //
          var page = new Page({
            name:       name,
            dir:        self.dir,
            layout:     self.pages[name].layout || self.layout,
            options:    self.pages[name],
            html:       self.html,
            content:    content,
            references: self.references,
            partials:   self.partials
          });

          //
          // Fully render all files associated with this page
          //
          //
          // If the `content` has a pluralized key to the page `name`,
          // then mixin the rendered page results. e.g.
          //
          // name = 'post'
          // content = { posts: { ... } }
          //
          // Conversely if no such convention exists just set the key
          // `name` in the fully rendered page to the results. e.g.
          //
          // name = 'index'
          // content = { posts: { ... } }
          //
          if (typeof content[inflect.pluralize(name)] === 'object') {
            utile.mixin(rendered, page.renderAll());
          }
          else {
            rendered[name] = page.renderAll();
          }
        }
      );

      next(null, rendered);
    },
    //
    // 4. Write all rendered content to /public
    //
    function writeFiles(rendered, next) {
      self.writeFiles(rendered, next);
    }
  ], function (err, _) {
    return err ? callback(err) : callback();
  });

  return this;
};

//
// ### function renderContent (dir, callback)
// #### @dir {string} Directory to render content files in.
// #### @callback {function} Continuation to respond to when complete.
//
// Renders all content files and directories in the specified `dir` searching
// recursively if necessary. The data structure returned is relatively simple:
//
// {
//   posts: {
//     "a-post.md": { _content: { html: "..", markdown: "..", metadata: { ... } } }
//     "a-folder": {
//       _content: {
//         { html: "..", markdown: "..", metadata: { ... } }
//       },
//       _files: ['file1.js', 'img1.png' ...]
//     }
//   }
//   ...
// }
//
Site.prototype.renderContent = function (dir, callback) {
  var rendered = {},
      self = this;

  //
  // List files in the directory.
  //
  fs.readdir(dir, function (err, files) {
    if (err) {
      return callback(err);
    }

    var supporting = [],
        markdown = [],
        dirs = [];

    //
    // Separate all markdown (.md, .markdown), supporting (non-markdown),
    // and directories ignoring all dotfiles.
    //
    files = files.forEach(function (file) {
      if (file[0] === '.') {
        return;
      }

      var ext = path.extname(file);

      if (ext === '.md' || ext === '.markdown') {
        markdown.push(file);
      }
      else if (ext.length) {
        supporting.push(file);
      }
      else {
        dirs.push(file);
      }
    });

    if (!supporting.length && !markdown.length && !dirs.length) {
      return callback(new Error('No files found to render'));
    }

    //
    // In parallel over all files and directories found with a concurrency
    // of 10.
    //
    async.parallel([
      //
      // * For Markdown content render them and place the results
      //   in the content hierarchy.
      //
      function renderMarkdowns(done) {
        async.forEachLimit(
          markdown,
          10,
          function renderMarkdown(file, next) {
            var fullpath = path.join(dir, file),
                ext = path.extname(file),
                name = path.basename(file, ext);

            fs.stat(fullpath, function (err, stats) {
              if (err) {
                return next(err);
              }

              content.render({
                source: fullpath,
                marked: self.marked,
                files: supporting,
                dir: dir
              }, function onMarkdown(err, results) {
                if (err) {
                  return next(err);
                }

                rendered[name] = {
                  _content: results,
                  _date: results.metadata.date
                    ? new Date(results.metadata.date)
                    : new Date(stats.ctime)
                };

                common.addDetails(rendered[name]._content, {
                  dir: path.join(self.dir, 'content'),
                  fullpath: fullpath,
                  files: supporting,
                  stats: stats
                });

                // create an item in the RSS feed
                feed.item({
                  title: results.metadata['page-details'].title,
                  description: results.truncated.html,
                  url: results.metadata['page-details'].href,
                  author: results.metadata.author,
                  date: rendered[name]._date
                })

                if (name === 'index') {
                  //
                  // Hoist the date associated with `index.md` files to
                  // the directory itself.
                  //
                  rendered._date = rendered[name]._date;
                }

                next();
              });
            });
          },
          done
        );
      },
      //
      // * For directories continue to search recursively for more files.
      //
      function renderDirs(done) {
        async.forEachLimit(
          dirs,
          10,
          function renderDir(file, next) {
            var fullpath = path.join(dir, file);

            self.renderContent(fullpath, function onDir(err, results) {
              if (err) {
                return next(err);
              }

              rendered[file] = results;
              next();
            });
          },
          done
        );
      }
    ], function (err) {
      if (err) {
        return callback(err);
      }

      //
      // If there are any supporting files set them to the
      // `_files` property on the rendered content.
      //
      if (supporting.length) {
        if (rendered.index) {
          rendered.index._files = supporting;
        }
        else {
          rendered._files = supporting;
        }
      }

      callback(null, rendered);
    });
  });
};

//
// ### function writeFiles (rendered, callback)
// #### @rendered {Object} Fully rendered site structure.
// #### @callback {function} Continuation to respond to when complete.
//
// Writes all files for the `rendered` content associated with this instance.
//
Site.prototype.writeFiles = function (rendered, callback) {
  var publicDir = path.join(this.dir, 'public'),
      written = {},
      self = this;

  function onMkdir(next) {
    return function (err) {
      return err && err.code !== 'EEXIST'
        ? next(err)
        : next();
    };
  }

  async.series([
    //
    // 1. Make `/path/to/site/public`
    //
    function mkdir(next) {
      fs.mkdir(publicDir, onMkdir(next));
    },
    function writeRSS(next){
      fs.writeFile(path.join(publicDir, 'feed.xml'), feed.xml(), 'utf8', next);
    },
    //
    // 2. Write the HTML for all keys and all associated
    //    files into a matching directory structure.
    //
    function writeAll(done) {
      async.forEachSeries(
        self.targetFiles(rendered),
        function write(target, next) {
          //
          // There are three operations that take place here:
          //
          if (target.contents === 'directory') {
            //
            // 1. Create directory: `{ fullpath: /path/to/public/file.html, contents: "directory" }`
            //
            fs.mkdir(target.fullpath, onMkdir(next));
          }
          else if (target.contents) {
            //
            // 2. Write html `{ fullpath: /path/to/public/file.html, contents: "..." }`
            //
            fs.writeFile(target.fullpath, target.contents, 'utf8', next);
          }
          else if (target.source) {
            //
            // 3. Copy supporting files `{ fullpath: /path/to/public/file.html, source: "..." }`
            //
            cpr(target.source, target.fullpath, function (err) {
              return err ? next(err) : next();
            });
          }
        },
        function (err) {
          return err instanceof Error
            ? done(err)
            : done();
        }
      );
    }
  ], function (err) {
    return err ? callback(err) : callback (null, written);
  });
};

//
// ### function targetFiles (rendered)
// #### @rendered {Object} Fully rendered site structure.
//
// Returns a list of objects representing:
// 1. Directories to create in /public
// 2. Write rendered HTML content
// 3. Copy supporting files
//
Site.prototype.targetFiles = function (rendered, pathTo) {
  var contentDir = path.join(this.dir, 'content'),
      publicDir = path.join(this.dir, 'public'),
      self = this;

  pathTo = pathTo || [];

  return Object.keys(rendered).reduce(function (all, key) {
    //
    // The set of child keys under this `key` in the rendering
    // hierarchy.
    //
    var fullpath = [publicDir].concat(pathTo),
        childKeys;

    childKeys = Object.keys(rendered[key]).filter(function (childKey) {
      return childKey[0] !== '_';
    });

    if (!rendered[key]._content) {
      //
      // 1. If there is no `_content` property then this is
      //    a directory that needs creation
      //
      all = all.concat([{
        fullpath: path.join.apply(path, fullpath.concat([key])),
        contents: 'directory'
      }]);
    }
    else if (!rendered[key]._content.date
      || self.since < rendered[key]._content.date) {
      //
      // 2. If there is a `_content` property then write
      //    a file with the HTML contents.
      //
      // TODO: We should have some indicator if a page in the
      // content tree is compiled from other pages or not.
      //
      all = all.concat({
        fullpath: path.join.apply(path, fullpath.concat([key + '.html'])),
        contents: rendered[key]._content.html
      });

      //
      // 3. If this file has any supporting files then copy
      //    those as well.
      //
      if (rendered[key]._content.files) {
        all = all.concat(rendered[key]._content.files.map(function (file) {
          return {
            fullpath: path.join.apply(path, fullpath.concat([path.basename(file)])),
            source: path.join(contentDir, rendered[key].dir || '', file)
          };
        }));
      }
    }

    //
    // 4. If there are any `childKeys` then get the files that should
    //    be created from them
    //
    if (childKeys.length) {
      all = all.concat(self.targetFiles(rendered[key], pathTo.concat([key])));
    }

    return all;
  }, []);
};
<|endoftext|>


<commit_before>"use strict"
// API Elasticsearch
const esmng = require('./esmng.js');
const uuidV1 = require('uuid/v1');

// ES variables
const INDEX_REQUEST = 'request';
const INDEX_HASHTAGS = 'syntax_tree';
const FIRST_HASHTAG = 'Origine';
const UNTREATED = 'Untreated';
const IN_PROGRESS = 'In progress';
const DONE = 'Done';

let table_state = [];
table_state[UNTREATED] = IN_PROGRESS;
table_state[IN_PROGRESS] = DONE;
table_state[DONE] = DONE;

var exports = module.exports = {};

/*
This function save the request into the ES server. The only parameters contains the image, the user_id, the position,
and the hashtags. The date and the request_id are automatically generated.
 */

function save_request(request, res, type_platform) {
  try {
      if (request.hasOwnProperty('user_id') && request.hasOwnProperty('image') && request.hasOwnProperty('position') &&
      request.hasOwnProperty('hashtags')) {
      let key = uuidV1();
      let actual_date = new Date();
      request.request_id = key;
      request.date = actual_date;
      request.technician_id = "";
      request.image_final = null;
      request.date_final = null;
      request.state = "Untreated";
      esmng.add_document(INDEX_REQUEST, type_platform, request, function (error, response) {
        if (error) {
          res.status(500).send("The report couldn't be saved : " + error.message);
        } else {
          res.send("Request considered");
        }
      });
    } else {
      throw new Error("The request isn't correct");
    }
  } catch (e) {
    res.status(500).send(e.message);
  }
}

/* This function retrieve all of the reports within the filter provided*/

function get_reports_filtered(request, res, type_platform) {
  let query = {"query": {}};
  try {
    let filters = request.filters;
    if (filters) {
      if(filters.length == 0) {
        query.query = {
            "match_all": {}
        };
      } else {
        filters.forEach(function (filter) {
          query.query = {"bool" : { "must" : [], "should" : []}};
          switch(filter.type) {
            case 'time_range' :
              if(filter.from) {
                let from = new Date(filter.from);
                query.query.bool.must.push({
                  "range": {
                    "date": {
                      "gte": from.getTime()
                    }
                  }
                });
              }
              if(filter.to) {
                let to = new Date(filter.to);
                query.query.bool.must.push({
                  "range": {
                    "date": {
                      "lte": to.getTime()
                    }
                  }
                });
              }
              break;
            case 'user_id' :
              break;
            case 'request_id' :
              query.query.bool.must.push({
                "term": {
                  "request_id": filter.request_id
                }
              });
              break;
            case 'hashtags' :
              break;
            case 'state' :
              filter.values.forEach(function(value) {
                query.query.bool.should.push({
                  "term": {
                    "state": value
                  }
                });
              });
              break;
            default :
              break;
          }
        });
      }
    esmng.search_document(INDEX_REQUEST, type_platform, query, function (hits) {
      let reports = [];
      if (hits.length != 0) {
        hits.forEach(function (hit) {
          reports.push(hit._source);
        });
      }
      let reports_geojson = toGeoJSON(reports, request);
      res.json(reports_geojson);
    });
    } else {
      throw new Error("The request isn't correct");
    }
  } catch (e) {
    res.status(500).send(e.message);
  }
}

function change_state(request, res, type_platform) {
  try {
    if (request.request_id) {
      let query = {"query": {"term": {
        "request_id" : request.request_id
      }}};
      esmng.search_document(INDEX_REQUEST, type_platform, query, function (hits) {
        let reports = [];
        if (hits.length != 0) {
          hits.forEach(function (hit) {
            reports.push(hit._source);
          });
        }
        let new_state = table_state[reports[0].state];
        if(new_state != null) {
          reports[0].state = new_state;
          esmng.add_document(INDEX_REQUEST, type_platform, reports[0], function (error, response) {
            if (error) {
              res.status(500).send("The report couldn't be saved : " + error.message);
            } else {
              res.json(reports[0]);
            }
          }, hits[0]._id);
        } else {
          throw new Error("The state isn't correct");
        }
      });
    } else {
      throw new Error("The request id parameter is missing");
    }
  } catch (e) {
    res.status(500).send(e.message);
  }
}

function toGeoJSON(hits, request) {
  let geojson = {
    "type": "FeatureCollection",
    "features": []
  };
  hits.forEach(function (hit) {
    let properties = {
      "user_id": hit.user_id,
      "request_id": hit.request_id,
      "date": hit.date,
      "hashtags": hit.hashtags,
      "state": hit.state,
      "technician_id": hit.technician_id
      "date_final": hit.date_final
    };
    if(request.full == true) {
      properties.image = hit.image;
      properties.image_final = hit.image_final;
    }
    let coordinates = [hit.position.lon, hit.position.lat];
    geojson.features.push({
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": coordinates
      },
      "properties": properties
    });
  });
  return geojson;
}

/* The hashtags associated with the parameter are retrieved from the ES Server and returned. */
function get_next_hashtags(hashtag, res, type_platform) {
  if(hashtag === '') {
    hashtag = FIRST_HASHTAG;
  }
  let query =  {
    match: { "name": hashtag }
  };
  esmng.search_document(INDEX_HASHTAGS, type_platform, query, function (hit) {
    console.log(hit.length);
    if (hit.length != 0) {
      let hashtags = { hashtags : [] };
      hashtags.hashtags = hit[0]._source.following;
      res.json(hashtags);
    } else {
      res.status(500).send("The hashtag doesn't exist");
    }
  });
}

//Exports
exports.save_request = save_request;
exports.get_next_hashtags = get_next_hashtags;
exports.get_reports_filtered = get_reports_filtered;
exports.change_state = change_state;<commit_msg>Correction 1<commit_after>"use strict"
// API Elasticsearch
const esmng = require('./esmng.js');
const uuidV1 = require('uuid/v1');

// ES variables
const INDEX_REQUEST = 'request';
const INDEX_HASHTAGS = 'syntax_tree';
const FIRST_HASHTAG = 'Origine';
const UNTREATED = 'Untreated';
const IN_PROGRESS = 'In progress';
const DONE = 'Done';

let table_state = [];
table_state[UNTREATED] = IN_PROGRESS;
table_state[IN_PROGRESS] = DONE;
table_state[DONE] = DONE;

var exports = module.exports = {};

/*
This function save the request into the ES server. The only parameters contains the image, the user_id, the position,
and the hashtags. The date and the request_id are automatically generated.
 */

function save_request(request, res, type_platform) {
  try {
      if (request.hasOwnProperty('user_id') && request.hasOwnProperty('image') && request.hasOwnProperty('position') &&
      request.hasOwnProperty('hashtags')) {
      let key = uuidV1();
      let actual_date = new Date();
      request.request_id = key;
      request.date = actual_date;
      request.technician_id = "";
      request.image_final = null;
      request.date_final = null;
      request.state = "Untreated";
      esmng.add_document(INDEX_REQUEST, type_platform, request, function (error, response) {
        if (error) {
          res.status(500).send("The report couldn't be saved : " + error.message);
        } else {
          res.send("Request considered");
        }
      });
    } else {
      throw new Error("The request isn't correct");
    }
  } catch (e) {
    res.status(500).send(e.message);
  }
}

/* This function retrieve all of the reports within the filter provided*/

function get_reports_filtered(request, res, type_platform) {
  let query = {"query": {}};
  try {
    let filters = request.filters;
    if (filters) {
      if(filters.length == 0) {
        query.query = {
            "match_all": {}
        };
      } else {
        filters.forEach(function (filter) {
          query.query = {"bool" : { "must" : [], "should" : []}};
          switch(filter.type) {
            case 'time_range' :
              if(filter.from) {
                let from = new Date(filter.from);
                query.query.bool.must.push({
                  "range": {
                    "date": {
                      "gte": from.getTime()
                    }
                  }
                });
              }
              if(filter.to) {
                let to = new Date(filter.to);
                query.query.bool.must.push({
                  "range": {
                    "date": {
                      "lte": to.getTime()
                    }
                  }
                });
              }
              break;
            case 'user_id' :
              break;
            case 'request_id' :
              query.query.bool.must.push({
                "term": {
                  "request_id": filter.request_id
                }
              });
              break;
            case 'hashtags' :
              break;
            case 'state' :
              filter.values.forEach(function(value) {
                query.query.bool.should.push({
                  "term": {
                    "state": value
                  }
                });
              });
              break;
            default :
              break;
          }
        });
      }
    esmng.search_document(INDEX_REQUEST, type_platform, query, function (hits) {
      let reports = [];
      if (hits.length != 0) {
        hits.forEach(function (hit) {
          reports.push(hit._source);
        });
      }
      let reports_geojson = toGeoJSON(reports, request);
      res.json(reports_geojson);
    });
    } else {
      throw new Error("The request isn't correct");
    }
  } catch (e) {
    res.status(500).send(e.message);
  }
}

function change_state(request, res, type_platform) {
  try {
    if (request.request_id) {
      let query = {"query": {"term": {
        "request_id" : request.request_id
      }}};
      esmng.search_document(INDEX_REQUEST, type_platform, query, function (hits) {
        let reports = [];
        if (hits.length != 0) {
          hits.forEach(function (hit) {
            reports.push(hit._source);
          });
        }
        let new_state = table_state[reports[0].state];
        if(new_state != null) {
          reports[0].state = new_state;
          esmng.add_document(INDEX_REQUEST, type_platform, reports[0], function (error, response) {
            if (error) {
              res.status(500).send("The report couldn't be saved : " + error.message);
            } else {
              res.json(reports[0]);
            }
          }, hits[0]._id);
        } else {
          throw new Error("The state isn't correct");
        }
      });
    } else {
      throw new Error("The request id parameter is missing");
    }
  } catch (e) {
    res.status(500).send(e.message);
  }
}

function toGeoJSON(hits, request) {
  let geojson = {
    "type": "FeatureCollection",
    "features": []
  };
  hits.forEach(function (hit) {
    let properties = {
      "user_id": hit.user_id,
      "request_id": hit.request_id,
      "date": hit.date,
      "hashtags": hit.hashtags,
      "state": hit.state,
      "technician_id": hit.technician_id,
      "date_final": hit.date_final
    };
    if(request.full == true) {
      properties.image = hit.image;
      properties.image_final = hit.image_final;
    }
    let coordinates = [hit.position.lon, hit.position.lat];
    geojson.features.push({
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": coordinates
      },
      "properties": properties
    });
  });
  return geojson;
}

/* The hashtags associated with the parameter are retrieved from the ES Server and returned. */
function get_next_hashtags(hashtag, res, type_platform) {
  if(hashtag === '') {
    hashtag = FIRST_HASHTAG;
  }
  let query =  {
    match: { "name": hashtag }
  };
  esmng.search_document(INDEX_HASHTAGS, type_platform, query, function (hit) {
    console.log(hit.length);
    if (hit.length != 0) {
      let hashtags = { hashtags : [] };
      hashtags.hashtags = hit[0]._source.following;
      res.json(hashtags);
    } else {
      res.status(500).send("The hashtag doesn't exist");
    }
  });
}

//Exports
exports.save_request = save_request;
exports.get_next_hashtags = get_next_hashtags;
exports.get_reports_filtered = get_reports_filtered;
exports.change_state = change_state;<|endoftext|>


<commit_before>/*
 * Build a simple index based on entry names.
 */
export const buildNameIndex = (entries) => {
  const index = {}
  entries.forEach((entry) => {
    let currentString = ''
    for (let character of entry.name) {
      currentString += character.toLowerCase()
      if (index[currentString] === undefined) index[currentString] = []
      index[currentString].push(entry)
    }
  })
  return index
}

/*
 * Generate an index to find task easily. Search will be based on the task
 * entity name and words appearing into it.
 * The result is an array of tasks.
 */
export const buildTaskIndex = (tasks) => {
  const index = {}
  const taskIndex = {}
  tasks.forEach((task) => {
    let stringToIndex =
      task.full_entity_name.replace(/_/g, ' ').replace(/-/g, ' ')
    let words = stringToIndex.split(' ').concat([
      task.task_type_name,
      task.task_status_short_name,
      task.project_name
    ])
    indexWords(index, taskIndex, task, words)
  })
  return index
}

/*
 * Generate an index to find asset easily. Search will be based on the asse
 * type name, and words appearing in the asset name.
 * Results are arrays of assets.
 */
export const buildAssetIndex = (entries) => {
  const index = {}
  const assetIndex = {}
  entries.forEach((asset) => {
    let stringToIndex = asset.name.replace(/_/g, ' ').replace(/-/g, ' ')
    let words = stringToIndex.split(' ').concat([asset.asset_type_name])
    indexWords(index, assetIndex, asset, words)
  })
  return index
}

/*
 * Generate an index to find shot easily. Search will be based on the episode,
 * sequence and shot names at the same time.
 * Results are arrays of shots.
 */
export const buildShotIndex = (shots) => {
  const index = {}
  const shotIndex = {}
  shots.forEach((shot) => {
    let words = [shot.name, shot.sequence_name, shot.episode_name]
    indexWords(index, shotIndex, shot, words)
  })
  return index
}

/*
 * Generate an index to find sequence easily. Search will be based on the
 * episode and sequence names at the same time.
 * Results are arrays of sequences.
 */
export const buildSequenceIndex = (sequences) => {
  const index = {}
  const sequenceIndex = {}
  sequences.forEach((sequence) => {
    let words = [sequence.name, sequence.episode_name]
    indexWords(index, sequenceIndex, sequence, words)
  })
  return index
}

/*
 * Generate an index to find episode easily. Search will be based on the
 * episode name.
 * Results are arrays of episodes.
 */
export const buildEpisodeIndex = (episodes) => {
  const index = {}
  const episodeIndex = {}
  episodes.forEach((episode) => {
    let words = [episode.name]
    indexWords(index, episodeIndex, episode, words)
  })
  return index
}

/*
 * Run a non case sensitive search on given index. It accepts different search
 * terms separated by spaces. Terms dedicated to task status filtering (like
 * modeling:wip) are ignored. The result is the intersection of queries.
 */
export const indexSearch = (index, queryText) => {
  if (queryText.length === 0) {
    return null
  } else {
    const queries = queryText.split(' ')
    const results = queries
      .map((query) => indexSearchWord(index, query))
      .filter((result) => result !== null)

    if (results.length > 0) {
      return results.reduce(resultIntersection, [...results[0]])
    } else {
      return null
    }
  }
}

/*
 * Turn an array of sets in an array which is the intersection of elements of
 * all sets.
 */
const resultIntersection = (a, b) => {
  return a.filter(x => b.has(x))
}

/*
 * Return search result for a given word and a given index. Empty word or task
 * type queries are returned as null.
 */
const indexSearchWord = (index, word) => {
  if (word && word.indexOf('=') < 0) {
    if (index[word.toLowerCase()]) {
      return new Set(index[word.toLowerCase()])
    } else {
      return new Set([])
    }
  } else {
    return null
  }
}

/*
 * Index all words in given index. An intermediary index is required
 * to make indexation faster (it is used to know if an asset is linked
 * with current key).
 */
const indexWords = (index, entryIndex, entry, words) => {
  for (let word of words) {
    let currentString = ''
    for (let character of word) {
      currentString += character.toLowerCase()
      if (index[currentString] === undefined) {
        index[currentString] = []
        entryIndex[currentString] = {}
      }

      if (!entryIndex[currentString][entry.id]) {
        index[currentString].push(entry)
        entryIndex[currentString][entry.id] = true
      }
    }
  }
  return index
}
<commit_msg>Index all asset type words<commit_after>/*
 * Build a simple index based on entry names.
 */
export const buildNameIndex = (entries) => {
  const index = {}
  entries.forEach((entry) => {
    let currentString = ''
    for (let character of entry.name) {
      currentString += character.toLowerCase()
      if (index[currentString] === undefined) index[currentString] = []
      index[currentString].push(entry)
    }
  })
  return index
}

/*
 * Generate an index to find task easily. Search will be based on the task
 * entity name and words appearing into it.
 * The result is an array of tasks.
 */
export const buildTaskIndex = (tasks) => {
  const index = {}
  const taskIndex = {}
  tasks.forEach((task) => {
    let stringToIndex =
      task.full_entity_name.replace(/_/g, ' ').replace(/-/g, ' ')
    let words = stringToIndex.split(' ').concat([
      task.task_type_name,
      task.task_status_short_name,
      task.project_name
    ])
    indexWords(index, taskIndex, task, words)
  })
  return index
}

/*
 * Generate an index to find asset easily. Search will be based on the asse
 * type name, and words appearing in the asset name.
 * Results are arrays of assets.
 */
export const buildAssetIndex = (entries) => {
  const index = {}
  const assetIndex = {}
  entries.forEach((asset) => {
    const stringToIndex = asset.name.replace(/_/g, ' ').replace(/-/g, ' ')
    const assetTypeWords = asset.asset_type_name.split(' ')
    const words = stringToIndex.split(' ').concat(assetTypeWords)
    indexWords(index, assetIndex, asset, words)
  })
  return index
}

/*
 * Generate an index to find shot easily. Search will be based on the episode,
 * sequence and shot names at the same time.
 * Results are arrays of shots.
 */
export const buildShotIndex = (shots) => {
  const index = {}
  const shotIndex = {}
  shots.forEach((shot) => {
    let words = [shot.name, shot.sequence_name, shot.episode_name]
    indexWords(index, shotIndex, shot, words)
  })
  return index
}

/*
 * Generate an index to find sequence easily. Search will be based on the
 * episode and sequence names at the same time.
 * Results are arrays of sequences.
 */
export const buildSequenceIndex = (sequences) => {
  const index = {}
  const sequenceIndex = {}
  sequences.forEach((sequence) => {
    let words = [sequence.name, sequence.episode_name]
    indexWords(index, sequenceIndex, sequence, words)
  })
  return index
}

/*
 * Generate an index to find episode easily. Search will be based on the
 * episode name.
 * Results are arrays of episodes.
 */
export const buildEpisodeIndex = (episodes) => {
  const index = {}
  const episodeIndex = {}
  episodes.forEach((episode) => {
    let words = [episode.name]
    indexWords(index, episodeIndex, episode, words)
  })
  return index
}

/*
 * Run a non case sensitive search on given index. It accepts different search
 * terms separated by spaces. Terms dedicated to task status filtering (like
 * modeling:wip) are ignored. The result is the intersection of queries.
 */
export const indexSearch = (index, queryText) => {
  if (queryText.length === 0) {
    return null
  } else {
    const queries = queryText.split(' ')
    const results = queries
      .map((query) => indexSearchWord(index, query))
      .filter((result) => result !== null)

    if (results.length > 0) {
      return results.reduce(resultIntersection, [...results[0]])
    } else {
      return null
    }
  }
}

/*
 * Turn an array of sets in an array which is the intersection of elements of
 * all sets.
 */
const resultIntersection = (a, b) => {
  return a.filter(x => b.has(x))
}

/*
 * Return search result for a given word and a given index. Empty word or task
 * type queries are returned as null.
 */
const indexSearchWord = (index, word) => {
  if (word && word.indexOf('=') < 0) {
    if (index[word.toLowerCase()]) {
      return new Set(index[word.toLowerCase()])
    } else {
      return new Set([])
    }
  } else {
    return null
  }
}

/*
 * Index all words in given index. An intermediary index is required
 * to make indexation faster (it is used to know if an asset is linked
 * with current key).
 */
const indexWords = (index, entryIndex, entry, words) => {
  for (let word of words) {
    let currentString = ''
    for (let character of word) {
      currentString += character.toLowerCase()
      if (index[currentString] === undefined) {
        index[currentString] = []
        entryIndex[currentString] = {}
      }

      if (!entryIndex[currentString][entry.id]) {
        index[currentString].push(entry)
        entryIndex[currentString][entry.id] = true
      }
    }
  }
  return index
}
<|endoftext|>


<commit_before>module.exports = {
  config: {
    fontSize: 12,
    fontFamily: '"Fira Code", Monoid, Menlo, "DejaVu Sans Mono", "Lucida Console", monospace',
    cursorColor: 'rgba(189, 195, 199, .8)',
    css: `
      header {
        box-shadow: 0px 2px 20px rgba(0, 0, 0, 0.17);
      }
    `,
    padding: '0',

    overlay: {
      animate: false,
      alwaysOnTop: true,
      hashShadow: false,
      hideOnBlur: false,
      hideDock: false,
      hotkeys: ['CommandOrControl+Z'],
      resizable: false,
      position: 'top',
      primaryDisplay: false,
      startup: true,
      size: 1,
      tray: false,
      unique: true
    }
  },

  plugins: [
    'hyperterm-draculicious',
    'hyperterm-title',
    'hyperterm-overlay',
    'hyperterm-alternatescroll',
    'hyperterm-focus-reporting',
    'hyperterm-paste',
    'hyperterm-final-say'
  ],

  // `~/.hyperterm_plugins/local/`
  localPlugins: []
};
<commit_msg>Update hyperterm configuration<commit_after>module.exports = {
  config: {
    fontSize: 12,
    fontFamily: '"Fira Code", Monoid, Menlo, "DejaVu Sans Mono", "Lucida Console", monospace',
    css: `
      header {
        box-shadow: 0px 2px 20px rgba(0, 0, 0, 0.17);
      }
    `,
    padding: '0'
  },

  plugins: [
    'hyperterm-adventurous',
    'hyperterm-title',
    'hyperterm-alternatescroll',
    'hyperterm-focus-reporting',
    'hyperterm-cursor',
    'hyperterm-final-say'
  ],

  // `~/.hyperterm_plugins/local/`
  localPlugins: []
};
<|endoftext|>


<commit_before>'use strict';

/*
 * openwms.org, the Open Warehouse Management System.
 * Copyright (C) 2014 Heiko Scherrer
 *
 * This file is part of openwms.org.
 *
 * openwms.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * openwms.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software. If not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 * Main colors:
 * blue		: 2e7bb1
 * yellow	: e1e76b
 * light-blue   : c9dcea
 * lighter-blue : edf4fa
 */

/*
 * env.model.js
 * Environment model module
 */

/*jslint         browser : true, continue : true,
 devel  : true, indent  : 2,    maxerr   : 50,
 newcap : true, nomen   : true, plusplus : true,
 regexp : true, sloppy  : true, vars     : false,
 white  : true
 */

/*global $, openwms */
define(['angular'], function () {

	var coreEnvModel = angular.module('coreEnvModel', []);

	var config = {

		env: {
			"DEVMODE": false,
			"backendUrl": 'http://localhost:8080/org.openwms.client.rest.provider',
//			"backendUrl" : 'http://backend.openwms.cloudbees.net',
			"buildNumber": '${build.number}',
			"buildDate": '${build.date}'
		},

		const: {
			AUTH_TOKEN: 'Auth-Token',
			TENANT_ID: 'TenantId'
		},

		events: {
			APP_LOGIN: 'CORE_APP_LOGIN',
			APP_LOGOUT: 'CORE_APP_LOGOUT',
			RETRIEVED_TOKEN: 'CORE_RETRIEVED_TOKEN',
			SUCCESSFULLY_LOGGED_IN: 'SUCCESSFULLY_LOGGED_IN',
			INVALID_CREDENTIALS: 'CORE_INVALID_CREDENTIALS'
		}};

	coreEnvModel.constant('CoreConfig', config);
});
<commit_msg>ST-84 - worked on offering screen<commit_after>'use strict';

/*
 * openwms.org, the Open Warehouse Management System.
 * Copyright (C) 2014 Heiko Scherrer
 *
 * This file is part of openwms.org.
 *
 * openwms.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * openwms.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software. If not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 * Main colors:
 * blue		: 2e7bb1
 * yellow	: e1e76b
 * light-blue   : c9dcea
 * lighter-blue : edf4fa
 */

/*
 * env.model.js
 * Environment model module
 */

/*jslint         browser : true, continue : true,
 devel  : true, indent  : 2,    maxerr   : 50,
 newcap : true, nomen   : true, plusplus : true,
 regexp : true, sloppy  : true, vars     : false,
 white  : true
 */

/*global $, openwms */
define(['angular'], function () {

	var coreEnvModel = angular.module('coreEnvModel', []);

	var config = {

		env: {
			"DEVMODE": false,
//			"backendUrl": 'http://localhost:8080/org.openwms.client.rest.provider',
			"backendUrl" : 'http://stampback.herokuapp.com',
			"buildNumber": '${build.number}',
			"buildDate": '${build.date}'
		},

		const: {
			AUTH_TOKEN: 'Auth-Token',
			TENANT_ID: 'TenantId'
		},

		events: {
			APP_LOGIN: 'CORE_APP_LOGIN',
			APP_LOGOUT: 'CORE_APP_LOGOUT',
			RETRIEVED_TOKEN: 'CORE_RETRIEVED_TOKEN',
			SUCCESSFULLY_LOGGED_IN: 'SUCCESSFULLY_LOGGED_IN',
			INVALID_CREDENTIALS: 'CORE_INVALID_CREDENTIALS'
		}};

	coreEnvModel.constant('CoreConfig', config);
});
<|endoftext|>


<commit_before>// Global variables elementsLeft and elementsRight are read in through another link in the the HTML.

// The "JSProblemState" JSON dictionary below is passed to the platform for grading and saving.
// Pass through whatever you wish. It will get logged.
var JSProblemState = {
	'pairings': [] // Pairings of the form [letter, number].
};

// Loads the cards from the cardList variable
function loadElements(){

	var matchingTable = $('#matchingTable');
	var loopy = 0;

	var tableArrayLeft = [];
	var tableArrayRight = [];
	var tableArray = [];
	var totalTableLength = 0;

	var leftElements = $('#leftelements');
	var rightElements = $('#rightelements');

	// We're going to build an array from the elementsLeft and elementsRight objects,
	// and then we'll build an HTML table out of that. It'll be easier.

	for(var key in elementsLeft){
		tableArrayLeft.push(key);
	}

	for(var key in elementsRight){
		tableArrayRight.push(key);
	}

	var totalTableLength = Math.max(tableArrayLeft.length, tableArrayRight.length);

	for(var i = 0; i<totalTableLength; i++){
		if(tableArrayLeft[i] && tableArrayRight[i]){
			tableArray.push([tableArrayLeft[i], tableArrayRight[i]]);
		}
		if(tableArrayLeft[i] && !tableArrayRight[i]){
			tableArray.push([tableArrayLeft[i], false]);
		}
		if(!tableArrayLeft[i] && tableArrayRight[i]){
			tableArray.push([false, tableArrayRight[i]]);
		}

	}

	// Now we actually build the elements.

	for(var i = 0; i < totalTableLength; i++){

		// Setting it up this way as extra debugging breadcrumbs.
		var thisElement = 'error message';
		var tableTag = 'no table';
		var label = 'no label';
		var content = 'no content';
		var innercontent = 'no inner content';
		var dropArea = 'nothing';

		// Left-hand elements:
		if(tableArray[i][0]){

			label = '
	'
				+ elementsLeft[tableArray[i][0]].label
				+ '
';

			// Handle image vs text
			// Also load full-sized elements in hidden space for zoom later.
			if(elementsLeft[tableArray[i][0]].type == 'image'){
				tableTag = '
';
				content = '

';
				$('#hiddenspace').append('

');
			}
			if(elementsLeft[tableArray[i][0]].type == 'text'){
				tableTag = '
';
				content = '
'
					+ elementsLeft[tableArray[i][0]].text
					+ '
';
				$('#hiddenspace').append('
'
					+ elementsLeft[tableArray[i][0]].text
					+ '
');
			}

			thisElement = tableTag + label + content + '
';

			leftElements.append(thisElement);

		}

		// Right-hand elements:
		if(tableArray[i][1]){

			label = '
	'
				+ elementsRight[tableArray[i][1]].label
				+ '
';
			dropArea = ' 	'

			// Handle image vs text.
			// Also load full-sized elements in hidden space for zoom later.
			if(elementsRight[tableArray[i][1]].type == 'image'){
				tableTag = '
';
				content = '
'
					+ dropArea
					+ '
';
				$('#hiddenspace').append('

');
			}
			if(elementsRight[tableArray[i][1]].type == 'text'){
				tableTag = '
';
				content = '
'
					+ dropArea
					+ ''
					+ elementsRight[tableArray[i][1]].text
					+ '
';
				$('#hiddenspace').append('
'
					+ elementsRight[tableArray[i][1]].text
					+ '
');
			}

			thisElement = tableTag + label + content + '
';

			rightElements.append(thisElement);

		}

	}

	// After adding all the elements, make the left-hand ones draggable.
	$('.element-left').draggable({
		opacity: 0.7,
		revert: 'invalid',	// Snap back visibly if not dropped to a valid location.
		helper: 'clone'		// This helps when dragging out of scrollable areas.
	});

	// Make the right-hand ones drop targets.
	$( '.element-right' ).droppable({
		tolerance: 'pointer',
		hoverClass: 'drophighlight',
		activeClass: 'dropactive',
		drop: handleDrop
	});

	// When an element is clicked, show the full-size image or text.
	$('.openbig').on('click tap', function(){
		var newID = 'big' + $(this).parents('table').attr('id');

		// Slightly different procedure for images and text cards.
		var modalWidth = 'auto';
		if($(this).parents('table').hasClass('text')){ modalWidth = 650; }

		$('#' + newID).dialog({
			width: modalWidth,
			modal: true,
			closeOnEscape: true,
			open: function(){			// Close that if they click outside the image.
				$('.ui-widget-overlay').on('click tap',function(){
					$('#' + newID).dialog('close');
				});
			}
		});
	});

}

function handleDrop(event, ui){

	var draggedElement = $(ui.draggable);
	var targetElement = $(this);

	var draggedID = draggedElement.attr('id');
	var letter = draggedID.replace('element', '');
	var targetID = targetElement.attr('id');
	var number = targetID.replace('element', '');
	var thisPairing = [letter,number];

	var existingMatch = false;

	// If these elements are already matched, we're done.
	for(var i = 0; i < JSProblemState.pairings.length; i++){
		if(_.isEqual(JSProblemState.pairings[i], thisPairing)){  // Uses underscore.js
			existingMatch = true;
			break;
		}
	}

	// If not, add visual indication of the match and update JSProblemState.
	if(!existingMatch){

		// Copy the title from the left-hand element and add it to the indicator space.
		var indicatorSpace = targetElement.find('td.drop-area');
		addMatch(indicatorSpace, letter, number);
		JSProblemState.pairings.push(thisPairing);
	}
}

function addMatch(indicatorSpace, letter, number){
	// Add to the DOM
	var indicator = '
' + elementsLeft[letter].label + '
';
	indicatorSpace.append(indicator);

	// Give this a removal button.
	$('#' + letter + '-' + number).append('x');
	$('.delete').on('click tap', function(event){selfDelete(event)});
}

// Callback function for the self-delete button on match indicators
function selfDelete(event){

	var target = $(event.target).parent();

	var targetID = target.attr('id').split('-'); // Letter before hyphen, number after.
	var letter = targetID[0];
	var number = targetID[1];

	var thisPairing = [letter, number];

	// Remove the group from JSProblemState
	var exIndex = _.indexOf(JSProblemState.pairings, thisPairing);
	JSProblemState.pairings.splice(exIndex, 1);

	// Remove the indicator from the DOM
	target.remove();

}

function putMatchesBack(){

	for(var i = 0; i < JSProblemState.pairings.length; i++){
		var letter = JSProblemState.pairings[i][0];
		var number = JSProblemState.pairings[i][1];
		var indicatorSpace = $('#element'+number).find('td.drop-area');
		addMatch(indicatorSpace, letter, number);
	}

}

$(document).ready(function(){
	loadElements();
});

// This wrapper function is necessary.
var MatchingJS = (function() {

	// REQUIRED --- DO NOT REMOVE/CHANGE!!
	var channel;

	// REQUIRED --- DO NOT REMOVE/CHANGE!!
	if (window.parent !== window) {
		channel = Channel.build({
			window: window.parent,
			origin: "*",
			scope: "JSInput"
		});
		channel.bind("getGrade", getGrade);
		channel.bind("getState", getState);
		channel.bind("setState", setState);

	}

	// getState() and setState() are required by the problem type.
	function getState(){
		console.log('getting state');
		return JSON.stringify(JSProblemState);
	}

	// TODO: This needs a major update. Be sure to put all the cards in their correct places.
	function setState() {
		console.log('setting state');
		stateStr = arguments.length === 1 ? arguments[0] : arguments[1];
		JSProblemState = JSON.parse(stateStr);

		// Put the cards in the correct locations based on what the problem state says.
		putMatchesBack();

	}

	// To keep students from seeing the solution that way, just pass the problem state back
	// and use python in the problem XML to analyze it.

	function getGrade() {
		console.log('getting grade');

		// Log the problem state.
		// This is called from the parent window's Javascript so that we can write to the official edX logs.
		parent.logThatThing(JSProblemState);

		// Return the whole problem state.
		return JSON.stringify(JSProblemState);
	}

	// REQUIRED --- DO NOT REMOVE/CHANGE!!
	return {
		getState: getState,
		setState: setState,
		getGrade: getGrade
	};

}());<commit_msg>Minor code cleanups.<commit_after>// Global variables elementsLeft and elementsRight are read in through another link in the the HTML.

// The "JSProblemState" JSON dictionary below is passed to the platform for grading and saving.
// Pass through whatever you wish. It will get logged.
var JSProblemState = {
	'pairings': [] // Pairings of the form [letter, number].
};

// Loads the cards from the cardList variable
function loadElements(){

	var tableArrayLeft = [];
	var tableArrayRight = [];
	var tableArray = [];
	var totalTableLength = 0;

	var leftElements = $('#leftelements');
	var rightElements = $('#rightelements');

	// We're going to build an array from the elementsLeft and elementsRight objects,
	// and then we'll build HTML elements out of that. It'll be easier.

	for(var key in elementsLeft){
		tableArrayLeft.push(key);
	}

	for(var key in elementsRight){
		tableArrayRight.push(key);
	}

	var totalTableLength = Math.max(tableArrayLeft.length, tableArrayRight.length);

	for(var i = 0; i<totalTableLength; i++){
		if(tableArrayLeft[i] && tableArrayRight[i]){
			tableArray.push([tableArrayLeft[i], tableArrayRight[i]]);
		}
		if(tableArrayLeft[i] && !tableArrayRight[i]){
			tableArray.push([tableArrayLeft[i], false]);
		}
		if(!tableArrayLeft[i] && tableArrayRight[i]){
			tableArray.push([false, tableArrayRight[i]]);
		}

	}

	// Now we actually build the elements.

	for(var i = 0; i < totalTableLength; i++){

		// Setting it up this way as extra debugging breadcrumbs.
		var thisElement = 'error message';
		var tableTag = 'no table';
		var label = 'no label';
		var content = 'no content';
		var innercontent = 'no inner content';
		var dropArea = 'nothing';

		// Left-hand elements:
		if(tableArray[i][0]){

			label = '
	'
				+ elementsLeft[tableArray[i][0]].label
				+ '
';

			// Handle image vs text
			// Also load full-sized elements in hidden space for zoom later.
			if(elementsLeft[tableArray[i][0]].type == 'image'){
				tableTag = '
';
				content = '

';
				$('#hiddenspace').append('

');
			}
			if(elementsLeft[tableArray[i][0]].type == 'text'){
				tableTag = '
';
				content = '
'
					+ elementsLeft[tableArray[i][0]].text
					+ '
';
				$('#hiddenspace').append('
'
					+ elementsLeft[tableArray[i][0]].text
					+ '
');
			}

			thisElement = tableTag + label + content + '
';

			leftElements.append(thisElement);

		}

		// Right-hand elements:
		if(tableArray[i][1]){

			label = '
	'
				+ elementsRight[tableArray[i][1]].label
				+ '
';
			dropArea = ' 	'

			// Handle image vs text.
			// Also load full-sized elements in hidden space for zoom later.
			if(elementsRight[tableArray[i][1]].type == 'image'){
				tableTag = '
';
				content = '
'
					+ dropArea
					+ '
';
				$('#hiddenspace').append('

');
			}
			if(elementsRight[tableArray[i][1]].type == 'text'){
				tableTag = '
';
				content = '
'
					+ dropArea
					+ ''
					+ elementsRight[tableArray[i][1]].text
					+ '
';
				$('#hiddenspace').append('
'
					+ elementsRight[tableArray[i][1]].text
					+ '
');
			}

			thisElement = tableTag + label + content + '
';

			rightElements.append(thisElement);

		}

	}

	// After adding all the elements, make the left-hand ones draggable.
	$('.element-left').draggable({
		opacity: 0.7,
		revert: 'invalid',	// Snap back visibly if not dropped to a valid location.
		helper: 'clone'		// This helps when dragging out of scrollable areas.
	});

	// Make the right-hand ones drop targets.
	$( '.element-right' ).droppable({
		tolerance: 'pointer',
		hoverClass: 'drophighlight',
		activeClass: 'dropactive',
		drop: handleDrop
	});

	// When an element is clicked, show the full-size image or text.
	$('.openbig').on('click tap', function(){
		var newID = 'big' + $(this).parents('table').attr('id');

		// Slightly different procedure for images and text cards.
		var modalWidth = 'auto';
		if($(this).parents('table').hasClass('text')){ modalWidth = 650; }

		$('#' + newID).dialog({
			width: modalWidth,
			modal: true,
			closeOnEscape: true,
			open: function(){			// Close that if they click outside the image.
				$('.ui-widget-overlay').on('click tap',function(){
					$('#' + newID).dialog('close');
				});
			}
		});
	});

}

function handleDrop(event, ui){

	var draggedElement = $(ui.draggable);
	var targetElement = $(this);

	var draggedID = draggedElement.attr('id');
	var letter = draggedID.replace('element', '');
	var targetID = targetElement.attr('id');
	var number = targetID.replace('element', '');
	var thisPairing = [letter,number];

	var existingMatch = false;

	// If these elements are already matched, we're done.
	for(var i = 0; i < JSProblemState.pairings.length; i++){
		if(_.isEqual(JSProblemState.pairings[i], thisPairing)){  // Uses underscore.js
			existingMatch = true;
			break;
		}
	}

	// If not, add visual indication of the match and update JSProblemState.
	if(!existingMatch){

		// Copy the title from the left-hand element and add it to the indicator space.
		var indicatorSpace = targetElement.find('td.drop-area');
		addMatch(indicatorSpace, letter, number);
		JSProblemState.pairings.push(thisPairing);
	}
}

function addMatch(indicatorSpace, letter, number){
	// Add to the DOM
	var indicator = '
' + elementsLeft[letter].label + '
';
	indicatorSpace.append(indicator);

	// Give this a removal button.
	$('#' + letter + '-' + number).append('x');
	$('.delete').on('click tap', function(event){selfDelete(event)});
}

// Callback function for the self-delete button on match indicators
function selfDelete(event){

	var target = $(event.target).parent();

	var targetID = target.attr('id').split('-'); // Letter before hyphen, number after.
	var letter = targetID[0];
	var number = targetID[1];

	var thisPairing = [letter, number];

	// Remove the group from JSProblemState
	var exIndex = _.indexOf(JSProblemState.pairings, thisPairing);
	JSProblemState.pairings.splice(exIndex, 1);

	// Remove the indicator from the DOM
	target.remove();

}

function putMatchesBack(){

	for(var i = 0; i < JSProblemState.pairings.length; i++){
		var letter = JSProblemState.pairings[i][0];
		var number = JSProblemState.pairings[i][1];
		var indicatorSpace = $('#element'+number).find('td.drop-area');
		addMatch(indicatorSpace, letter, number);
	}

}

$(document).ready(function(){
	loadElements();
});

// This wrapper function is necessary.
var MatchingJS = (function() {

	// REQUIRED --- DO NOT REMOVE/CHANGE!!
	var channel;

	// REQUIRED --- DO NOT REMOVE/CHANGE!!
	if (window.parent !== window) {
		channel = Channel.build({
			window: window.parent,
			origin: "*",
			scope: "JSInput"
		});
		channel.bind("getGrade", getGrade);
		channel.bind("getState", getState);
		channel.bind("setState", setState);

	}

	// getState() and setState() are required by the problem type.
	function getState(){
		console.log('getting state');
		return JSON.stringify(JSProblemState);
	}

	// TODO: This needs a major update. Be sure to put all the cards in their correct places.
	function setState() {
		console.log('setting state');
		stateStr = arguments.length === 1 ? arguments[0] : arguments[1];
		JSProblemState = JSON.parse(stateStr);

		// Put the cards in the correct locations based on what the problem state says.
		putMatchesBack();

	}

	// To keep students from seeing the solution that way, just pass the problem state back
	// and use python in the problem XML to analyze it.

	function getGrade() {
		console.log('getting grade');

		// Log the problem state.
		// This is called from the parent window's Javascript so that we can write to the official edX logs.
		parent.logThatThing(JSProblemState);

		// Return the whole problem state.
		return JSON.stringify(JSProblemState);
	}

	// REQUIRED --- DO NOT REMOVE/CHANGE!!
	return {
		getState: getState,
		setState: setState,
		getGrade: getGrade
	};

}());<|endoftext|>