<commit_before>/**
 * Debugger support for skulpt module
 */

var Sk = Sk || {}; //jshint ignore:line

/**
 * Changes :
 *
 * - Suspension.colno -> Suspension.$colno
 * - Suspension.lineno -> Suspension.$lineno
 * - Suspension.filename -> Suspension.$filename
 *
 * Modification on hasOwnProperty (or skulpt crash).
 * Internal call to hasOwnProperty here moved to debuggerHasOwnProperty (origninal of hasOwnProperty).
 *
 * goog.exportSymbol -> Sk.exportSymbol
 *
 * Add :
 * - this.output_callback._onStepError(e);
 * - this.output_callback._onStepSuccess(e);
 */

var DEBUG_DEBUGGER = false;
var debuggerLog = function () {
  if (DEBUG_DEBUGGER) {
    // 1. Convert args to a normal array
    var args = Array.prototype.slice.call(arguments);

    // 2. Prepend log prefix log string
    args.unshift("[debugger.js] ");

    // 3. Pass along arguments to console.log
    console.log.apply(console, args);
  }
};

function hasOwnProperty(prop) {
  var obj = this;
  var proto = obj.constructor.prototype;
  return (prop in obj) &&
      (!(prop in proto) || proto[prop] !== obj[prop]);
}

function debuggerHasOwnProperty(obj, prop) {
  var proto = obj.constructor.prototype;
  return (prop in obj) &&
      (!(prop in proto) || proto[prop] !== obj[prop]);
}

Sk.Breakpoint = function (filename, lineno, colno) {
  this.filename = filename;
  this.lineno = lineno;
  this.colno = colno;
  this.enabled = true;
  this.ignore_count = 0;
};

Sk.Debugger = function (filename, output_callback) {
  this.dbg_breakpoints = {};
  this.tmp_breakpoints = {};
  this.suspension_stack = [];
  this.current_suspension = -1;
  this.eval_callback = null;
  this.suspension = null;
  this.output_callback = output_callback;
  this.step_mode = false;
  this.filename = filename;

  /**
   * Contains the last references of objects that will be retrieved by Skulpt's promises.
   *
   * This is required because the way Skulpt handles classes is so as it stores the reference of
   * the class before calling a method, and then retrieves it after. Since the codecast implementation
   * modify the references each time something is modified into an object, we need to get the updated
   * object, hence the last reference of that object when we want to get the result of the promise.
   *
   * Storing references of objects would disable the garbage collector to clear those objects when necessary,
   * so we also want to clean those references when there are not needed anymore.
   * Since it's possible to have a method of an object that calls another, or itself recursively, we store
   * the number of use of the reference, so we know when we can safely clear the reference.
   *
   * {
   *     UUID_OBJ_1 : {
   *         reference: The object 1,
   *         nb: The number of use
   *     },
   *     UUID_OBJ_2 : {
   *         reference: The object 2,
   *         nb: The number of use
   *     }
   * }
   *
   * When nb becomes 0, we can remove the reference as we don't need it anymore.
   */
  this._promise_references = {};
};

/**
 * Add a reference of an object that will be retrieved later using a Skulpt promise, and which
 * may later have a new reference.
 *
 * @param object The object.
 */
Sk.Debugger.prototype.registerPromiseReference = function (object) {
  if (object.hasOwnProperty('_uuid')) {
    if (!this._promise_references.hasOwnProperty(object._uuid)) {
      this._promise_references[object._uuid] = {
        reference: object,
        nb: 0
      }
    }

    this._promise_references[object._uuid].nb++;
  }
};

/**
 * Updates the promise reference of an object if it exists.
 *
 * @param object The new object reference.
 */
Sk.Debugger.prototype.updatePromiseReference = function (object) {
  if (object.hasOwnProperty('_uuid') && this._promise_references.hasOwnProperty(object._uuid)) {
    this._promise_references[object._uuid].reference = object;
  }
};

Sk.Debugger.prototype.print = function (txt) {
  if (this.output_callback != null) {
    //this.output_callback.logPrint(txt + "\n");
  }
};

Sk.Debugger.prototype.get_source_line = function (lineno) {
  if (this.output_callback != null) {
    return this.output_callback.get_source_line(lineno);
  }

  return "";
};

Sk.Debugger.prototype.move_up_the_stack = function () {
  this.current_suspension = Math.min(this.current_suspension + 1, this.suspension_stack.length - 1);
};

Sk.Debugger.prototype.move_down_the_stack = function () {
  this.current_suspension = Math.max(this.current_suspension - 1, 0);
};

Sk.Debugger.prototype.enable_step_mode = function () {
  this.step_mode = true;
};

Sk.Debugger.prototype.disable_step_mode = function () {
  this.step_mode = false;
};

Sk.Debugger.prototype.get_suspension_stack = function () {
  return this.suspension_stack;
};

Sk.Debugger.prototype.get_active_suspension = function () {
  if (this.suspension_stack.length === 0) {
    return null;
  }

  return this.suspension_stack[this.current_suspension];
};

Sk.Debugger.prototype.generate_breakpoint_key = function (filename, lineno, colno) {
  var key = filename + "-" + lineno;
  return key;
};

Sk.Debugger.prototype.check_breakpoints = function (filename, lineno, colno, globals, locals) {
  // debuggerLog('check_breakpoints', filename, lineno, colno, globals, locals);

  // If Step mode is enabled then ignore breakpoints since we will just break
  // at every line.
  return true;
  if (this.step_mode === true) {
    return true;
  }

  var key = this.generate_breakpoint_key(filename, lineno, colno);
  if (debuggerHasOwnProperty(this.dbg_breakpoints, key) &&
      this.dbg_breakpoints[key].enabled === true) {
    var bp = null;
    if (debuggerHasOwnProperty(this.tmp_breakpoints, key)) {
      delete this.dbg_breakpoints[key];
      delete this.tmp_breakpoints[key];
      return true;
    }

    this.dbg_breakpoints[key].ignore_count -= 1;
    this.dbg_breakpoints[key].ignore_count = Math.max(0, this.dbg_breakpoints[key].ignore_count);

    bp = this.dbg_breakpoints[key];
    if (bp.ignore_count === 0) {
      return true;
    } else {
      return false;
    }
  }
  return false;
};

Sk.Debugger.prototype.get_breakpoints_list = function () {
  return this.dbg_breakpoints;
};

Sk.Debugger.prototype.disable_breakpoint = function (filename, lineno, colno) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);

  if (debuggerHasOwnProperty(this.dbg_breakpoints, key)) {
    this.dbg_breakpoints[key].enabled = false;
  }
};

Sk.Debugger.prototype.enable_breakpoint = function (filename, lineno, colno) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);

  if (debuggerHasOwnProperty(this.dbg_breakpoints, key)) {
    this.dbg_breakpoints[key].enabled = true;
  }
};

Sk.Debugger.prototype.clear_breakpoint = function (filename, lineno, colno) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);
  if (debuggerHasOwnProperty(this.dbg_breakpoints, key)) {
    delete this.dbg_breakpoints[key];
    return null;
  } else {
    return "Invalid breakpoint specified: " + filename + " line: " + lineno;
  }
};

Sk.Debugger.prototype.clear_all_breakpoints = function () {
  this.dbg_breakpoints = {};
  this.tmp_breakpoints = {};
};

Sk.Debugger.prototype.set_ignore_count = function (filename, lineno, colno, count) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);
  if (debuggerHasOwnProperty(this.dbg_breakpoints, key)) {
    var bp = this.dbg_breakpoints[key];
    bp.ignore_count = count;
  }
};

Sk.Debugger.prototype.set_condition = function (filename, lineno, colno, lhs, cond, rhs) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);
  var bp;
  if (debuggerHasOwnProperty(this.dbg_breakpoints, key)) {
    // Set a new condition
    bp = this.dbg_breakpoints[key];
  } else {
    bp = new Sk.Breakpoint(filename, lineno, colno);
  }

  bp.condition = new Sk.Condition(lhs, cond, rhs);
  this.dbg_breakpoints[key] = bp;
};

Sk.Debugger.prototype.print_suspension_info = function (suspension) {
  var filename = suspension.$filename;
  var lineno = suspension.$lineno;
  var colno = suspension.$colno;
  if (DEBUG_DEBUGGER) {
    console.log("Hit Breakpoint at <" + filename + "> at line: " + lineno + " column: " + colno + "\n");
    console.log("----------------------------------------------------------------------------------\n");
    console.log(" ==> " + this.get_source_line(lineno - 1) + "\n");
    console.log("----------------------------------------------------------------------------------\n");
    console.log(suspension);
  }
};

Sk.Debugger.prototype.set_suspension = function (suspension) {
  debuggerLog('set_suspension', suspension);

  var parent = null;
  if (!debuggerHasOwnProperty(suspension, "$filename") && suspension.child instanceof Sk.misceval.Suspension) {
    suspension = suspension.child;
  }

  // Pop the last suspension of the stack if there is more than 0
  if (this.suspension_stack.length > 0) {
    this.suspension_stack.pop();
    this.current_suspension -= 1;
  }

  // Unroll the stack to get each suspension.
  while (suspension instanceof Sk.misceval.Suspension) {
    parent = suspension;
    this.suspension_stack.push(parent);
    this.current_suspension += 1;
    suspension = suspension.child;
  }

  suspension = parent;

  this.print_suspension_info(suspension);
};

Sk.Debugger.prototype.add_breakpoint = function (filename, lineno, colno, temporary) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);
  this.dbg_breakpoints[key] = new Sk.Breakpoint(filename, lineno, colno);
  if (temporary) {
    this.tmp_breakpoints[key] = true;
  }
};

Sk.Debugger.prototype.suspension_handler = function (susp) {
  return new Promise(function (resolve, reject) {
    try {
      resolve(susp.resume());
    } catch (e) {
      reject(e);
    }
  });
};

Sk.Debugger.prototype.resume = function (resolve, reject) {
  debuggerLog('resume');

  // Reset the suspension stack to the topmost
  this.current_suspension = this.suspension_stack.length - 1;

  if (this.suspension_stack.length === 0) {
    this.print("No running program");

    if (typeof resolve === 'function') {
      resolve();
    }
  } else {
    var promise = this.suspension_handler(this.get_active_suspension());
    var self = this;
    promise.then(function (value) {
      if (value.data && value.data.promise) {
        // If waiting for input, wait that it has resolved too before continuing.
        value.data.promise.then((inputValue) => {
          // Skulpt is taking the value into the result parameter, so let's put it in !
          value.data.result = inputValue;

          self.success(value, resolve, reject);
        });
      } else if (value.hasOwnProperty('_uuid')) {
        /**
         * In the case the value is a class, its reference may have changed since the
         * creation of the Promise, which is done before the call to a method.
         * We want to get its last reference.
         */
        value = self._promise_references[value._uuid].reference;
        self._promise_references[value._uuid].nb--;
        if (self._promise_references[value._uuid].nb < 1) {
          delete self._promise_references[value._uuid];
        }

        self.success(value, resolve, reject);
      } else {
        self.success(value, resolve, reject);
      }
    }, function (error) {
      /**
       * Note : We call resolve and not reject in case of error because resolve throws an Exception
       * and breaks the player which stops when there is an exception.
       */
      self.error(error, resolve);
    });
  }
};

Sk.Debugger.prototype.pop_suspension_stack = function () {
  this.suspension_stack.pop();
  this.current_suspension -= 1;
};

Sk.Debugger.prototype.success = function (r, resolve, reject) {
  debuggerLog('success', r, resolve);

  if (r instanceof Sk.misceval.Suspension) {
    debuggerLog('success suspension');
    this.set_suspension(r);
    if (this.output_callback != null) {
      this.output_callback._onStepSuccess(resolve);
    }
  } else {
    if (this.suspension_stack.length > 0) {
      // Current suspension needs to be popped of the stack
      this.pop_suspension_stack();

      if (this.suspension_stack.length === 0) {
        debuggerLog('success complete');

        this.print("Program execution complete");

        if (typeof resolve === 'function') {
          resolve();
        }

        return;
      }

      debuggerLog('here we are');

      var parent_suspension = this.get_active_suspension();
      // The child has completed the execution. So override the child's resume
      // so we can continue the execution.
      parent_suspension.child.resume = function () {
        return r;
      };

      this.resume(resolve, reject);
    } else {
      debuggerLog('success complete 2');

      this.print("Program execution complete");

      if (typeof resolve === 'function') {
        resolve();
      }
    }
  }
};

Sk.Debugger.prototype.error = function (e, reject) {
  debuggerLog('error', e);

  if (this.output_callback != null) {
    this.output_callback._onStepError(e, reject);
  }
  this.print("Traceback (most recent call last):");
  for (var idx = 0; idx < e.traceback.length; ++idx) {
    this.print("  File \"" + e.traceback[idx].filename + "\", line " + e.traceback[idx].lineno + ", in <module>");
    var code = this.get_source_line(e.traceback[idx].lineno - 1);
    code = code.trim();
    code = "    " + code;
    this.print(code);
  }

  var err_ty = e.constructor.tp$name;
  for (idx = 0; idx < e.args.v.length; ++idx) {
    this.print(err_ty + ": " + e.args.v[idx].v);
  }

  if (typeof reject === 'function') {
    reject();
  }
};

Sk.Debugger.prototype.asyncToPromise = function (suspendablefn, suspHandlers, debugger_obj) {
  return new Promise(function (resolve, reject) {
    try {
      var r = suspendablefn();

      (function handleResponse(r) {
        try {
          while (r instanceof Sk.misceval.Suspension) {
            debugger_obj.set_suspension(r);
            return;
          }

          resolve(r);
        } catch (e) {
          reject(e);
        }
      })(r);

    } catch (e) {
      reject(e);
    }
  });
};

Sk.Debugger.prototype.execute = function (suspendablefn, suspHandlers) {
  var r = suspendablefn();

  if (r instanceof Sk.misceval.Suspension) {
    this.suspensions.concat(r);
    this.eval_callback(r);
  }
};

Sk.exportSymbol("Sk.Debugger", Sk.Debugger);
<commit_msg>skulpt: Fix debugger on program end<commit_after>/**
 * Debugger support for skulpt module
 */

var Sk = Sk || {}; //jshint ignore:line

/**
 * Changes :
 *
 * - Suspension.colno -> Suspension.$colno
 * - Suspension.lineno -> Suspension.$lineno
 * - Suspension.filename -> Suspension.$filename
 *
 * Modification on hasOwnProperty (or skulpt crash).
 * Internal call to hasOwnProperty here moved to debuggerHasOwnProperty (origninal of hasOwnProperty).
 *
 * goog.exportSymbol -> Sk.exportSymbol
 *
 * Add :
 * - this.output_callback._onStepError(e);
 * - this.output_callback._onStepSuccess(e);
 */

var DEBUG_DEBUGGER = false;
var debuggerLog = function () {
  if (DEBUG_DEBUGGER) {
    // 1. Convert args to a normal array
    var args = Array.prototype.slice.call(arguments);

    // 2. Prepend log prefix log string
    args.unshift("[debugger.js] ");

    // 3. Pass along arguments to console.log
    console.log.apply(console, args);
  }
};

function hasOwnProperty(prop) {
  var obj = this;
  var proto = obj.constructor.prototype;
  return (prop in obj) &&
      (!(prop in proto) || proto[prop] !== obj[prop]);
}

function debuggerHasOwnProperty(obj, prop) {
  var proto = obj.constructor.prototype;
  return (prop in obj) &&
      (!(prop in proto) || proto[prop] !== obj[prop]);
}

Sk.Breakpoint = function (filename, lineno, colno) {
  this.filename = filename;
  this.lineno = lineno;
  this.colno = colno;
  this.enabled = true;
  this.ignore_count = 0;
};

Sk.Debugger = function (filename, output_callback) {
  this.dbg_breakpoints = {};
  this.tmp_breakpoints = {};
  this.suspension_stack = [];
  this.current_suspension = -1;
  this.eval_callback = null;
  this.suspension = null;
  this.output_callback = output_callback;
  this.step_mode = false;
  this.filename = filename;

  /**
   * Contains the last references of objects that will be retrieved by Skulpt's promises.
   *
   * This is required because the way Skulpt handles classes is so as it stores the reference of
   * the class before calling a method, and then retrieves it after. Since the codecast implementation
   * modify the references each time something is modified into an object, we need to get the updated
   * object, hence the last reference of that object when we want to get the result of the promise.
   *
   * Storing references of objects would disable the garbage collector to clear those objects when necessary,
   * so we also want to clean those references when there are not needed anymore.
   * Since it's possible to have a method of an object that calls another, or itself recursively, we store
   * the number of use of the reference, so we know when we can safely clear the reference.
   *
   * {
   *     UUID_OBJ_1 : {
   *         reference: The object 1,
   *         nb: The number of use
   *     },
   *     UUID_OBJ_2 : {
   *         reference: The object 2,
   *         nb: The number of use
   *     }
   * }
   *
   * When nb becomes 0, we can remove the reference as we don't need it anymore.
   */
  this._promise_references = {};
};

/**
 * Add a reference of an object that will be retrieved later using a Skulpt promise, and which
 * may later have a new reference.
 *
 * @param object The object.
 */
Sk.Debugger.prototype.registerPromiseReference = function (object) {
  if (object.hasOwnProperty('_uuid')) {
    if (!this._promise_references.hasOwnProperty(object._uuid)) {
      this._promise_references[object._uuid] = {
        reference: object,
        nb: 0
      }
    }

    this._promise_references[object._uuid].nb++;
  }
};

/**
 * Updates the promise reference of an object if it exists.
 *
 * @param object The new object reference.
 */
Sk.Debugger.prototype.updatePromiseReference = function (object) {
  if (object.hasOwnProperty('_uuid') && this._promise_references.hasOwnProperty(object._uuid)) {
    this._promise_references[object._uuid].reference = object;
  }
};

Sk.Debugger.prototype.print = function (txt) {
  if (this.output_callback != null) {
    this.output_callback.print(txt + "\n");
  }
};

Sk.Debugger.prototype.get_source_line = function (lineno) {
  if (this.output_callback != null) {
    return this.output_callback.get_source_line(lineno);
  }

  return "";
};

Sk.Debugger.prototype.move_up_the_stack = function () {
  this.current_suspension = Math.min(this.current_suspension + 1, this.suspension_stack.length - 1);
};

Sk.Debugger.prototype.move_down_the_stack = function () {
  this.current_suspension = Math.max(this.current_suspension - 1, 0);
};

Sk.Debugger.prototype.enable_step_mode = function () {
  this.step_mode = true;
};

Sk.Debugger.prototype.disable_step_mode = function () {
  this.step_mode = false;
};

Sk.Debugger.prototype.get_suspension_stack = function () {
  return this.suspension_stack;
};

Sk.Debugger.prototype.get_active_suspension = function () {
  if (this.suspension_stack.length === 0) {
    return null;
  }

  return this.suspension_stack[this.current_suspension];
};

Sk.Debugger.prototype.generate_breakpoint_key = function (filename, lineno, colno) {
  var key = filename + "-" + lineno;
  return key;
};

Sk.Debugger.prototype.check_breakpoints = function (filename, lineno, colno, globals, locals) {
  // debuggerLog('check_breakpoints', filename, lineno, colno, globals, locals);

  // If Step mode is enabled then ignore breakpoints since we will just break
  // at every line.
  return true;
  if (this.step_mode === true) {
    return true;
  }

  var key = this.generate_breakpoint_key(filename, lineno, colno);
  if (debuggerHasOwnProperty(this.dbg_breakpoints, key) &&
      this.dbg_breakpoints[key].enabled === true) {
    var bp = null;
    if (debuggerHasOwnProperty(this.tmp_breakpoints, key)) {
      delete this.dbg_breakpoints[key];
      delete this.tmp_breakpoints[key];
      return true;
    }

    this.dbg_breakpoints[key].ignore_count -= 1;
    this.dbg_breakpoints[key].ignore_count = Math.max(0, this.dbg_breakpoints[key].ignore_count);

    bp = this.dbg_breakpoints[key];
    if (bp.ignore_count === 0) {
      return true;
    } else {
      return false;
    }
  }
  return false;
};

Sk.Debugger.prototype.get_breakpoints_list = function () {
  return this.dbg_breakpoints;
};

Sk.Debugger.prototype.disable_breakpoint = function (filename, lineno, colno) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);

  if (debuggerHasOwnProperty(this.dbg_breakpoints, key)) {
    this.dbg_breakpoints[key].enabled = false;
  }
};

Sk.Debugger.prototype.enable_breakpoint = function (filename, lineno, colno) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);

  if (debuggerHasOwnProperty(this.dbg_breakpoints, key)) {
    this.dbg_breakpoints[key].enabled = true;
  }
};

Sk.Debugger.prototype.clear_breakpoint = function (filename, lineno, colno) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);
  if (debuggerHasOwnProperty(this.dbg_breakpoints, key)) {
    delete this.dbg_breakpoints[key];
    return null;
  } else {
    return "Invalid breakpoint specified: " + filename + " line: " + lineno;
  }
};

Sk.Debugger.prototype.clear_all_breakpoints = function () {
  this.dbg_breakpoints = {};
  this.tmp_breakpoints = {};
};

Sk.Debugger.prototype.set_ignore_count = function (filename, lineno, colno, count) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);
  if (debuggerHasOwnProperty(this.dbg_breakpoints, key)) {
    var bp = this.dbg_breakpoints[key];
    bp.ignore_count = count;
  }
};

Sk.Debugger.prototype.set_condition = function (filename, lineno, colno, lhs, cond, rhs) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);
  var bp;
  if (debuggerHasOwnProperty(this.dbg_breakpoints, key)) {
    // Set a new condition
    bp = this.dbg_breakpoints[key];
  } else {
    bp = new Sk.Breakpoint(filename, lineno, colno);
  }

  bp.condition = new Sk.Condition(lhs, cond, rhs);
  this.dbg_breakpoints[key] = bp;
};

Sk.Debugger.prototype.print_suspension_info = function (suspension) {
  var filename = suspension.$filename;
  var lineno = suspension.$lineno;
  var colno = suspension.$colno;
  if (DEBUG_DEBUGGER) {
    console.log("Hit Breakpoint at <" + filename + "> at line: " + lineno + " column: " + colno + "\n");
    console.log("----------------------------------------------------------------------------------\n");
    console.log(" ==> " + this.get_source_line(lineno - 1) + "\n");
    console.log("----------------------------------------------------------------------------------\n");
    console.log(suspension);
  }
};

Sk.Debugger.prototype.set_suspension = function (suspension) {
  debuggerLog('set_suspension', suspension);

  var parent = null;
  if (!debuggerHasOwnProperty(suspension, "$filename") && suspension.child instanceof Sk.misceval.Suspension) {
    suspension = suspension.child;
  }

  // Pop the last suspension of the stack if there is more than 0
  if (this.suspension_stack.length > 0) {
    this.suspension_stack.pop();
    this.current_suspension -= 1;
  }

  // Unroll the stack to get each suspension.
  while (suspension instanceof Sk.misceval.Suspension) {
    parent = suspension;
    this.suspension_stack.push(parent);
    this.current_suspension += 1;
    suspension = suspension.child;
  }

  suspension = parent;

  this.print_suspension_info(suspension);
};

Sk.Debugger.prototype.add_breakpoint = function (filename, lineno, colno, temporary) {
  var key = this.generate_breakpoint_key(filename, lineno, colno);
  this.dbg_breakpoints[key] = new Sk.Breakpoint(filename, lineno, colno);
  if (temporary) {
    this.tmp_breakpoints[key] = true;
  }
};

Sk.Debugger.prototype.suspension_handler = function (susp) {
  return new Promise(function (resolve, reject) {
    try {
      resolve(susp.resume());
    } catch (e) {
      reject(e);
    }
  });
};

Sk.Debugger.prototype.resume = function (resolve, reject) {
  debuggerLog('resume');

  // Reset the suspension stack to the topmost
  this.current_suspension = this.suspension_stack.length - 1;

  if (this.suspension_stack.length === 0) {
    this.print("No running program");

    if (typeof resolve === 'function') {
      resolve();
    }
  } else {
    var promise = this.suspension_handler(this.get_active_suspension());
    var self = this;
    promise.then(function (value) {
      if (value.data && value.data.promise) {
        // If waiting for input, wait that it has resolved too before continuing.
        value.data.promise.then((inputValue) => {
          // Skulpt is taking the value into the result parameter, so let's put it in !
          value.data.result = inputValue;

          self.success(value, resolve, reject);
        });
      } else if (value.hasOwnProperty('_uuid')) {
        /**
         * In the case the value is a class, its reference may have changed since the
         * creation of the Promise, which is done before the call to a method.
         * We want to get its last reference.
         */
        value = self._promise_references[value._uuid].reference;
        self._promise_references[value._uuid].nb--;
        if (self._promise_references[value._uuid].nb < 1) {
          delete self._promise_references[value._uuid];
        }

        self.success(value, resolve, reject);
      } else {
        self.success(value, resolve, reject);
      }
    }, function (error) {
      /**
       * Note : We call resolve and not reject in case of error because resolve throws an Exception
       * and breaks the player which stops when there is an exception.
       */
      self.error(error, resolve);
    });
  }
};

Sk.Debugger.prototype.pop_suspension_stack = function () {
  this.suspension_stack.pop();
  this.current_suspension -= 1;
};

Sk.Debugger.prototype.success = function (r, resolve, reject) {
  debuggerLog('success', r, resolve);

  if (r instanceof Sk.misceval.Suspension) {
    debuggerLog('success suspension');
    this.set_suspension(r);
    if (this.output_callback != null) {
      this.output_callback._onStepSuccess(resolve);
    }
  } else {
    if (this.suspension_stack.length > 0) {
      // Current suspension needs to be popped of the stack
      this.pop_suspension_stack();

      if (this.suspension_stack.length === 0) {
        debuggerLog('success complete');

        this.print("Program execution complete");

        if (typeof resolve === 'function') {
          resolve();
        }

        return;
      }

      debuggerLog('here we are');

      var parent_suspension = this.get_active_suspension();
      // The child has completed the execution. So override the child's resume
      // so we can continue the execution.
      parent_suspension.child.resume = function () {
        return r;
      };

      this.resume(resolve, reject);
    } else {
      debuggerLog('success complete 2');

      this.print("Program execution complete");

      if (typeof resolve === 'function') {
        resolve();
      }
    }
  }
};

Sk.Debugger.prototype.error = function (e, reject) {
  debuggerLog('error', e);

  if (this.output_callback != null) {
    this.output_callback._onStepError(e, reject);
  }
  this.print("Traceback (most recent call last):");
  for (var idx = 0; idx < e.traceback.length; ++idx) {
    this.print("  File \"" + e.traceback[idx].filename + "\", line " + e.traceback[idx].lineno + ", in <module>");
    var code = this.get_source_line(e.traceback[idx].lineno - 1);
    code = code.trim();
    code = "    " + code;
    this.print(code);
  }

  var err_ty = e.constructor.tp$name;
  for (idx = 0; idx < e.args.v.length; ++idx) {
    this.print(err_ty + ": " + e.args.v[idx].v);
  }

  if (typeof reject === 'function') {
    reject();
  }
};

Sk.Debugger.prototype.asyncToPromise = function (suspendablefn, suspHandlers, debugger_obj) {
  return new Promise(function (resolve, reject) {
    try {
      var r = suspendablefn();

      (function handleResponse(r) {
        try {
          while (r instanceof Sk.misceval.Suspension) {
            debugger_obj.set_suspension(r);
            return;
          }

          resolve(r);
        } catch (e) {
          reject(e);
        }
      })(r);

    } catch (e) {
      reject(e);
    }
  });
};

Sk.Debugger.prototype.execute = function (suspendablefn, suspHandlers) {
  var r = suspendablefn();

  if (r instanceof Sk.misceval.Suspension) {
    this.suspensions.concat(r);
    this.eval_callback(r);
  }
};

Sk.exportSymbol("Sk.Debugger", Sk.Debugger);
<|endoftext|>


<commit_before>// Initialization --------------------------------------------------------------
$.getJSON("../config.json", function(data) {
  console.log("initialized");
  SC.initialize({
    client_id: data["soundcloud_client_id"]
  });
});

var commands = new commands(chrome);
var controller = new controller(chrome);

chrome.storage.sync.clear();

// GLOBALS - since the background page keeps running, these variables will
// be tracked and used.
var stream = null,
  currentTracks = null,
  currentSongIdx = -1;
// -----------------------------------------------------------------------------
chrome.runtime.onConnect.addListener(function(port) {
  clearPagination();
  displayCurrentSong(port);
  displayPreviousSearch(port);
  port.onMessage.addListener(messageHandler(port));
});

function messageHandler(port) {
  var listener = function(msg) {
    var content = msg.content;
    var message = msg.message;

    switch (message) {
      case "play-song":
        playSong(content.index, port);
        break;
      case "search":
        var searchInfo = {
          q: content,
          limit: 10,
          linked_partitioning: 1
        };

        SC.get("/tracks", searchInfo).then(function(res) {
          chrome.storage.sync.set({
            previousSearch: searchInfo
          });
          displayTracks(port, res);
        });
        break;
      case "toggle":
        if (!!stream) {
          stream.toggle();
        }
        break;
      case "next-track":
        if (currentSongIdx >= currentTracks.length) {
          break;
        }
        playSong(++currentSongIdx, port);

        break;
      case "prev-track":
        if (currentSongIdx <= 0) {
          break;
        }
        playSong(--currentSongIdx, port);

        break;
      case "next-tracks":
        var currentTrackHref = content;
        $.getJSON(currentTrackHref, function(res) {
          displayTracks(port, res);
          chrome.storage.sync.set({
            prevTracks: getPrevHref(currentTrackHref)
          });
        });
        break;
      case "prev-tracks":
        chrome.storage.sync.get("prevTracks", function(obj) {
          if (chrome.runtime.lastError == null && !$.isEmptyObject(obj)) {
            var currentTrackHref = obj.prevTracks;
            $.getJSON(obj.prevTracks, function(res) {
              displayTracks(port, res);
              chrome.storage.sync.set({
                prevTracks: getPrevHref(currentTrackHref)
              });
            });
          }
        });
        break;
      case "replay":
        if (!!!stream) {
          break;
        }
        stream.on("time", function() {
          var diff = stream.controller.getDuration() - stream.currentTime();
          if (diff < 1000) {
            console.log("seeking to beginning");
            stream.pause();
            stream.seek(0);
            stream.play();
            stream.off("time");
          }
        });
        break;
      default:
        break;
    }
  };
  return listener;
}

function playSong(index, port) {
  if (!!stream) {
    stream.pause();
  }

  var track = currentTracks[index];
  chrome.storage.sync.set({
    currentTrack: track
  });
  try {
    displayCurrentExtensionTrack(port);
  } catch (error) {}

  SC.stream("/tracks/" + track.id).then(
    function(player) {
      stream = player;
      currentSongIdx = index;

      stream.play();

      stream.on("finish", function() {
        console.log("finished");
        chrome.storage.sync.set({
          currentTrack: {}
        });

        // cached result will require a reset
        stream.off("finish");
        stream.seek(0);

        // TODO: automatically display new current song when popup is open
        // TODO: out-of-bounds handling that would require pagination
        playSong(index + 1, port);
      });
    },
    function(error) {
      console.log("Streaming error: " + error);
    }
  );
}

function displayTracks(port, tracksResp) {
  currentTracks = tracksResp.collection;
  port.postMessage({
    message: "display-tracks",
    content: tracksResp
  });
}

function displayPreviousSearch(port) {
  chrome.storage.sync.get(["previousSearch"], function(obj) {
    if (chrome.runtime.lastError == null && "previousSearch" in obj) {
      SC.get("/tracks", obj.previousSearch).then(function(res) {
        displayTracks(port, res);
      });
    }
  });
}

function displayCurrentSong(port) {
  chrome.tabs.query(
    {
      url: "*://soundcloud.com/*"
    },
    function(soundcloudTabs) {
      if (soundcloudTabs.length > 0) {
        var currentlyPlaying = null;
        // TODO: try to not have to look thru all the tabs every time. Caching?
        var audibleTabs = soundcloudTabs.filter(function(t) {
          return t.audible == true;
        });
        if (audibleTabs.length == 0) {
          displayCurrentExtensionTrack(port);
          return;
        }
        port.postMessage({
          message: "display-current-track",
          content: {
            track: audibleTabs[0],
            isPlaying: true
          }
        });
      } else {
        displayCurrentExtensionTrack(port);
      }
    }
  );
}

function displayCurrentExtensionTrack(port) {
  // TODO: determine if port is connected before trying to read from storage
  chrome.storage.sync.get(["currentTrack"], function(obj) {
    if (
      chrome.runtime.lastError == null &&
      "currentTrack" in obj &&
      !$.isEmptyObject(obj.currentTrack)
    ) {
      port.postMessage({
        message: "display-current-track",
        content: {
          track: obj.currentTrack,
          isPlaying: !!stream && stream.controller.getState() === "playing"
        }
      });
    }
  });
}

function clearPagination() {
  chrome.storage.sync.remove(["prevTracks", "nextTracks"]);
}

// Assumes valid API url from soundcloud
function getPrevHref(url) {
  var oldUrl = url.split("?");
  var params = $.parseParams(oldUrl[1]);
  if (params.offset > 0) {
    params.offset =
      Number.parseInt(params.offset) - Number.parseInt(params.limit);
  }
  return oldUrl[0] + "?" + $.param(params);
}

function getNextHref(url) {
  var oldUrl = url.split("?");
  var params = $.parseParams(oldUrl[1]);
  params.offset =
    Number.parseInt(params.offset) + Number.parseInt(params.limit);
  return oldUrl[0] + "?" + $.param(params);
}

// --------- keyboard shortcut listener -------------------------------------
chrome.commands.onCommand.addListener(function(command) {
  switch (command) {
    case "open-soundcloud":
      commands.switchToSoundcloudTab();
      break;
    case "previous-location":
      commands.switchToPreviousLocation();
    default:
      break;
  }
});

// https://gist.github.com/kares/956897
(function($) {
  var re = /([^&=]+)=?([^&]*)/g;
  var decodeRE = /\+/g; // Regex for replacing addition symbol with a space
  var decode = function(str) {
    return decodeURIComponent(str.replace(decodeRE, " "));
  };
  $.parseParams = function(query) {
    var params = {},
      e;
    while ((e = re.exec(query))) {
      var k = decode(e[1]),
        v = decode(e[2]);
      if (k.substring(k.length - 2) === "[]") {
        k = k.substring(0, k.length - 2);
        (params[k] || (params[k] = [])).push(v);
      } else params[k] = v;
    }
    return params;
  };
})(jQuery);
<commit_msg>try/catch when displaying current track<commit_after>// Initialization --------------------------------------------------------------
$.getJSON("../config.json", function(data) {
  console.log("initialized");
  SC.initialize({
    client_id: data["soundcloud_client_id"]
  });
});

var commands = new commands(chrome);
var controller = new controller(chrome);

chrome.storage.sync.clear();

// GLOBALS - since the background page keeps running, these variables will
// be tracked and used.
var stream = null,
  currentTracks = null,
  currentSongIdx = -1;
// -----------------------------------------------------------------------------
chrome.runtime.onConnect.addListener(function(port) {
  clearPagination();
  displayCurrentSong(port);
  displayPreviousSearch(port);
  port.onMessage.addListener(messageHandler(port));
});

function messageHandler(port) {
  var listener = function(msg) {
    var content = msg.content;
    var message = msg.message;

    switch (message) {
      case "play-song":
        playSong(content.index, port);
        break;
      case "search":
        var searchInfo = {
          q: content,
          limit: 10,
          linked_partitioning: 1
        };

        SC.get("/tracks", searchInfo).then(function(res) {
          chrome.storage.sync.set({
            previousSearch: searchInfo
          });
          displayTracks(port, res);
        });
        break;
      case "toggle":
        if (!!stream) {
          stream.toggle();
        }
        break;
      case "next-track":
        if (currentSongIdx >= currentTracks.length) {
          break;
        }
        playSong(++currentSongIdx, port);

        break;
      case "prev-track":
        if (currentSongIdx <= 0) {
          break;
        }
        playSong(--currentSongIdx, port);

        break;
      case "next-tracks":
        var currentTrackHref = content;
        $.getJSON(currentTrackHref, function(res) {
          displayTracks(port, res);
          chrome.storage.sync.set({
            prevTracks: getPrevHref(currentTrackHref)
          });
        });
        break;
      case "prev-tracks":
        chrome.storage.sync.get("prevTracks", function(obj) {
          if (chrome.runtime.lastError == null && !$.isEmptyObject(obj)) {
            var currentTrackHref = obj.prevTracks;
            $.getJSON(obj.prevTracks, function(res) {
              displayTracks(port, res);
              chrome.storage.sync.set({
                prevTracks: getPrevHref(currentTrackHref)
              });
            });
          }
        });
        break;
      case "replay":
        if (!!!stream) {
          break;
        }
        stream.on("time", function() {
          var diff = stream.controller.getDuration() - stream.currentTime();
          if (diff < 1000) {
            console.log("seeking to beginning");
            stream.pause();
            stream.seek(0);
            stream.play();
            stream.off("time");
          }
        });
        break;
      default:
        break;
    }
  };
  return listener;
}

function playSong(index, port) {
  if (!!stream) {
    stream.pause();
  }

  var track = currentTracks[index];
  chrome.storage.sync.set({
    currentTrack: track
  });
  try {
    displayCurrentExtensionTrack(port);
  } catch (error) {}

  SC.stream("/tracks/" + track.id).then(
    function(player) {
      stream = player;
      currentSongIdx = index;

      stream.play();

      stream.on("finish", function() {
        chrome.storage.sync.set({
          currentTrack: {}
        });

        // cached result will require a reset
        stream.off("finish");
        stream.seek(0);

        // TODO: automatically display new current song when popup is open
        // TODO: out-of-bounds handling that would require pagination
        playSong(index + 1, port);
      });
    },
    function(error) {
      console.log("Streaming error: " + error);
    }
  );
}

function displayTracks(port, tracksResp) {
  currentTracks = tracksResp.collection;
  port.postMessage({
    message: "display-tracks",
    content: tracksResp
  });
}

function displayPreviousSearch(port) {
  chrome.storage.sync.get(["previousSearch"], function(obj) {
    if (chrome.runtime.lastError == null && "previousSearch" in obj) {
      SC.get("/tracks", obj.previousSearch).then(function(res) {
        displayTracks(port, res);
      });
    }
  });
}

function displayCurrentSong(port) {
  chrome.tabs.query(
    {
      url: "*://soundcloud.com/*"
    },
    function(soundcloudTabs) {
      if (soundcloudTabs.length > 0) {
        var currentlyPlaying = null;
        // TODO: try to not have to look thru all the tabs every time. Caching?
        var audibleTabs = soundcloudTabs.filter(function(t) {
          return t.audible == true;
        });
        if (audibleTabs.length == 0) {
          displayCurrentExtensionTrack(port);
          return;
        }
        port.postMessage({
          message: "display-current-track",
          content: {
            track: audibleTabs[0],
            isPlaying: true
          }
        });
      } else {
        displayCurrentExtensionTrack(port);
      }
    }
  );
}

function displayCurrentExtensionTrack(port) {
  chrome.storage.sync.get(["currentTrack"], function(obj) {
    if (
      chrome.runtime.lastError == null &&
      "currentTrack" in obj &&
      !$.isEmptyObject(obj.currentTrack)
    ) {
      try {
        port.postMessage({
          message: "display-current-track",
          content: {
            track: obj.currentTrack,
            isPlaying: !!stream && stream.controller.getState() === "playing"
          }
        });
      } catch (e) {
        console.log("Couldn't display track: " + e);
      }
    }
  });
}

function clearPagination() {
  chrome.storage.sync.remove(["prevTracks", "nextTracks"]);
}

// Assumes valid API url from soundcloud
function getPrevHref(url) {
  var oldUrl = url.split("?");
  var params = $.parseParams(oldUrl[1]);
  if (params.offset > 0) {
    params.offset =
      Number.parseInt(params.offset) - Number.parseInt(params.limit);
  }
  return oldUrl[0] + "?" + $.param(params);
}

function getNextHref(url) {
  var oldUrl = url.split("?");
  var params = $.parseParams(oldUrl[1]);
  params.offset =
    Number.parseInt(params.offset) + Number.parseInt(params.limit);
  return oldUrl[0] + "?" + $.param(params);
}

// --------- keyboard shortcut listener -------------------------------------
chrome.commands.onCommand.addListener(function(command) {
  switch (command) {
    case "open-soundcloud":
      commands.switchToSoundcloudTab();
      break;
    case "previous-location":
      commands.switchToPreviousLocation();
    default:
      break;
  }
});

// https://gist.github.com/kares/956897
(function($) {
  var re = /([^&=]+)=?([^&]*)/g;
  var decodeRE = /\+/g; // Regex for replacing addition symbol with a space
  var decode = function(str) {
    return decodeURIComponent(str.replace(decodeRE, " "));
  };
  $.parseParams = function(query) {
    var params = {},
      e;
    while ((e = re.exec(query))) {
      var k = decode(e[1]),
        v = decode(e[2]);
      if (k.substring(k.length - 2) === "[]") {
        k = k.substring(0, k.length - 2);
        (params[k] || (params[k] = [])).push(v);
      } else params[k] = v;
    }
    return params;
  };
})(jQuery);
<|endoftext|>


<commit_before>import React, {useState} from 'react'
import Hook from 'util/hook'
import Async from 'client/components/Async'
import config from 'gen/config'

const {UPLOAD_URL} = config

const FileDrop = (props) => <Async {...props}
                                   load={import(/* webpackChunkName: "extra" */ '../../client/components/FileDrop')}/>
const TypePicker = (props) => <Async {...props}
                                     load={import(/* webpackChunkName: "admin" */ '../../client/components/TypePicker')}/>
const SimpleSwitch = (props) => <Async {...props} expose="SimpleSwitch"
                                       load={import(/* webpackChunkName: "admin" */ '../../gensrc/ui/admin')}/>

export default () => {

    // add an extra column for Media at the beginning
    Hook.on('TypeTableColumns', ({type, columns}) => {
        if (type === 'Media') {
            columns.splice(1, 0, {title: 'Data', id: 'data'})
        }
    })

    // add some extra data to the table
    Hook.on('TypeTable', ({type, dataSource, data, container}) => {
        if (type === 'Media') {
            dataSource.forEach((d, i) => {
                const item = data.results[i]
                if( item ) {
                    const mimeType = item.mimeType ? item.mimeType.split('/') : ['file']

                    d.data =

                            {
                                (mimeType[0] === 'image' ?

                                        :

 1 ? mimeType[1] : 'doc'}>

                                )
                            }

                }
            })
        }
    })

    // add some extra data to the table
    Hook.on('TypeTableAction', function ({type, actions}) {
        if (type === 'Media') {

            actions.unshift({
                name: 'Upload new Media', onClick: () => {
                    setTimeout(() => {
                        this.setState({createEditDialog: true, createEditDialogParams: 'upload'})
                    }, 300)
                }
            })
        }
    })

    // add some extra data to the table
    Hook.on('TypeCreateEditDialog', function ({type, props, dataToEdit}) {
        if (type === 'Media' && !dataToEdit && this.state.createEditDialogParams === 'upload') {
            // remove save button
            props.actions.splice(1, 1)

            const MediaUploader = () => {
                const [conversion, setConversion] = useState(
                    []
                )

                const [group, setGroup] = useState(
                    []
                )

                const [useCdn, setUseCdn] = useState(
                    true
                )
                return (
                    [

                            <TypePicker onChange={(e) => {
                                setConversion(JSON.parse(e.target.value[0].conversion))
                            }} name="conversion" placeholder="Select a conversion"
                                        type="MediaConversion"/>

                            <TypePicker onChange={(e) => {
                                const groups = []
                                e.target.value.forEach(value=>{
                                    groups.push(value._id)
                                })
                                setGroup(groups)
                            }} multi={true} name="group" placeholder="Select a group"
                                        type="MediaGroup"/>

,
                        <SimpleSwitch key="useCdn" label="Upload file to CDN" name="useCdn"
                                      onChange={(e)=>{
                                          setUseCdn(e.target.checked)
                                      }} checked={useCdn}/>,
                        <FileDrop key="fileDrop" multi={true} conversion={conversion} accept="*/*"
                                  uploadTo="/graphql/upload" resizeImages={true}
                                  data={{group, useCdn}}
                                  onSuccess={r => {
                                      this.setState({createEditDialog: false, createEditDialogParams: null})

                                      this.getData(this.pageParams, false)
                                      // TODO: but it directly into the store instead of reload
                                      //const queries = this.getQueries(type), storeKey = this.getStoreKey(type)

                                  }}/>]
                )
            }

            props.children = <MediaUploader/>
        }
    })

}
<commit_msg>Small improvements<commit_after>import React, {useState} from 'react'
import Hook from 'util/hook'
import Async from 'client/components/Async'
import config from 'gen/config'

const {UPLOAD_URL} = config

const FileDrop = (props) => <Async {...props}
                                   load={import(/* webpackChunkName: "admin" */ '../../client/components/FileDrop')}/>
const TypePicker = (props) => <Async {...props}
                                     load={import(/* webpackChunkName: "admin" */ '../../client/components/TypePicker')}/>
const SimpleSwitch = (props) => <Async {...props} expose="SimpleSwitch"
                                       load={import(/* webpackChunkName: "admin" */ '../../gensrc/ui/admin')}/>

export default () => {

    // add an extra column for Media at the beginning
    Hook.on('TypeTableColumns', ({type, columns}) => {
        if (type === 'Media') {
            columns.splice(1, 0, {title: 'Data', id: 'data'})
        }
    })

    // add some extra data to the table
    Hook.on('TypeTable', ({type, dataSource, data, container}) => {
        if (type === 'Media') {
            dataSource.forEach((d, i) => {
                const item = data.results[i]
                if( item ) {
                    const mimeType = item.mimeType ? item.mimeType.split('/') : ['file']

                    d.data =

                            {
                                (mimeType[0] === 'image' ?

                                        :

 1 ? mimeType[1] : 'doc'}>

                                )
                            }

                }
            })
        }
    })

    // add some extra data to the table
    Hook.on('TypeTableAction', function ({type, actions}) {
        if (type === 'Media') {

            actions.unshift({
                name: 'Upload new Media', onClick: () => {
                    setTimeout(() => {
                        this.setState({createEditDialog: true, createEditDialogParams: 'upload'})
                    }, 300)
                }
            })
        }
    })

    // add some extra data to the table
    Hook.on('TypeCreateEditDialog', function ({type, props, dataToEdit}) {
        if (type === 'Media' && !dataToEdit && this.state.createEditDialogParams === 'upload') {
            // remove save button
            props.actions.splice(1, 1)

            const MediaUploader = () => {
                const [conversion, setConversion] = useState(
                    []
                )

                const [group, setGroup] = useState(
                    []
                )

                const [useCdn, setUseCdn] = useState(
                    true
                )
                return (
                    [

                            <TypePicker onChange={(e) => {
                                setConversion(JSON.parse(e.target.value[0].conversion))
                            }} name="conversion" placeholder="Select a conversion"
                                        type="MediaConversion"/>

                            <TypePicker onChange={(e) => {
                                const groups = []
                                e.target.value.forEach(value=>{
                                    groups.push(value._id)
                                })
                                setGroup(groups)
                            }} multi={true} name="group" placeholder="Select a group"
                                        type="MediaGroup"/>

,
                        <SimpleSwitch key="useCdn" label="Upload file to CDN" name="useCdn"
                                      onChange={(e)=>{
                                          setUseCdn(e.target.checked)
                                      }} checked={useCdn}/>,
                        <FileDrop key="fileDrop" multi={true} conversion={conversion} accept="*/*"
                                  uploadTo="/graphql/upload" resizeImages={true}
                                  data={{group, useCdn}}
                                  onSuccess={r => {
                                      this.setState({createEditDialog: false, createEditDialogParams: null})

                                      this.getData(this.pageParams, false)
                                      // TODO: but it directly into the store instead of reload
                                      //const queries = this.getQueries(type), storeKey = this.getStoreKey(type)

                                  }}/>]
                )
            }

            props.children = <MediaUploader/>
        }
    })

}
<|endoftext|>


<commit_before>
const employeeInfo = {
  importantPeople: [
    {
      name: 'Pete Levin',
      position: 'Co-Founder',
      photoName: 'Pete',
      isImportant: true
    },
    {
      name: 'Ted Kacandes',
      position: 'Co-Flounder',
      photoName: 'Ted',
      isImportant: true
    },
    {
      name: 'Howie Kleinberg',
      position: 'COO & President',
      photoName: 'Howie',
      isImportant: true
    },
    {
      name: 'Mike Molnar',
      position: 'Managing Partner',
      photoName: 'Mike',
      isImportant: true
    },
    {
      name: 'Jerry Levin',
      position: 'Chief Financial Officer',
      photoName: 'Jerry',
      isImportant: true
    },
    {
      name: 'Sean Lynam',
      position: 'Director of Marketing',
      photoName: 'Sean',
      isImportant: true
    },
    {
      name: 'Robert Sosin',
      position: 'Senior Creative Director',
      photoName: 'RobertS',
      isImportant: true
    },
    {
      name: 'Clayton Benn',
      position: 'Director of Video Production',
      photoName: 'Clayton',
      isImportant: true
    }
  ],
  otherPeople: [
    {
      name: 'Adrien Hobbs',
      position: 'Developer',
      photoName: 'Adrien'
    },
    {
      name: 'Matt Will',
      position: 'Art Director',
      photoName: 'Matt'
    },
    {
      name: 'Ann Ketly',
      position: 'Office Manager',
      photoName: 'Ann'
    },
    {
      name: 'Andre Cole',
      position: 'Video Editor',
      photoName: 'Andre'
    },
    {
      name: 'Lindsey Cirmotich',
      position: 'Junior Designer',
      photoName: 'Lindsey'
    },
    {
      name: 'Emily Stone',
      position: 'Designer',
      photoName: 'EmilyS'
    },
    // {
    //   name: 'Gina Amsellem',
    //   position: 'Social Designer',
    //   photoName: 'GinaA'
    // },
    {
      name: 'Brit Mullen',
      position: 'Social Designer',
      photoName: 'Brit'
    },
    {
      name: 'Ben Tuber',
      position: 'Art Director',
      photoName: 'Ben'
    },
    {
      name: 'Aaron J. Perez',
      position: 'Digital Strategist',
      photoName: 'Aaron'
    },
    {
      name: 'Daniela Guernica',
      position: 'Community Manager',
      photoName: 'Danielle'
    },
    {
      name: 'Emily Babich',
      position: 'Junior Video Producer',
      photoName: 'EmilyB'
    },
    {
      name: 'Eric Drumm',
      position: 'Digital Strategist',
      photoName: 'Eric'
    },
    {
      name: 'Erin Resnick',
      position: 'Community Manager',
      photoName: 'Erin'
    },
    {
      name: 'Kendell Burton',
      position: 'Social Designer',
      photoName: 'Kendell'
    },
    {
      name: 'Lauren Farmer',
      position: 'Digital Strategist',
      photoName: 'Lauren'
    },
    {
      name: 'Jennifer Fink',
      position: 'Digital Strategist',
      photoName: 'Jennifer'
    },
    {
      name: 'Peter Kondratowicz',
      position: 'Senior Designer',
      photoName: 'PK'
    },
    {
      name: 'Sarah Pine',
      position: 'Digital Strategist',
      photoName: 'Sarah'
    },
    {
      name: 'Simona Belau',
      position: 'Digital Strategist',
      photoName: 'Simona'
    },
    {
      name: 'Stephanie Rogers',
      position: 'Sr. Account Manager',
      photoName: 'Steph'
    },
    {
      name: 'Tara Lewis',
      position: 'Sr. Project Manager',
      photoName: 'Tara'
    },
    {
      name: 'Sean Orr',
      position: 'Video Editor',
      photoName: 'SeanO'
    },
    {
      name: 'Paula Ersly',
      position: 'Community Manager',
      photoName: 'Paula'
    },
    {
      name: 'Eric Goldie',
      position: 'Community Manager',
      photoName: 'EricG'
    },
    {
      name: 'Paola Bossola',
      position: 'Video Producer',
      photoName: 'Paola'
    },
    {
      name: 'Zoe Greenblatt',
      position: 'Community Manager',
      photoName: 'Zoe'
    },
    {
      name: 'Casey De Pont',
      position: 'Art Director',
      photoName: 'Casey'
    },
    {
      name: 'Nöel Anderson',
      position: 'Motion Designer',
      photoName: 'Noel'
    },
    {
      name: 'Tara Prangell',
      position: 'Community Manager',
      photoName: 'TaraP'
    },
    {
      name: 'Robert Lester',
      position: 'Creative Director',
      photoName: 'Robert'
    }
  ]
};

export default employeeInfo;
<commit_msg>update employee list<commit_after>
const employeeInfo = {
  importantPeople: [
    {
      name: 'Pete Levin',
      position: 'Co-Founder',
      photoName: 'Pete',
      isImportant: true
    },
    {
      name: 'Ted Kacandes',
      position: 'Co-Flounder',
      photoName: 'Ted',
      isImportant: true
    },
    {
      name: 'Howie Kleinberg',
      position: 'COO & President',
      photoName: 'Howie',
      isImportant: true
    },
    {
      name: 'Mike Molnar',
      position: 'Managing Partner',
      photoName: 'Mike',
      isImportant: true
    },
    {
      name: 'Jerry Levin',
      position: 'Chief Financial Officer',
      photoName: 'Jerry',
      isImportant: true
    },
    {
      name: 'Sean Lynam',
      position: 'Director of Marketing',
      photoName: 'Sean',
      isImportant: true
    },
    {
      name: 'Clayton Benn',
      position: 'Director of Video Production',
      photoName: 'Clayton',
      isImportant: true
    }
  ],
  otherPeople: [
    {
      name: 'Adrien Hobbs',
      position: 'Developer',
      photoName: 'Adrien'
    },
    {
      name: 'Matt Will',
      position: 'Art Director',
      photoName: 'Matt'
    },
    {
      name: 'Ann Ketly',
      position: 'Office Manager',
      photoName: 'Ann'
    },
    {
      name: 'Andre Cole',
      position: 'Video Editor',
      photoName: 'Andre'
    },
    {
      name: 'Lindsey Cirmotich',
      position: 'Junior Designer',
      photoName: 'Lindsey'
    },
    {
      name: 'Emily Stone',
      position: 'Designer',
      photoName: 'EmilyS'
    },
    // {
    //   name: 'Gina Amsellem',
    //   position: 'Social Designer',
    //   photoName: 'GinaA'
    // },
    {
      name: 'Brit Mullen',
      position: 'Social Designer',
      photoName: 'Brit'
    },
    {
      name: 'Ben Tuber',
      position: 'Art Director',
      photoName: 'Ben'
    },
    {
      name: 'Aaron J. Perez',
      position: 'Digital Strategist',
      photoName: 'Aaron'
    },
    {
      name: 'Daniela Guernica',
      position: 'Digital Strategist',
      photoName: 'Danielle'
    },
    {
      name: 'Emily Babich',
      position: 'Junior Video Producer',
      photoName: 'EmilyB'
    },
    {
      name: 'Eric Drumm',
      position: 'Digital Strategist',
      photoName: 'Eric'
    },
    {
      name: 'Erin Resnick',
      position: 'Community Manager',
      photoName: 'Erin'
    },
    {
      name: 'Kendell Burton',
      position: 'Social Designer',
      photoName: 'Kendell'
    },
    {
      name: 'Lauren Farmer',
      position: 'Digital Strategist',
      photoName: 'Lauren'
    },
    {
      name: 'Jennifer Fink',
      position: 'Digital Strategist',
      photoName: 'Jennifer'
    },
    {
      name: 'Peter Kondratowicz',
      position: 'Senior Designer',
      photoName: 'PK'
    },
    {
      name: 'Sarah Pine',
      position: 'Digital Strategist',
      photoName: 'Sarah'
    },
    {
      name: 'Stephanie Rogers',
      position: 'Sr. Account Manager',
      photoName: 'Steph'
    },
    {
      name: 'Tara Lewis',
      position: 'Sr. Project Manager',
      photoName: 'Tara'
    },
    {
      name: 'Sean Orr',
      position: 'Video Editor',
      photoName: 'SeanO'
    },
    {
      name: 'Paula Ersly',
      position: 'Community Manager',
      photoName: 'Paula'
    },
    {
      name: 'Eric Goldie',
      position: 'Community Manager',
      photoName: 'EricG'
    },
    {
      name: 'Zoe Greenblatt',
      position: 'Community Manager',
      photoName: 'Zoe'
    },
    {
      name: 'Casey De Pont',
      position: 'Art Director',
      photoName: 'Casey'
    },
    {
      name: 'Nöel Anderson',
      position: 'Motion Designer',
      photoName: 'Noel'
    },
    {
      name: 'Tara Prangell',
      position: 'Community Manager',
      photoName: 'TaraP'
    },
    {
      name: 'Robert Lester',
      position: 'Creative Director',
      photoName: 'Robert'
    }
  ]
};

export default employeeInfo;
<|endoftext|>


<commit_before>
const employeeInfo = {
  importantPeople: [
    {
      name: 'Pete Levin',
      position: 'Co-Founder',
      photoName: 'Pete',
      isImportant: true
    },
    {
      name: 'Ted Kacandes',
      position: 'Co-Flounder',
      photoName: 'Ted',
      isImportant: true
    },
    {
      name: 'Howie Kleinberg',
      position: 'COO & President',
      photoName: 'Howie',
      isImportant: true
    },
    {
      name: 'Mike Molnar',
      position: 'Managing Partner',
      photoName: 'Mike',
      isImportant: true
    },
    {
      name: 'Jerry Levin',
      position: 'Chief Financial Officer',
      photoName: 'Jerry',
      isImportant: true
    },
    {
      name: 'Sean Lynam',
      position: 'Director of Marketing',
      photoName: 'Sean',
      isImportant: true
    },
    {
      name: 'Clayton Benn',
      position: 'Director of Video Production',
      photoName: 'Clayton',
      isImportant: true
    }
  ],
  otherPeople: [
    {
      name: 'Adrien Hobbs',
      position: 'Developer',
      photoName: 'Adrien'
    },
    {
      name: 'Matt Will',
      position: 'Art Director',
      photoName: 'Matt'
    },
    {
      name: 'Ann Ketly',
      position: 'Office Manager',
      photoName: 'Ann'
    },
    {
      name: 'Andre Cole',
      position: 'Video Editor',
      photoName: 'Andre'
    },
    {
      name: 'Lindsey Cirmotich',
      position: 'Junior Designer',
      photoName: 'Lindsey'
    },
    {
      name: 'Emily Stone',
      position: 'Designer',
      photoName: 'EmilyS'
    },
    // {
    //   name: 'Gina Amsellem',
    //   position: 'Social Designer',
    //   photoName: 'GinaA'
    // },
    {
      name: 'Brit Mullen',
      position: 'Social Designer',
      photoName: 'Brit'
    },
    {
      name: 'Ben Tuber',
      position: 'Art Director',
      photoName: 'Ben'
    },
    {
      name: 'Aaron J. Perez',
      position: 'Digital Strategist',
      photoName: 'Aaron'
    },
    {
      name: 'Daniela Guernica',
      position: 'Digital Strategist',
      photoName: 'Danielle'
    },
    {
      name: 'Emily Babich',
      position: 'Junior Video Producer',
      photoName: 'EmilyB'
    },
    {
      name: 'Eric Drumm',
      position: 'Digital Strategist',
      photoName: 'Eric'
    },
    {
      name: 'Erin Resnick',
      position: 'Community Manager',
      photoName: 'Erin'
    },
    {
      name: 'Kendell Burton',
      position: 'Social Designer',
      photoName: 'Kendell'
    },
    {
      name: 'Lauren Farmer',
      position: 'Digital Strategist',
      photoName: 'Lauren'
    },
    {
      name: 'Jennifer Fink',
      position: 'Digital Strategist',
      photoName: 'Jennifer'
    },
    {
      name: 'Peter Kondratowicz',
      position: 'Senior Designer',
      photoName: 'PK'
    },
    {
      name: 'Sarah Pine',
      position: 'Digital Strategist',
      photoName: 'Sarah'
    },
    {
      name: 'Stephanie Rogers',
      position: 'Sr. Account Manager',
      photoName: 'Steph'
    },
    {
      name: 'Tara Lewis',
      position: 'Sr. Project Manager',
      photoName: 'Tara'
    },
    {
      name: 'Sean Orr',
      position: 'Video Editor',
      photoName: 'SeanO'
    },
    {
      name: 'Paula Ersly',
      position: 'Community Manager',
      photoName: 'Paula'
    },
    {
      name: 'Eric Goldie',
      position: 'Community Manager',
      photoName: 'EricG'
    },
    {
      name: 'Zoe Greenblatt',
      position: 'Community Manager',
      photoName: 'Zoe'
    },
    {
      name: 'Casey De Pont',
      position: 'Art Director',
      photoName: 'Casey'
    },
    {
      name: 'Nöel Anderson',
      position: 'Motion Designer',
      photoName: 'Noel'
    },
    {
      name: 'Tara Prangell',
      position: 'Community Manager',
      photoName: 'TaraP'
    },
    {
      name: 'Robert Lester',
      position: 'Creative Director',
      photoName: 'Robert'
    }
  ]
};

export default employeeInfo;
<commit_msg>add sosin back<commit_after>
const employeeInfo = {
  importantPeople: [
    {
      name: 'Pete Levin',
      position: 'Co-Founder',
      photoName: 'Pete',
      isImportant: true
    },
    {
      name: 'Robert Sosin',
      position: 'Senior Creative Director',
      photoName: 'RobertS',
      isImportant: true
    },
    {
      name: 'Ted Kacandes',
      position: 'Co-Flounder',
      photoName: 'Ted',
      isImportant: true
    },
    {
      name: 'Howie Kleinberg',
      position: 'COO & President',
      photoName: 'Howie',
      isImportant: true
    },
    {
      name: 'Mike Molnar',
      position: 'Managing Partner',
      photoName: 'Mike',
      isImportant: true
    },
    {
      name: 'Jerry Levin',
      position: 'Chief Financial Officer',
      photoName: 'Jerry',
      isImportant: true
    },
    {
      name: 'Sean Lynam',
      position: 'Director of Marketing',
      photoName: 'Sean',
      isImportant: true
    },
    {
      name: 'Clayton Benn',
      position: 'Director of Video Production',
      photoName: 'Clayton',
      isImportant: true
    }
  ],
  otherPeople: [
    {
      name: 'Adrien Hobbs',
      position: 'Developer',
      photoName: 'Adrien'
    },
    {
      name: 'Matt Will',
      position: 'Art Director',
      photoName: 'Matt'
    },
    {
      name: 'Ann Ketly',
      position: 'Office Manager',
      photoName: 'Ann'
    },
    {
      name: 'Andre Cole',
      position: 'Video Editor',
      photoName: 'Andre'
    },
    {
      name: 'Lindsey Cirmotich',
      position: 'Junior Designer',
      photoName: 'Lindsey'
    },
    {
      name: 'Emily Stone',
      position: 'Designer',
      photoName: 'EmilyS'
    },
    // {
    //   name: 'Gina Amsellem',
    //   position: 'Social Designer',
    //   photoName: 'GinaA'
    // },
    {
      name: 'Brit Mullen',
      position: 'Social Designer',
      photoName: 'Brit'
    },
    {
      name: 'Ben Tuber',
      position: 'Art Director',
      photoName: 'Ben'
    },
    {
      name: 'Aaron J. Perez',
      position: 'Digital Strategist',
      photoName: 'Aaron'
    },
    {
      name: 'Daniela Guernica',
      position: 'Digital Strategist',
      photoName: 'Danielle'
    },
    {
      name: 'Emily Babich',
      position: 'Junior Video Producer',
      photoName: 'EmilyB'
    },
    {
      name: 'Eric Drumm',
      position: 'Digital Strategist',
      photoName: 'Eric'
    },
    {
      name: 'Erin Resnick',
      position: 'Community Manager',
      photoName: 'Erin'
    },
    {
      name: 'Kendell Burton',
      position: 'Social Designer',
      photoName: 'Kendell'
    },
    {
      name: 'Lauren Farmer',
      position: 'Digital Strategist',
      photoName: 'Lauren'
    },
    {
      name: 'Jennifer Fink',
      position: 'Digital Strategist',
      photoName: 'Jennifer'
    },
    {
      name: 'Peter Kondratowicz',
      position: 'Senior Designer',
      photoName: 'PK'
    },
    {
      name: 'Sarah Pine',
      position: 'Digital Strategist',
      photoName: 'Sarah'
    },
    {
      name: 'Stephanie Rogers',
      position: 'Sr. Account Manager',
      photoName: 'Steph'
    },
    {
      name: 'Tara Lewis',
      position: 'Sr. Project Manager',
      photoName: 'Tara'
    },
    {
      name: 'Sean Orr',
      position: 'Video Editor',
      photoName: 'SeanO'
    },
    {
      name: 'Paula Ersly',
      position: 'Community Manager',
      photoName: 'Paula'
    },
    {
      name: 'Eric Goldie',
      position: 'Community Manager',
      photoName: 'EricG'
    },
    {
      name: 'Zoe Greenblatt',
      position: 'Community Manager',
      photoName: 'Zoe'
    },
    {
      name: 'Casey De Pont',
      position: 'Art Director',
      photoName: 'Casey'
    },
    {
      name: 'Nöel Anderson',
      position: 'Motion Designer',
      photoName: 'Noel'
    },
    {
      name: 'Tara Prangell',
      position: 'Community Manager',
      photoName: 'TaraP'
    },
    {
      name: 'Robert Lester',
      position: 'Creative Director',
      photoName: 'Robert'
    }
  ]
};

export default employeeInfo;
<|endoftext|>


<commit_before>dojo.require("dijit._editor.RichText");
dojo.require("dojo.parser");

dojo.require("dojox.sql");
dojo.require("dojox.off");
dojo.require("dojox.off.ui");
dojo.require("dojox.off.sync");

// configure how we should work offline

// set our application name
dojox.off.ui.appName = "Moxie";

// add our list of resources we need offline
// Moxie resources
dojox.off.files.cache([
					"editor.html",
					"editor.js",
					"about.html"
					]);

// add the RichText widget JavaScript files
dojox.off.files.cache([
					"../../../../dijit/_editor/selection.js",
					"../../../../dijit/_editor/RichText.js"
					]);

var moxie = {
	_availableKeys: null,
	_documents: null,

	initialize: function(){
		//console.debug("moxie.initialize");

		// make sure the rich text control is finished
		// loading; workaround for bug 3395
		var richTextControl = dijit.byId("storageValue");
		if(richTextControl.isLoaded == false){
			dojo.connect(richTextControl, "onLoad", this, "initialize");
			return;
		}

		// clear out old values
		dojo.byId("storageKey").value = "";
		richTextControl.setValue("Click Here to Begin Editing");

		// initialize our event handlers
		var directory = dojo.byId("directory");
		dojo.connect(directory, "onchange", this, this.directoryChange);
		dojo.connect(dojo.byId("saveButton"), "onclick", this, this.save);

		// create our database
		this._createDb();

		// load and write out our available keys
		this._loadKeys();

		// setup our offline handlers
		this._initOfflineHandlers();

		this._initialized = true;
	},

	directoryChange: function(evt){
		var key = evt.target.value;

		// add this value into the form
		var keyNameField = dojo.byId("storageKey");
		keyNameField.value = key;

		// if blank key ignore
		if(key == ""){
			return;
		}

		this._load(key);
	},

	save: function(evt){
		// cancel the button's default behavior
		evt.preventDefault();
		evt.stopPropagation();

		// get the new values
		var key = dojo.byId("storageKey").value;
		var richTextControl = dijit.byId("storageValue");
		var value = richTextControl.getValue();

		if(key == null || typeof key == "undefined" || key == ""){
			alert("Please enter a file name");
			return;
		}

		if(value == null || typeof value == "undefined" || value == ""){
			alert("Please enter file contents");
			return;
		}

		// do the save
		this._save(key, value)
	},

	_save: function(key, value, log){
		this._printStatus("Saving '" + key + "'...");

		if(dojox.off.isOnline == true){
			this._saveOnline(key, value, log);
		}else{
			this._saveOffline(key, value);
		}
	},

	_saveOnline: function(key, value){
		var self = this;
		var doLoad = function(data){
			//console.debug("load, data="+data);
			self._printStatus("Saved '" + key + "'");

			// add to our list of available keys
			self._addKey(key);

			if(dojox.off.sync.log.isReplaying == false){
				// update the list of available keys
				self._printAvailableKeys();
			}else{
				dojox.off.sync.log.continueReplay();
			}
		};

		var bindArgs = {
			url:	 "/moxie/" + encodeURIComponent(key),
			content:	{"content": value},
			error:		function(err){
				//console.debug("error, err="+err);
				var msg = "Unable to save file " + key + ": " + err;
				if(dojox.off.sync.log.isReplaying == false){
					alert(msg);
				}else{
					dojox.off.sync.log.haltReplay(msg);
				}
			},
			load:		doLoad
		};

		// dispatch the request
		dojo.xhrPost(bindArgs);
	},

	_saveOffline: function(key, value){
		// create a command object to capture this action
		var command = {name: "save", key: key, value: value};

		// save it in our command log for replaying when we
		// go back online
		dojox.off.sync.log.add(command);

		// also add it to our offline, downloaded data
		this._documents.push({fileName: key, content: value});
		dojox.sql("INSERT INTO DOCUMENTS (fileName, content) VALUES (?, ?)",
						key, value);

		// update our UI
		this._printStatus("Saved '" + key + "'");
		this._addKey(key);
		this._printAvailableKeys();
	},

	_loadKeys: function(){
		if(dojox.off.isOnline == true){
			this._loadKeysOnline();
		}else{
			this._loadKeysOffline();
		}
	},

	_loadKeysOnline: function(){
		var self = this;
		var url = "/moxie/*"
					+ "?browserbust=" + new Date().getTime()
					+ "&proxybust=" + new Date().getTime();
		var bindArgs = {
			url:	 url,
			handleAs:	"javascript",
			headers:		{ "Accept" : "text/javascript" },
			error:		function(err){
				//console.debug("error, err="+err);
				alert("Unable to load our list of available keys from "
						+ "the server: " + err.message);
			},
			load:		function(data){
				//console.debug("load, data="+data);
				// 'data' is a JSON array, where each entry is a String filename
				// of the available keys
				self._availableKeys = data;
				self._printAvailableKeys();
			}
		};

		// dispatch the request
		dojo.xhrGet(bindArgs);
	},

	_loadKeysOffline: function(){
		this._loadDownloadedData();
		this._printAvailableKeys();
	},

	_printAvailableKeys: function(){
		var directory = dojo.byId("directory");

		// clear out any old keys
		directory.innerHTML = "";

		// add a blank selection
		var optionNode = document.createElement("option");
		optionNode.appendChild(document.createTextNode(""));
		optionNode.value = "";
		directory.appendChild(optionNode);

		// sort our available keys alphabetically
		var keys = this._availableKeys.slice();
		keys.sort();

		// add new ones
		for (var i = 0; i < keys.length; i++) {
			var optionNode = document.createElement("option");
			optionNode.appendChild(document.createTextNode(keys[i]));
			optionNode.value = keys[i];
			directory.appendChild(optionNode);
		}
	},

	_addKey: function(key){
		var alreadyPresent = false;
		for(var i = 0; i < this._availableKeys.length; i++){
			if(this._availableKeys[i] == key){
				alreadyPresent = true;
				break;
			}
		}

		if(alreadyPresent == false){
			this._availableKeys.push(key);
		}
	},

	_load: function(key){
		this._printStatus("Loading '" + key + "'...");

		if(dojox.off.isOnline == true){
			this._loadOnline(key);
		}else{
			this._loadOffline(key);
		}
	},

	_loadOnline: function(key){
		// get the value from the server
		var self = this;
		// add 'cachebust' to the URL to make sure we get a fresh
		// copy that is not returned from either the browser's cache
		// or the local offline proxy's cache
		var url = "/moxie/" + encodeURIComponent(key)
					+ "?cachebust=" + new Date().getTime();
		var bindArgs = {
			url:	 url,
			handleAs:	"text",
			error:		function(err){
				//console.debug("error, err="+err);
				alert("The file " + key + " is not available: "
						+ err.message);
			},
			load:		function(data){
				//console.debug("load, data="+data);
				self._updateEditorContents(data);

				// print out that we are done
				self._printStatus("Loaded '" + key + "'");
			}
		};

		// dispatch the request
		dojo.xhrGet(bindArgs);
	},

	_loadOffline: function(key){
		var doc = null;
		for(var i = 0; i < this._documents.length; i++){
			var currentDoc = this._documents[i];
			if(currentDoc.fileName == key){
				doc = currentDoc;
				break;
			}
		}

		this._updateEditorContents(doc.content);
	},

	_updateEditorContents: function(contents){
		// set the new Editor widget value
		var richTextControl = dijit.byId("storageValue");
		richTextControl.setValue(contents);
	},

	_printStatus: function(message){
		// remove the old status
		var top = dojo.byId("top");
		for (var i = 0; i < top.childNodes.length; i++) {
			var currentNode = top.childNodes[i];
			if (currentNode.nodeType == 1 &&
					currentNode.className == "status") {
				top.removeChild(currentNode);
			}
		}

		var status = document.createElement("span");
		status.className = "status";
		status.innerHTML = message;

		top.appendChild(status);
		dojo.fadeOut({ node: status, duration: 2000 }).play();
	},

	_initOfflineHandlers: function(){
		// setup what we do when we are replaying our command
		// log when the network reappears
		var self = this;
		dojox.off.sync.log.onCommand = function(command){
			if(command.name == "save"){
				self._save(command.key, command.value);
			}
		}

		// setup how we download our data from the server
		dojox.off.sync.doDownload = function(){
			// actually download our data
			self._downloadData();
		}

		// refresh our UI when we are finished syncing
		dojox.off.sync.onFinished = function(){
			dojox.off.ui.onFinished();

			self._printAvailableKeys();
		}
	},

	_downloadData: function(){
		var self = this;
		// add 'cachebust' to the URL to make sure we get a fresh
		// copy that is not returned from either the browser's cache
		// or the local offline proxy's cache
		var bindArgs = {
			url:	 "/moxie/download?cachebust=" + new Date().getTime(),
			handleAs:	"javascript",
			headers:	{ "Accept" : "text/javascript" },
			error:		function(err){
				//console.debug("moxie._downloadData.error, err="+err);
				if(err.message){
					err = err.message;
				}

				var message = "Unable to download our documents from server: "
								+ err;
				dojox.off.sync.finishedDownloading(false, message);
			},
			load:		function(data){
				//console.debug("moxie._downloadData.load");
				self._saveDownloadedData(data);
			}
		};

		// dispatch the request
		dojo.xhrGet(bindArgs);
	},

	_saveDownloadedData: function(data){
		// 'data' is a JSON structure passed to us by the server
		// that is an array of object literals, where each literal
		// has a 'fileName' entry and a 'content' entry.
		this._createDb();

		dojo.forEach(data, function(record){
			dojox.sql("INSERT INTO DOCUMENTS (fileName, content) VALUES (?, ?)",
						record.fileName, record.content);
		});
		dojox.off.sync.finishedDownloading(true, null);
	},

	_loadDownloadedData: function(){
		this._availableKeys = [];
		this._documents = dojox.sql("SELECT * FROM DOCUMENTS");
		if(this._documents == null
			|| typeof this._documents == "undefined"){
			this._documents = [];
		}

		for(var i = 0; i < this._documents.length; i++){
			var fileName = this._documents[i].fileName;
			this._availableKeys.push(fileName);
		}
	},

	_createDb: function(){
		dojox.sql("DROP TABLE IF EXISTS DOCUMENTS");
		dojox.sql("CREATE TABLE IF NOT EXISTS DOCUMENTS ("
					+ "fileName		TEXT NOT NULL PRIMARY KEY UNIQUE, "
					+ "content		TEXT NOT NULL) ");
	}
};

// wait until Dojo Offline and the default Offline Widget are ready
// before we initialize ourselves. When this gets called the page
// is also finished loading.
dojo.connect(dojox.off.ui, "onLoad", moxie, moxie.initialize);

// tell Dojo Offline we are ready for it to initialize itself now
// that we have finished configuring it for our application
dojox.off.initialize();
<commit_msg>Removed bug in deleting documents table<commit_after>dojo.require("dijit._editor.RichText");
dojo.require("dojo.parser");

dojo.require("dojox.sql");
dojo.require("dojox.off");
dojo.require("dojox.off.ui");
dojo.require("dojox.off.sync");

// configure how we should work offline

// set our application name
dojox.off.ui.appName = "Moxie";

// add our list of resources we need offline
// Moxie resources
dojox.off.files.cache([
					"editor.html",
					"editor.js",
					"about.html"
					]);

// add the RichText widget JavaScript files
dojox.off.files.cache([
					"../../../../dijit/_editor/selection.js",
					"../../../../dijit/_editor/RichText.js"
					]);

var moxie = {
	_availableKeys: null,
	_documents: null,

	initialize: function(){
		//console.debug("moxie.initialize");

		// make sure the rich text control is finished
		// loading; workaround for bug 3395
		var richTextControl = dijit.byId("storageValue");
		if(richTextControl.isLoaded == false){
			dojo.connect(richTextControl, "onLoad", this, "initialize");
			return;
		}

		// clear out old values
		dojo.byId("storageKey").value = "";
		richTextControl.setValue("Click Here to Begin Editing");

		// initialize our event handlers
		var directory = dojo.byId("directory");
		dojo.connect(directory, "onchange", this, this.directoryChange);
		dojo.connect(dojo.byId("saveButton"), "onclick", this, this.save);

		// create our database
		this._createDb();

		// load and write out our available keys
		this._loadKeys();

		// setup our offline handlers
		this._initOfflineHandlers();

		this._initialized = true;
	},

	directoryChange: function(evt){
		var key = evt.target.value;

		// add this value into the form
		var keyNameField = dojo.byId("storageKey");
		keyNameField.value = key;

		// if blank key ignore
		if(key == ""){
			return;
		}

		this._load(key);
	},

	save: function(evt){
		// cancel the button's default behavior
		evt.preventDefault();
		evt.stopPropagation();

		// get the new values
		var key = dojo.byId("storageKey").value;
		var richTextControl = dijit.byId("storageValue");
		var value = richTextControl.getValue();

		if(key == null || typeof key == "undefined" || key == ""){
			alert("Please enter a file name");
			return;
		}

		if(value == null || typeof value == "undefined" || value == ""){
			alert("Please enter file contents");
			return;
		}

		// do the save
		this._save(key, value)
	},

	_save: function(key, value, log){
		this._printStatus("Saving '" + key + "'...");

		if(dojox.off.isOnline == true){
			this._saveOnline(key, value, log);
		}else{
			this._saveOffline(key, value);
		}
	},

	_saveOnline: function(key, value){
		var self = this;
		var doLoad = function(data){
			//console.debug("load, data="+data);
			self._printStatus("Saved '" + key + "'");

			// add to our list of available keys
			self._addKey(key);

			if(dojox.off.sync.log.isReplaying == false){
				// update the list of available keys
				self._printAvailableKeys();
			}else{
				dojox.off.sync.log.continueReplay();
			}
		};

		var bindArgs = {
			url:	 "/moxie/" + encodeURIComponent(key),
			content:	{"content": value},
			error:		function(err){
				//console.debug("error, err="+err);
				var msg = "Unable to save file " + key + ": " + err;
				if(dojox.off.sync.log.isReplaying == false){
					alert(msg);
				}else{
					dojox.off.sync.log.haltReplay(msg);
				}
			},
			load:		doLoad
		};

		// dispatch the request
		dojo.xhrPost(bindArgs);
	},

	_saveOffline: function(key, value){
		// create a command object to capture this action
		var command = {name: "save", key: key, value: value};

		// save it in our command log for replaying when we
		// go back online
		dojox.off.sync.log.add(command);

		// also add it to our offline, downloaded data
		this._documents.push({fileName: key, content: value});
		dojox.sql("INSERT INTO DOCUMENTS (fileName, content) VALUES (?, ?)",
						key, value);

		// update our UI
		this._printStatus("Saved '" + key + "'");
		this._addKey(key);
		this._printAvailableKeys();
	},

	_loadKeys: function(){
		if(dojox.off.isOnline == true){
			this._loadKeysOnline();
		}else{
			this._loadKeysOffline();
		}
	},

	_loadKeysOnline: function(){
		var self = this;
		var url = "/moxie/*"
					+ "?browserbust=" + new Date().getTime()
					+ "&proxybust=" + new Date().getTime();
		var bindArgs = {
			url:	 url,
			handleAs:	"javascript",
			headers:		{ "Accept" : "text/javascript" },
			error:		function(err){
				//console.debug("error, err="+err);
				alert("Unable to load our list of available keys from "
						+ "the server: " + err.message);
			},
			load:		function(data){
				//console.debug("load, data="+data);
				// 'data' is a JSON array, where each entry is a String filename
				// of the available keys
				self._availableKeys = data;
				self._printAvailableKeys();
			}
		};

		// dispatch the request
		dojo.xhrGet(bindArgs);
	},

	_loadKeysOffline: function(){
		this._loadDownloadedData();
		this._printAvailableKeys();
	},

	_printAvailableKeys: function(){
		var directory = dojo.byId("directory");

		// clear out any old keys
		directory.innerHTML = "";

		// add a blank selection
		var optionNode = document.createElement("option");
		optionNode.appendChild(document.createTextNode(""));
		optionNode.value = "";
		directory.appendChild(optionNode);

		// sort our available keys alphabetically
		var keys = this._availableKeys.slice();
		keys.sort();

		// add new ones
		for (var i = 0; i < keys.length; i++) {
			var optionNode = document.createElement("option");
			optionNode.appendChild(document.createTextNode(keys[i]));
			optionNode.value = keys[i];
			directory.appendChild(optionNode);
		}
	},

	_addKey: function(key){
		var alreadyPresent = false;
		for(var i = 0; i < this._availableKeys.length; i++){
			if(this._availableKeys[i] == key){
				alreadyPresent = true;
				break;
			}
		}

		if(alreadyPresent == false){
			this._availableKeys.push(key);
		}
	},

	_load: function(key){
		this._printStatus("Loading '" + key + "'...");

		if(dojox.off.isOnline == true){
			this._loadOnline(key);
		}else{
			this._loadOffline(key);
		}
	},

	_loadOnline: function(key){
		// get the value from the server
		var self = this;
		// add 'cachebust' to the URL to make sure we get a fresh
		// copy that is not returned from either the browser's cache
		// or the local offline proxy's cache
		var url = "/moxie/" + encodeURIComponent(key)
					+ "?cachebust=" + new Date().getTime();
		var bindArgs = {
			url:	 url,
			handleAs:	"text",
			error:		function(err){
				//console.debug("error, err="+err);
				alert("The file " + key + " is not available: "
						+ err.message);
			},
			load:		function(data){
				//console.debug("load, data="+data);
				self._updateEditorContents(data);

				// print out that we are done
				self._printStatus("Loaded '" + key + "'");
			}
		};

		// dispatch the request
		dojo.xhrGet(bindArgs);
	},

	_loadOffline: function(key){
		var doc = null;
		for(var i = 0; i < this._documents.length; i++){
			var currentDoc = this._documents[i];
			if(currentDoc.fileName == key){
				doc = currentDoc;
				break;
			}
		}

		this._updateEditorContents(doc.content);
	},

	_updateEditorContents: function(contents){
		// set the new Editor widget value
		var richTextControl = dijit.byId("storageValue");
		richTextControl.setValue(contents);
	},

	_printStatus: function(message){
		// remove the old status
		var top = dojo.byId("top");
		for (var i = 0; i < top.childNodes.length; i++) {
			var currentNode = top.childNodes[i];
			if (currentNode.nodeType == 1 &&
					currentNode.className == "status") {
				top.removeChild(currentNode);
			}
		}

		var status = document.createElement("span");
		status.className = "status";
		status.innerHTML = message;

		top.appendChild(status);
		dojo.fadeOut({ node: status, duration: 2000 }).play();
	},

	_initOfflineHandlers: function(){
		// setup what we do when we are replaying our command
		// log when the network reappears
		var self = this;
		dojox.off.sync.log.onCommand = function(command){
			if(command.name == "save"){
				self._save(command.key, command.value);
			}
		}

		// setup how we download our data from the server
		dojox.off.sync.doDownload = function(){
			// actually download our data
			self._downloadData();
		}

		// refresh our UI when we are finished syncing
		dojox.off.sync.onFinished = function(){
			dojox.off.ui.onFinished();

			self._printAvailableKeys();
		}
	},

	_downloadData: function(){
		var self = this;
		// add 'cachebust' to the URL to make sure we get a fresh
		// copy that is not returned from either the browser's cache
		// or the local offline proxy's cache
		var bindArgs = {
			url:	 "/moxie/download?cachebust=" + new Date().getTime(),
			handleAs:	"javascript",
			headers:	{ "Accept" : "text/javascript" },
			error:		function(err){
				//console.debug("moxie._downloadData.error, err="+err);
				if(err.message){
					err = err.message;
				}

				var message = "Unable to download our documents from server: "
								+ err;
				dojox.off.sync.finishedDownloading(false, message);
			},
			load:		function(data){
				//console.debug("moxie._downloadData.load");
				self._saveDownloadedData(data);
			}
		};

		// dispatch the request
		dojo.xhrGet(bindArgs);
	},

	_saveDownloadedData: function(data){
		// 'data' is a JSON structure passed to us by the server
		// that is an array of object literals, where each literal
		// has a 'fileName' entry and a 'content' entry.
		dojox.sql("DROP TABLE IF EXISTS DOCUMENTS");

		this._createDb();

		dojo.forEach(data, function(record){
			dojox.sql("INSERT INTO DOCUMENTS (fileName, content) VALUES (?, ?)",
						record.fileName, record.content);
		});
		dojox.off.sync.finishedDownloading(true, null);
	},

	_loadDownloadedData: function(){
		this._availableKeys = [];
		this._documents = dojox.sql("SELECT * FROM DOCUMENTS");
		if(this._documents == null
			|| typeof this._documents == "undefined"){
			this._documents = [];
		}

		for(var i = 0; i < this._documents.length; i++){
			var fileName = this._documents[i].fileName;
			this._availableKeys.push(fileName);
		}
	},

	_createDb: function(){
		dojox.sql("CREATE TABLE IF NOT EXISTS DOCUMENTS ("
					+ "fileName		TEXT NOT NULL PRIMARY KEY UNIQUE, "
					+ "content		TEXT NOT NULL) ");
	}
};

// wait until Dojo Offline and the default Offline Widget are ready
// before we initialize ourselves. When this gets called the page
// is also finished loading.
dojo.connect(dojox.off.ui, "onLoad", moxie, moxie.initialize);

// tell Dojo Offline we are ready for it to initialize itself now
// that we have finished configuring it for our application
dojox.off.initialize();
<|endoftext|>