<commit_before>export { AsyncWorker_Proxy as Proxy };

import * as Pool from "../Pool.js";
import * as Recyclable from "../Recyclable.js";
import { PromiseResolveReject } from "./AsyncWorker_PromiseResolveReject.js";
import { processingId_Resulter_Map } from "./AsyncWorker_processingId_Resulter_Map.js";
import { Resulter } from "./AsyncWorker_Resulter.js";
import * as AsyncWorker_Checker from "./AsyncWorker_Checker.js";

/**
 * Hold the worker and its related promise map. It is a wrapper of a neural network
 * web worker for handling and communicating easily.
 *
 * @member {string} workerModuleURL
 *   The (absolute) javascript module URL which will be loaded (asynchronously) by
 * worker body (stub).
 *
 * @member {Worker} worker
 *   The worker.
 *
 * @member {number} processingId_next
 *   The next processing id. Zero means no command has been sent. Every
 * postCommand_and_expectResult() call will use a new id.
 *
 * @member {processingId_Resulter_Map} the_processingId_Resulter_Map
 *   Every worker has a result pending promise map. The key of the map is processing
 * id. The value of the map is a AsyncWorker.Resulter.
 *
 */
class AsyncWorker_Proxy extends Recyclable.Root {

  /**
   * Used as default AsyncWorker.Proxy provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "AsyncWorker.Proxy.Pool",
    AsyncWorker_Proxy, AsyncWorker_Proxy.setAsConstructor );

  /** */
  constructor( workerModuleURL ) {
    super();
    AsyncWorker_Proxy.setAsConstructor_self.call( this, workerModuleURL );
  }

  /** @override */
  static setAsConstructor( workerModuleURL ) {
    super.setAsConstructor();
    AsyncWorker_Proxy.setAsConstructor_self.call( this, workerModuleURL );
    return this;
  }

  /** @override */
  static setAsConstructor_self( workerModuleURL ) {

    // Q: What if processingId become too large (e.g. infinity)?
    // A: Because Number.MAX_SAFE_INTEGER is pretty large (at least, 2 ** 52 ),
    //    it is not so easy to become out of bounds.
    //
    this.processingId_next = 0;

    if ( this.the_processingId_Resulter_Map )
      this.the_processingId_Resulter_Map.clear();
    else
      this.the_processingId_Resulter_Map = new processingId_Resulter_Map();

    AsyncWorker_Proxy.createWorker_byModuleURL.call( this, workerModuleURL );
  }

  /** @override */
  disposeResources() {

    if ( this.worker ) {
      // Note: No processingId, because this command needs not return value.
      this.postCommandArgs( [ "disposeResources" ] );
      this.worker = null;
    }

    this.workerBlobObjectURL_dispose();

    this.workerOptions = undefined;
    this.workerURL = undefined;
    this.workerModuleURL = undefined;

    this.the_processingId_Resulter_Map.clear();

    this.processingId_next = undefined;

    super.disposeResources();
  }

  /** */
  workerBlobObjectURL_dispose() {
    if ( this.workerBlobObjectURL ) {
      URL.revokeObjectURL( this.workerBlobObjectURL );
      this.workerBlobObjectURL = null;
    }
  }

  /**
   * @param {AsyncWorker_Proxy} this
   *   The worker proxy.
   */
  static createWorker_byModuleURL( workerModuleURL ) {
    this.workerModuleURL = workerModuleURL;

//!!! (2022/09/22 Remarked)
// Deprecated, because of cross origin web worker issue. Please use
// .WorkerBodyStub_create_Codes_BlobObjectURL() instead.)
//
//     // (2022/09/21 Remarked) Use WorkerBodyStub_create_Codes_BlobObjectURL() instead.
//     //this.workerURL = AsyncWorker_Proxy.create_WorkerBodyStub_URL( workerModuleURL );
//
//     // (2022/09/21 Remarked) Use WorkerBodyStub_create_Codes_BlobObjectURL() instead.
//     //let workerDataURI
//     //  = AsyncWorker_Proxy.create_WorkerBodyStub_Codes_DataURI( workerModuleURL );
//     //this.workerURL = workerDataURI;

    // Use blob object URL as a workaround for cross origin web worker problem (which
    // will be encountered when use WorkerBodyStub_URL or WorkerBodyStub_Codes_DataURI).
    this.workerBlobObjectURL
      = AsyncWorker_Proxy.WorkerBodyStub_create_Codes_BlobObjectURL( workerModuleURL );
    this.workerURL = this.workerBlobObjectURL;

    // Q: Why not use "module" type worker?
    // A: A "module" type worker can not use importScripts() to load global library.
    //    (e.g. tensorflow.js)
    //
    // On the other hand, a "classic" type worker can use both importScripts() and
    // import() to load global library and module.
    //
    this.workerOptions = null;

    this.worker = new Worker( this.workerURL, this.workerOptions );

    // Register callback from the web worker.
    this.worker.onmessage
      = AsyncWorker_Proxy.onmessage_from_AsyncWorker_Body.bind( this );
  }

// //!!! (2022/09/22 Remarked)
// Deprecated, because of cross origin web worker issue. Please use
// .WorkerBodyStub_create_Codes_BlobObjectURL() instead.)
//
//   /**
//    * (Deprecated, because of cross origin web worker issue. Please use
//    * .WorkerBodyStub_create_Codes_BlobObjectURL() instead.)
//    *
//    * Create a URL string of AsyncWorker_BodyStub.js which is the main (i.e. body)
//    * javascript file of web worker. It is viewed as a classic javascript file (i.e.
//    * not an importable module). But it will load specified workerModuleURL (in its
//    * query string) as a module.
//    *
//    * @param {string} workerModuleURL
//    *   An (absolute) URL to a javascript module file. It will be imported
//    * (asynchronously) by classic javascript file AsyncWorker_BodyStub.js.
//    */
//   static create_WorkerBodyStub_URL( workerModuleURL ) {
//     let encodedWorkerModuleURL = encodeURIComponent( workerModuleURL );
//
//     // Assume the web worker module javascript file is a sibling file (i.e. inside
//     // the same folder) of this module file.
//     let workerBodyStubURL = new URL( "AsyncWorker_BodyStub.js", import.meta.url );
//     let url = `${workerBodyStubURL}?workerModuleURL=${encodedWorkerModuleURL}`;
//     return url;
//   }
//
//   /**
//    * (Deprecated, because of cross origin web worker issue. Please use
//    * .WorkerBodyStub_create_Codes_BlobObjectURL() instead.)
//    *
//    * Create a data URI representing the main (i.e. body) javascript file of web worker.
//    * It is viewed as a classic javascript file (i.e. not an importable module). But
//    * it will load specified workerModuleURL as a module.
//    *
//    * @param {string} workerModuleURL
//    *   An (absolute) URL to a javascript module file. It will be imported
//    * (asynchronously) by this generated classic javascript file (as a dataURI).
//    */
//   static WorkerBodyStub_create_Codes_DataURI( workerModuleURL ) {
//     let codes = AsyncWorker_Proxy.WorkerBodyStub_create_Codes_String( workerModuleURL );
//
//     let workerDataURI = AsyncWorker_Proxy.createDataURI_byStringASCII(
//       AsyncWorker_Proxy.JS_MIME_TYPE_STRING, codes );
//
//     return workerDataURI;
//   }
//
//   /**
//    * @param {string} strMimeType  The result mime type of the data URI.
//    * @param {string} strASCII     The ASCII text to be embedded in data URI.
//    *
//    * @return {string} Return the data URI string.
//    */
//   static createDataURI_byStringASCII( strMimeType, strASCII ) {
//     let str_base64 = btoa( strASCII );
//     let dataURI = `data:${strMimeType};base64,${str_base64}`;
//     return dataURI;
//   }

  /**
   * Create a data URI representing the main (i.e. body) javascript file of web worker.
   * It is viewed as a classic javascript file (i.e. not an importable module). But
   * it will load specified workerModuleURL as a module.
   *
   * @param {string} workerModuleURL
   *   An (absolute) URL to a javascript module file. It will be imported
   * (asynchronously) by this generated classic javascript file (as a dataURI).
   */
  static WorkerBodyStub_create_Codes_BlobObjectURL( workerModuleURL ) {
    let codes = AsyncWorker_Proxy.WorkerBodyStub_create_Codes_String( workerModuleURL );
    let blob = new Blob( [ codes ], { type: AsyncWorker_Proxy.JS_MIME_TYPE_STRING } );
    let workerBlobObjectURL = URL.createObjectURL( blob );
    return workerBlobObjectURL;
  }

  /**
   * In module (non-classic) web worker, static import is available. But the function
   * importScripts() will not be avbailable. For solving this problem, using
   * classic (non-module) web worker so that some global library (e.g. tensorflow.js)
   * can be loaded by importScripts(). And use dynamic import() to load ourselves
   * modules because import() can be used in classic (non-module) script.
   *
   * @param {string} workerModuleURL
   *   An (absolute) URL to a javascript module file. It will be imported
   * (asynchronously) by this generated classic javascript file (as a dataURI).
   *
   * @return {string}
   *   Return javascript codes string representing the main (i.e. body) javascript
   * file of web worker.
   */
  static WorkerBodyStub_create_Codes_String( workerModuleURL ) {

//!!! ...unfinished... (2022/09/15)
// What if loading workerModuleURL failed?
// Re-try (but should inform this WorkerProxy and user).

      // The codes do the following:
      //
      //   - Import the specified module URL.
      //   - Create a temporary message queue.
      //   - Collect all messages before
      //       AsyncWorker_Proxy.onmessage_from_AsyncWorker_Body() be registered
      //       as message handler.
      //
      let codes = ``
        + `import( "${workerModuleURL}" );\n`
        + `AsyncWorker_Body_temporaryMessageQueue = [];\n`
        + `onmessage = ( e ) => {\n`
        // + `  console.log( "Hello" );\n`
        // + `  console.log( e );\n`
        + `  AsyncWorker_Body_temporaryMessageQueue.push( e );\n`
        + `}\n`
        ;

      return codes;
    }

  /**
   * Send command and args (perhaps, with transferable object array) to WorkerBody
   * and expect the final result.
   *
   * @param {Array} commandArgs
   *   An array (i.e. [ comand, ...args ]) which will be sent to the WorkerBody.
   *
   *   - The command is a string which is the WorkerBody's method function to be
   *       called.
   *
   *   - The args is an array which will be destructured into multiple arguments
   *       and passed into the WorkerBody's method function.
   *
   * @param {Array} transferableObjectArray
   *   The transferable object array when postMessage. It could be undefined (but
   * can not be null).
   *
   * @return {Promise}
   *   Return a promise resolved to the final value of the processing's resulter
   * from WorkerBody. (i.e. Only the Xxx of { done: true, value: Xxx } resolved.
   * All other intermediate result { done: false, value: Yyy } are discarded.)
   */
  createPromise_by_postCommandArgs( commandArgs, transferableObjectArray ) {
    let resulter = this.createResulter_by_postCommandArgs(
      commandArgs, transferableObjectArray );
    return resulter.untilDone();
  }

  /**
   * Send command and args (perhaps, with transferable object array) to WorkerBody
   * and expect result.
   *
   * @param {Array} commandArgs
   *   An array (i.e. [ comand, ...args ]) which will be sent to the WorkerBody.
   *
   *   - The command is a string which is the WorkerBody's method function to be
   *       called.
   *
   *   - The args is an array which will be destructured into multiple arguments
   *       and passed into the WorkerBody's method function.
   *
   * @param {Array} transferableObjectArray
   *   The transferable object array when postMessage. It could be undefined (but
   * can not be null).
   *
   * @return {AsyncWorker.Resulter}
   *   Return an async generator for receving result from WorkerBody of the processing.
   */
  createResulter_by_postCommandArgs( commandArgs, transferableObjectArray ) {
    let processingId = this.processingId_next;
    ++this.processingId_next;

    // Prepare the processing's result's receiving queue before sending it.
    let resulter = this.the_processingId_Resulter_Map.createResulter_by_processingId(
      processingId );

    let processingId_commandArgs = [ processingId, ...commandArgs ];
    this.worker.postMessage( processingId_commandArgs, transferableObjectArray );

    // Check large objects are transferred (rather than copied) to ensure performance.
    {
      let bTransferred = AsyncWorker_Checker
        .ImageData_ArrayBuffer_TypedArray_isTransferred( commandArgs );

      if ( !bTransferred )
        throw Error( `AsyncWorker_Proxy.createResulter_by_postCommandArgs(): `
          + `bTransferred ( ${bTransferred} ) should be ( true ) `
          + `after commandArgs transferred to worker. `
          + `commandArgs=[ ${commandArgs} ]`
        );
    }

    return resulter;
  }

  /**
   * Send command and args (perhaps, with transferable object array) to WorkerBody
   * without expecting result. (i.e. fire-and-forget)
   *
   * @param {Array} commandArgs
   *   An array (i.e. [ comand, ...args ]) which will be sent to the WorkerBody.
   *
   *   - The command is a string which is the WorkerBody's method function to be
   *       called.
   *
   *   - The args is an array which will be destructured into multiple arguments
   *       and passed into the WorkerBody's method function.
   *
   * @param {Array} transferableObjectArray
   *   The transferable object array when postMessage. It could be undefined (but
   * can not be null).
   */
  postCommandArgs( commandArgs, transferableObjectArray ) {
    let processingId_commandArgs = [ undefined, ...commandArgs ]; // no processingId.
    this.worker.postMessage( processingId_commandArgs, transferableObjectArray );

    // Check large objects are transferred (rather than copied) to ensure performance.
    {
      let bTransferred = AsyncWorker_Checker
        .ImageData_ArrayBuffer_TypedArray_isTransferred( commandArgs );

      if ( !bTransferred )
        throw Error( `AsyncWorker_Proxy.postCommandArgs(): `
          + `bTransferred ( ${bTransferred} ) should be ( true ) `
          + `after commandArgs transferred to worker. `
          + `commandArgs=[ ${commandArgs} ]`
        );
    }
  }

  /**
   * Dispatch messages come from the owned web worker. Please register this method
   * as this WorkerProxy's Worker.onmessage.
   *
   * @param {AsyncWorker_Proxy} this
   *   The "this" should be binded to this AsyncWorker_Proxy object.
   */
  static onmessage_from_AsyncWorker_Body( e ) {

    // If the web worker source is blob object URL, revoke it after worker created
    // successfully for release resource.
    this.workerBlobObjectURL_dispose();

    // ( e.data == [ processingId, done, value ] )
    let [ processingId, done, value ] = e.data;

    this.the_processingId_Resulter_Map.resolve_or_reject_by_processingId_done_value(
      processingId, done, value );
  }

}

AsyncWorker_Proxy.JS_MIME_TYPE_STRING = "text/javascript";
<commit_msg>Improve comments.<commit_after>export { AsyncWorker_Proxy as Proxy };

import * as Pool from "../Pool.js";
import * as Recyclable from "../Recyclable.js";
import { PromiseResolveReject } from "./AsyncWorker_PromiseResolveReject.js";
import { processingId_Resulter_Map } from "./AsyncWorker_processingId_Resulter_Map.js";
import { Resulter } from "./AsyncWorker_Resulter.js";
import * as AsyncWorker_Checker from "./AsyncWorker_Checker.js";

/**
 * Hold the worker and its related promise map. It is a wrapper of a neural network
 * web worker for handling and communicating easily.
 *
 * @member {string} workerModuleURL
 *   The (absolute) javascript module URL which will be loaded (asynchronously) by
 * worker body (stub).
 *
 * @member {Worker} worker
 *   The worker.
 *
 * @member {number} processingId_next
 *   The next processing id. Zero means no command has been sent. Every
 * postCommand_and_expectResult() call will use a new id.
 *
 * @member {processingId_Resulter_Map} the_processingId_Resulter_Map
 *   Every worker has a result pending promise map. The key of the map is processing
 * id. The value of the map is a AsyncWorker.Resulter.
 *
 */
class AsyncWorker_Proxy extends Recyclable.Root {

  /**
   * Used as default AsyncWorker.Proxy provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "AsyncWorker.Proxy.Pool",
    AsyncWorker_Proxy, AsyncWorker_Proxy.setAsConstructor );

  /** */
  constructor( workerModuleURL ) {
    super();
    AsyncWorker_Proxy.setAsConstructor_self.call( this, workerModuleURL );
  }

  /** @override */
  static setAsConstructor( workerModuleURL ) {
    super.setAsConstructor();
    AsyncWorker_Proxy.setAsConstructor_self.call( this, workerModuleURL );
    return this;
  }

  /** @override */
  static setAsConstructor_self( workerModuleURL ) {

    // Q: What if processingId become too large (e.g. infinity)?
    // A: Because Number.MAX_SAFE_INTEGER is pretty large (at least, 2 ** 52 ),
    //    it is not so easy to become out of bounds.
    //
    this.processingId_next = 0;

    if ( this.the_processingId_Resulter_Map )
      this.the_processingId_Resulter_Map.clear();
    else
      this.the_processingId_Resulter_Map = new processingId_Resulter_Map();

    AsyncWorker_Proxy.createWorker_byModuleURL.call( this, workerModuleURL );
  }

  /** @override */
  disposeResources() {

    if ( this.worker ) {
      // Note: No processingId, because this command needs not return value.
      this.postCommandArgs( [ "disposeResources" ] );
      this.worker = null;
    }

!!! ...unfinished... (2022/09/26)
//should rejecct all pending Resulter.

    this.workerBlobObjectURL_dispose();

    this.workerOptions = undefined;
    this.workerURL = undefined;
    this.workerModuleURL = undefined;

    this.the_processingId_Resulter_Map.clear();

    this.processingId_next = undefined;

    super.disposeResources();
  }

  /** */
  workerBlobObjectURL_dispose() {
    if ( this.workerBlobObjectURL ) {
      URL.revokeObjectURL( this.workerBlobObjectURL );
      this.workerBlobObjectURL = null;
    }
  }

  /**
   * @param {AsyncWorker_Proxy} this
   *   The worker proxy.
   */
  static createWorker_byModuleURL( workerModuleURL ) {
    this.workerModuleURL = workerModuleURL;

//!!! (2022/09/22 Remarked)
// Deprecated, because of cross origin web worker issue. Please use
// .WorkerBodyStub_create_Codes_BlobObjectURL() instead.)
//
//     // (2022/09/21 Remarked) Use WorkerBodyStub_create_Codes_BlobObjectURL() instead.
//     //this.workerURL = AsyncWorker_Proxy.create_WorkerBodyStub_URL( workerModuleURL );
//
//     // (2022/09/21 Remarked) Use WorkerBodyStub_create_Codes_BlobObjectURL() instead.
//     //let workerDataURI
//     //  = AsyncWorker_Proxy.create_WorkerBodyStub_Codes_DataURI( workerModuleURL );
//     //this.workerURL = workerDataURI;

    // Use blob object URL as a workaround for cross origin web worker problem (which
    // will be encountered when use WorkerBodyStub_URL or WorkerBodyStub_Codes_DataURI).
    this.workerBlobObjectURL
      = AsyncWorker_Proxy.WorkerBodyStub_create_Codes_BlobObjectURL( workerModuleURL );
    this.workerURL = this.workerBlobObjectURL;

    // Q: Why not use "module" type worker?
    // A: A "module" type worker can not use importScripts() to load global library.
    //    (e.g. tensorflow.js)
    //
    // On the other hand, a "classic" type worker can use both importScripts() and
    // import() to load global library and module.
    //
    this.workerOptions = null;

    this.worker = new Worker( this.workerURL, this.workerOptions );

    // Register callback from the web worker.
    this.worker.onmessage
      = AsyncWorker_Proxy.onmessage_from_AsyncWorker_Body.bind( this );
  }

// //!!! (2022/09/22 Remarked)
// Deprecated, because of cross origin web worker issue. Please use
// .WorkerBodyStub_create_Codes_BlobObjectURL() instead.)
//
//   /**
//    * (Deprecated, because of cross origin web worker issue. Please use
//    * .WorkerBodyStub_create_Codes_BlobObjectURL() instead.)
//    *
//    * Create a URL string of AsyncWorker_BodyStub.js which is the main (i.e. body)
//    * javascript file of web worker. It is viewed as a classic javascript file (i.e.
//    * not an importable module). But it will load specified workerModuleURL (in its
//    * query string) as a module.
//    *
//    * @param {string} workerModuleURL
//    *   An (absolute) URL to a javascript module file. It will be imported
//    * (asynchronously) by classic javascript file AsyncWorker_BodyStub.js.
//    */
//   static create_WorkerBodyStub_URL( workerModuleURL ) {
//     let encodedWorkerModuleURL = encodeURIComponent( workerModuleURL );
//
//     // Assume the web worker module javascript file is a sibling file (i.e. inside
//     // the same folder) of this module file.
//     let workerBodyStubURL = new URL( "AsyncWorker_BodyStub.js", import.meta.url );
//     let url = `${workerBodyStubURL}?workerModuleURL=${encodedWorkerModuleURL}`;
//     return url;
//   }
//
//   /**
//    * (Deprecated, because of cross origin web worker issue. Please use
//    * .WorkerBodyStub_create_Codes_BlobObjectURL() instead.)
//    *
//    * Create a data URI representing the main (i.e. body) javascript file of web worker.
//    * It is viewed as a classic javascript file (i.e. not an importable module). But
//    * it will load specified workerModuleURL as a module.
//    *
//    * @param {string} workerModuleURL
//    *   An (absolute) URL to a javascript module file. It will be imported
//    * (asynchronously) by this generated classic javascript file (as a dataURI).
//    */
//   static WorkerBodyStub_create_Codes_DataURI( workerModuleURL ) {
//     let codes = AsyncWorker_Proxy.WorkerBodyStub_create_Codes_String( workerModuleURL );
//
//     let workerDataURI = AsyncWorker_Proxy.createDataURI_byStringASCII(
//       AsyncWorker_Proxy.JS_MIME_TYPE_STRING, codes );
//
//     return workerDataURI;
//   }
//
//   /**
//    * @param {string} strMimeType  The result mime type of the data URI.
//    * @param {string} strASCII     The ASCII text to be embedded in data URI.
//    *
//    * @return {string} Return the data URI string.
//    */
//   static createDataURI_byStringASCII( strMimeType, strASCII ) {
//     let str_base64 = btoa( strASCII );
//     let dataURI = `data:${strMimeType};base64,${str_base64}`;
//     return dataURI;
//   }

  /**
   * Create a data URI representing the main (i.e. body) javascript file of web worker.
   * It is viewed as a classic javascript file (i.e. not an importable module). But
   * it will load specified workerModuleURL as a module.
   *
   * @param {string} workerModuleURL
   *   An (absolute) URL to a javascript module file. It will be imported
   * (asynchronously) by this generated classic javascript file (as a dataURI).
   */
  static WorkerBodyStub_create_Codes_BlobObjectURL( workerModuleURL ) {
    let codes = AsyncWorker_Proxy.WorkerBodyStub_create_Codes_String( workerModuleURL );
    let blob = new Blob( [ codes ], { type: AsyncWorker_Proxy.JS_MIME_TYPE_STRING } );
    let workerBlobObjectURL = URL.createObjectURL( blob );
    return workerBlobObjectURL;
  }

  /**
   * In module (non-classic) web worker, static import is available. But the function
   * importScripts() will not be avbailable. For solving this problem, using
   * classic (non-module) web worker so that some global library (e.g. tensorflow.js)
   * can be loaded by importScripts(). And use dynamic import() to load ourselves
   * modules because import() can be used in classic (non-module) script.
   *
   * @param {string} workerModuleURL
   *   An (absolute) URL to a javascript module file. It will be imported
   * (asynchronously) by this generated classic javascript file (as a dataURI).
   *
   * @return {string}
   *   Return javascript codes string representing the main (i.e. body) javascript
   * file of web worker.
   */
  static WorkerBodyStub_create_Codes_String( workerModuleURL ) {

//!!! ...unfinished... (2022/09/15)
// What if loading workerModuleURL failed?
// Re-try (but should inform this WorkerProxy and user).

      // The codes do the following:
      //
      //   - Import the specified module URL.
      //   - Create a temporary message queue.
      //   - Collect all messages before
      //       AsyncWorker_Proxy.onmessage_from_AsyncWorker_Body() be registered
      //       as message handler.
      //
      let codes = ``
        + `import( "${workerModuleURL}" );\n`
        + `AsyncWorker_Body_temporaryMessageQueue = [];\n`
        + `onmessage = ( e ) => {\n`
        // + `  console.log( "Hello" );\n`
        // + `  console.log( e );\n`
        + `  AsyncWorker_Body_temporaryMessageQueue.push( e );\n`
        + `}\n`
        ;

      return codes;
    }

  /**
   * Send command and args (perhaps, with transferable object array) to WorkerBody
   * and expect the final result.
   *
   * @param {Array} commandArgs
   *   An array (i.e. [ comand, ...args ]) which will be sent to the WorkerBody.
   *
   *   - The command is a string which is the WorkerBody's method function to be
   *       called.
   *
   *   - The args is an array which will be destructured into multiple arguments
   *       and passed into the WorkerBody's method function.
   *
   * @param {Array} transferableObjectArray
   *   The transferable object array when postMessage. It could be undefined (but
   * can not be null).
   *
   * @return {Promise}
   *   Return a promise resolved to the final value of the processing's resulter
   * from WorkerBody. (i.e. Only the Xxx of { done: true, value: Xxx } resolved.
   * All other intermediate result { done: false, value: Yyy } are discarded.)
   */
  createPromise_by_postCommandArgs( commandArgs, transferableObjectArray ) {
    let resulter = this.createResulter_by_postCommandArgs(
      commandArgs, transferableObjectArray );
    return resulter.untilDone();
  }

  /**
   * Send command and args (perhaps, with transferable object array) to WorkerBody
   * and expect result.
   *
   * @param {Array} commandArgs
   *   An array (i.e. [ comand, ...args ]) which will be sent to the WorkerBody.
   *
   *   - The command is a string which is the WorkerBody's method function to be
   *       called.
   *
   *   - The args is an array which will be destructured into multiple arguments
   *       and passed into the WorkerBody's method function.
   *
   * @param {Array} transferableObjectArray
   *   The transferable object array when postMessage. It could be undefined (but
   * can not be null).
   *
   * @return {AsyncWorker.Resulter}
   *   Return an async generator for receving result from WorkerBody of the processing.
   */
  createResulter_by_postCommandArgs( commandArgs, transferableObjectArray ) {
    let processingId = this.processingId_next;
    ++this.processingId_next;

    // Prepare the processing's result's receiving queue before sending it.
    let resulter = this.the_processingId_Resulter_Map.createResulter_by_processingId(
      processingId );

    let processingId_commandArgs = [ processingId, ...commandArgs ];
    this.worker.postMessage( processingId_commandArgs, transferableObjectArray );

    // Check large objects are transferred (rather than copied) to ensure performance.
    {
      let bTransferred = AsyncWorker_Checker
        .ImageData_ArrayBuffer_TypedArray_isTransferred( commandArgs );

      if ( !bTransferred )
        throw Error( `AsyncWorker_Proxy.createResulter_by_postCommandArgs(): `
          + `bTransferred ( ${bTransferred} ) should be ( true ) `
          + `after commandArgs transferred to worker. `
          + `commandArgs=[ ${commandArgs} ]`
        );
    }

    return resulter;
  }

  /**
   * Send command and args (perhaps, with transferable object array) to WorkerBody
   * without expecting result. (i.e. fire-and-forget)
   *
   * @param {Array} commandArgs
   *   An array (i.e. [ comand, ...args ]) which will be sent to the WorkerBody.
   *
   *   - The command is a string which is the WorkerBody's method function to be
   *       called.
   *
   *   - The args is an array which will be destructured into multiple arguments
   *       and passed into the WorkerBody's method function.
   *
   * @param {Array} transferableObjectArray
   *   The transferable object array when postMessage. It could be undefined (but
   * can not be null).
   */
  postCommandArgs( commandArgs, transferableObjectArray ) {
    let processingId_commandArgs = [ undefined, ...commandArgs ]; // no processingId.
    this.worker.postMessage( processingId_commandArgs, transferableObjectArray );

    // Check large objects are transferred (rather than copied) to ensure performance.
    {
      let bTransferred = AsyncWorker_Checker
        .ImageData_ArrayBuffer_TypedArray_isTransferred( commandArgs );

      if ( !bTransferred )
        throw Error( `AsyncWorker_Proxy.postCommandArgs(): `
          + `bTransferred ( ${bTransferred} ) should be ( true ) `
          + `after commandArgs transferred to worker. `
          + `commandArgs=[ ${commandArgs} ]`
        );
    }
  }

  /**
   * Dispatch messages come from the owned web worker. Please register this method
   * as this WorkerProxy's Worker.onmessage.
   *
   * @param {AsyncWorker_Proxy} this
   *   The "this" should be binded to this AsyncWorker_Proxy object.
   */
  static onmessage_from_AsyncWorker_Body( e ) {

    // If the web worker source is blob object URL, revoke it after worker created
    // successfully for release resource.
    this.workerBlobObjectURL_dispose();

    // ( e.data == [ processingId, done, value ] )
    let [ processingId, done, value ] = e.data;

    this.the_processingId_Resulter_Map.resolve_or_reject_by_processingId_done_value(
      processingId, done, value );
  }

}

AsyncWorker_Proxy.JS_MIME_TYPE_STRING = "text/javascript";
<|endoftext|>


<commit_before>var b=[];document.addEventListener("keydown",function(a){if(b.push(a.key),b.splice(-6,b.length-5),"nubar"===b.join("")){a=document.querySelectorAll("a,p,h2,h3,h4");for(var c=0;c<a.length;c+=1)a[c].innerHTML=a[c].innerHTML.replace(/v/g,'\u03bd')}});
<commit_msg>#63 fixing link bug by reducing scope<commit_after>var b=[];document.addEventListener("keydown",function(a){if(b.push(a.key),b.splice(-6,b.length-5),"nubar"===b.join("")){a=document.querySelectorAll("a,p");for(var c=0;c<a.length;c+=1)a[c].innerHTML=a[c].innerHTML.replace(/v/g,'\u03bd')}});
<|endoftext|>


<commit_before>/*
 * node-analytics
 * Copyright(c) 2016 Andrew Lake
 * MIT Licensed
 */

// defaults
var fs = require('fs')
,   path = require('path')
,   http = require('http')
,   crypto = require('crypto')

// installed
var get_ip = require('ipware')().get_ip
,   mongoose = require('mongoose')
,   useragent = require('useragent')
,   maxmind = require('maxmind')
,   cookie = require('cookie')
,   async = require('async')
,   s_io = require('socket.io')
,   colours = require('colors/safe');

// globals
var db
,   geo_lookup
,   Session
,   io

// -------------------------------------------------------------

module.exports = analytics;

var opts = {
    db_host:    'localhost'
  , db_port:    27017
  , db_name:    'node_analytics_db'
  , ws_port:    8080
  , geo_ip:     true
  , mmdb:       'GeoLite2-City.mmdb'
  , log:        true
  , log_pre:    colours.green('node-analytics') + ' ||'
  , error_log:  true
  , error_pre:  colours.green('node-analytics') + ' ' + colours.red('ERROR') + ' ::'
}

console.log(colours.green('node-analytics'), "active: wait for MongoDB, GeoIP, & WebSocket")
console.log(colours.green('node-analytics'), "don't forget to copy", colours.red('node-analytics-client.js'), "from to public directory")

function analytics(opts_in){
    for(var k in opts_in) opts[k] = opts_in[k];

    mongoDB();
    geoDB();
    socketInit();

    // On page call:
    return function(req, res, next){
        // Otherwise, log page load
        var session;

        async.series([
                getSession
              , function(callback){
                    async.parallel([
                            getIp
                          , getLocation
                          , getSystem
                          , getReqData
                        ],
                        function(errs){
                            if(errs) log.error(errs)
                            callback(null);
                        }
                    );
              }
              , saveSession
            ]
          , function(errs){
              if(errs) log.error(errs)
              next();
          }
        )

        // .userID >> set cookie
        function getSession(callback){

            async.series([
                    setSession
                  , setCookies
                ],
                function(errs){
                    if(errs) log.error(errs)
                    callback(null);
                }
            )

            function setSession(callback){
                var cookies = cookie.parse(req.headers.cookie || '');

                // cookies.na_session  :: session._id
                // cookies.na_user     :: session.user

                // Establish session: new/old session? new/old user?
                if(cookies.na_session){
                    log('Session cookie found:', cookies.na_session)

                    Session.findById(cookies.na_session, function(err, result){
                        if(err) log.error(err)

                        if(!result){
                            log.error('Session not found :: id[cookie]:', cookies.na_session)

                            // send to check if user instead
                            if(cookies.na_user) userSession()
                            else newSession()
                        }
                        else {
                            log('Session continues :: id:', cookies.na_session)
                            session = result;

                            callback(null)
                        }
                    })
                }
                else if(cookies.na_user) userSession()
                else newSession()

                function userSession(){
                    log('User cookie found:', cookies.na_user)
                    session = new Session({ user: cookies.na_user })
                    log('Old user, new session :: user:', session.user)

                    callback(null)
                }
                function newSession(){
                    session = new Session();
                    session.user = session._id.toString();
                    log('New user, new session :: user:', session.user)

                    callback(null)
                }
            }

            function setCookies(callback){
                // Set cookies
                res.cookie('na_session', session._id.toString(), {
                    maxAge:     1000 * 60 * 15              // 15 mins
                  , httpOnly:   true
                })
                res.cookie('na_user', session.user, {
                    maxAge:     1000 * 60 * 60 * 24 * 365   // 1 year
                  , httpOnly:   true
                })

                callback(null)
            }

            function encrypt(text){
                var cipher = crypto.createCipher('aes192', 'a password');
                var encrypted = cipher.update(text, 'utf8', 'hex');
                encrypted += cipher.final('hex');

                log('Encrypted', text, encrypted)

                return encrypted;
            }
            function decrypt(text){
                var decipher = crypto.createDecipher('aes192', 'a password');

                var decrypted = decipher.update(text, 'hex', 'utf8');
                decrypted += decipher.final('utf8');

                log('Decrypted', text, decrypted)

                return decrypted;
            }
        }

        // .ip
        function getIp(callback){
            session.ip = get_ip(req).clientIp
            callback(null)
        }

        // .geo :: .city, .state, .country
        function getLocation(callback){
            if(!geo_lookup) return callback(null);

            var loc = geo_lookup.get(session.ip)

            if(loc.city) session.geo.city = loc.city.names.en;
            if(loc.subdivisions) session.geo.state = loc.subdivisions[0].iso_code;
            if(loc.country) session.geo.country = loc.country.iso_code;
            if(loc.continent) session.geo.continent = loc.continent.code;
            if(loc.location) session.geo.time_zone = loc.location.time_zone;

            callback(null)
        }

        // .system :: .os{, .broswer{ .name, .version
        function getSystem(callback){
            var agent = useragent.parse(req.headers['user-agent']);
            var os = agent.os;
            session.system.browser.name = agent.family;
            session.system.browser.version = agent.major + '.' + agent.minor + '.' + agent.patch

            session.system.os.name = os.family;
            session.system.os.version = os.major + '.' + os.minor + '.' + os.patch

            callback(null)
        }

        // .req :: .host, .url, .query, .ref
        function getReqData(callback){
            var sesreq = {}

            sesreq.host = req.hostname;
            sesreq.url  = req.url;

            for(var field in req.query){
                if(field === 'ref') sesreq.ref = req.query[field]
                else {
                    sesreq.query.push({
                        field: field
                      , value: req.query[field]
                    })
                }
            }

            session.reqs.push(sesreq)

            res.cookie('na_req_ind', session.reqs.length - 1, {
                    maxAge:     1000 * 60 * 15              // 15 mins
                  , httpOnly:   true
                })

            callback(null)
        }

        // CLIENT
        // resolution [once]
        // session_length [total]
        // time
        // reaches
        // pauses
        // clicks

        function saveSession(callback){
            // Update DB
            session.save(function(err){
                if(err) return callback(err)

                log.session(session, 'Session instantiated')
                callback(null)
            })
        }
    }
}

function mongoDB(){
    // Connect to MongoDB
    var db_url = 'mongodb://' + opts.db_host + ':' + opts.db_port + '/' + opts.db_name
    db = mongoose.connect(db_url).connection;
    db.on('error', function(err) {
        log.error('MongoDB error: Data will not be saved :: err:', err)
    });
    db.once('open', function() {
        log('MongoDB connection successful')
    });

    // Schema
    var Session_Schema = mongoose.Schema({
        user: { type: String, index: true }
      , date: { type: Date, default: Date.now }
      , ip: String
      , geo: {
                city:    { type: String, index: true }
              , state:   { type: String, index: true }
              , country: { type: String, index: true }
              , continent: { type: String, index: true }
              , time_zone: { type: String, index: true }
            }
      , system: {
                os: {
                    name: String
                  , version: String
                }
              , browser: {
                    name: String
                  , version: String
                }
            }
      , time: Number
      , resolution: {
                width: Number
              , height: Number
            }
      , reqs: [{
                host: String
              , url: { type: String, index: true }
              , query: [{ field: String, value: String }]
              , ref: { type: String, index: true }
              , time: Number
              , reaches: [String]
              , pauses: [{
                    section: String
                  , time: Number
                }]
              , clicks: [String]
           }]
    })

    Session = mongoose.model('Session', Session_Schema);
}

function geoDB(){
    // Check for mmdb
    if(opts.geo_ip){
        fs.stat(opts.mmdb, function(err, stats){
            if(err) return log.error(err, 'GeoIP DB file not found :: Path:', opts.mmdb)

            try { geo_lookup = maxmind.open(opts.mmdb) }
            catch(e){ return log.error('GeoIP DB read error :: err:', e) }

            log('GeoIP DB loaded successfully')
        })
    }
}

function socketInit(){
    io = s_io(opts.ws_port);
    io.use(function(socket, next){
        var cookies = cookie.parse(socket.handshake.headers.cookie || null);
        if(cookies && cookies.na_session) next();
        else log.error('Socket authentication error; no session cookie')
    })
    io.on('connection', function(socket) {
        var cookies = cookie.parse(socket.handshake.headers.cookie || '');
        var session_id = cookies.na_session;
        var req_index = parseInt(cookies.na_req_ind)
        var session;

        var session_start = Date.now();

        if(session_id){
            Session.findById(session_id, function(err, result){
                if(err) return log.error('Session find error :: id[socket]', session_id, err)
                if(!result) return log.error('Session not found :: id[socket]', session_id)

                session = result;
                log.session(session, 'Socket connected')
                socketResponse();
            })
        }

        function socketResponse(){
            socket.on('resolution', function(params){
                // Set only once
                if(!session.resolution){
                    session.resolution = params;
                    dbUpdate(session, 'resolution', params)
                }
            })
            socket.on('click', function(id){
                session.reqs[req_index].clicks.push(id);
                dbUpdate(session, 'click', id)
            })
            socket.on('reach', function(id){
                session.reqs[req_index].reaches.push(id);
                dbUpdate(session, 'reach', id)
            })
            socket.on('pause', function(params){
               session.reqs[req_index].pauses.push(params);
               dbUpdate(session, 'pause', params)
            })

            // Disconnection
            socket.on('disconnect', function() {
                // function is only to allow minimization
                (function(){
                    // group session time from req times; update
                    var t = (Date.now() - session_start) / 1000;
                    session.reqs[req_index].time = t;

                    var session_t = 0;
                    for(var i = 0; i < session.reqs.length; i++) session_t += session.reqs[i].time;
                    session.session_time = session_t;

                    dbUpdate(session, 'time', t);
                })();

                log.session(session, 'Socket disconnection');
            })
        }
    })

    log('Websocket server established');
}

function dbUpdate(session, service, data){
    session.save(function(err, saved, numAffected) {
        if(err) return log.error('socket', service, 'update error ::', err)
        log.session(saved, 'socket', service, 'updated [', data, ']')
    })
}

var log = function(){
    if(opts.log){
        var args = Array.prototype.slice.call(arguments);
        args.unshift(opts.log_pre);

        console.log.apply(console, args);
    }
}
log.error = function(){
    if(opts.error_log){
        var args = Array.prototype.slice.call(arguments);
        args.unshift(opts.error_pre);

        console.error.apply(console, args);
    }
}
log.session = function(session){
    if(opts.log){
        // build ident
        var user = session.user;
        var ident = user.substr(user.length - 6);

        if(session.geo){
            if(session.geo.city){
                ident += ' ' + session.geo.city
                if(session.geo.state) ident += ', ' + session.geo.state
            }
            else if(session.geo.state){
            ident += ' ' + session.geo.state
            if(session.geo.country) ident += ', ' + session.geo.country
        }
        }

        // substitute ident for session in args
        var args = Array.prototype.slice.call(arguments);
        args[0] = colours.blue(ident) + ' ||';

        // add prefix to start
        args.unshift(opts.log_pre);

        console.log.apply(console, args);
    }
}<commit_msg>logger now prints date<commit_after>/*
 * node-analytics
 * Copyright(c) 2016 Andrew Lake
 * MIT Licensed
 */

// defaults
var fs = require('fs')
,   path = require('path')
,   http = require('http')
,   crypto = require('crypto')

// installed
var get_ip = require('ipware')().get_ip
,   mongoose = require('mongoose')
,   useragent = require('useragent')
,   maxmind = require('maxmind')
,   cookie = require('cookie')
,   async = require('async')
,   s_io = require('socket.io')
,   colours = require('colors/safe');

// globals
var db
,   geo_lookup
,   Session
,   io

// -------------------------------------------------------------

module.exports = analytics;

var opts = {
    db_host:    'localhost'
  , db_port:    27017
  , db_name:    'node_analytics_db'
  , ws_port:    8080
  , geo_ip:     true
  , mmdb:       'GeoLite2-City.mmdb'
  , log:        true
  , log_pre:    colours.green('node-analytics') + ' ||'
  , error_log:  true
  , error_pre:  colours.green('node-analytics') + ' ' + colours.red('ERROR') + ' ::'
}

console.log(colours.green('node-analytics'), "active: wait for MongoDB, GeoIP, & WebSocket")
console.log(colours.green('node-analytics'), "don't forget to copy", colours.red('node-analytics-client.js'), "from to public directory")

function analytics(opts_in){
    for(var k in opts_in) opts[k] = opts_in[k];

    mongoDB();
    geoDB();
    socketInit();

    // HTTP request:
    return function(req, res, next){
        // Otherwise, log page load
        var session;

        async.series([
                getSession
              , function(callback){
                    async.parallel([
                            getIp
                          , getLocation
                          , getSystem
                          , getReqData
                        ],
                        function(errs){
                            if(errs) log.error(errs)
                            callback(null);
                        }
                    );
              }
              , saveSession
            ]
          , function(errs){
              if(errs) log.error(errs)
              next();
          }
        )

        // .userID >> set cookie
        function getSession(callback){

            async.series([
                    setSession
                  , setCookies
                ],
                function(errs){
                    if(errs) log.error(errs)
                    callback(null);
                }
            )

            function setSession(callback){
                var cookies = cookie.parse(req.headers.cookie || '');

                // cookies.na_session  :: session._id
                // cookies.na_user     :: session.user

                // Establish session: new/old session? new/old user?
                if(cookies.na_session){
                    log('Session cookie found:', cookies.na_session)

                    Session.findById(cookies.na_session, function(err, result){
                        if(err) log.error(err)

                        if(!result){
                            log.error('Session not found :: id[cookie]:', cookies.na_session)

                            // send to check if user instead
                            if(cookies.na_user) userSession()
                            else newSession()
                        }
                        else {
                            log('Session continues :: id:', cookies.na_session)
                            session = result;

                            callback(null)
                        }
                    })
                }
                else if(cookies.na_user) userSession()
                else newSession()

                function userSession(){
                    log('User cookie found:', cookies.na_user)
                    session = new Session({ user: cookies.na_user })
                    log('Old user, new session :: user:', session.user)

                    callback(null)
                }
                function newSession(){
                    session = new Session();
                    session.user = session._id.toString();
                    log('New user, new session :: user:', session.user)

                    callback(null)
                }
            }

            function setCookies(callback){
                // Set cookies
                res.cookie('na_session', session._id.toString(), {
                    maxAge:     1000 * 60 * 15              // 15 mins
                  , httpOnly:   true
                })
                res.cookie('na_user', session.user, {
                    maxAge:     1000 * 60 * 60 * 24 * 365   // 1 year
                  , httpOnly:   true
                })

                callback(null)
            }

            function encrypt(text){
                var cipher = crypto.createCipher('aes192', 'a password');
                var encrypted = cipher.update(text, 'utf8', 'hex');
                encrypted += cipher.final('hex');

                log('Encrypted', text, encrypted)

                return encrypted;
            }
            function decrypt(text){
                var decipher = crypto.createDecipher('aes192', 'a password');

                var decrypted = decipher.update(text, 'hex', 'utf8');
                decrypted += decipher.final('utf8');

                log('Decrypted', text, decrypted)

                return decrypted;
            }
        }

        // .ip
        function getIp(callback){
            session.ip = get_ip(req).clientIp
            callback(null)
        }

        // .geo :: .city, .state, .country
        function getLocation(callback){
            if(!geo_lookup) return callback(null);

            var loc = geo_lookup.get(session.ip)

            if(loc.city) session.geo.city = loc.city.names.en;
            if(loc.subdivisions) session.geo.state = loc.subdivisions[0].iso_code;
            if(loc.country) session.geo.country = loc.country.iso_code;
            if(loc.continent) session.geo.continent = loc.continent.code;
            if(loc.location) session.geo.time_zone = loc.location.time_zone;

            callback(null)
        }

        // .system :: .os{, .broswer{ .name, .version
        function getSystem(callback){
            var agent = useragent.parse(req.headers['user-agent']);
            var os = agent.os;
            session.system.browser.name = agent.family;
            session.system.browser.version = agent.major + '.' + agent.minor + '.' + agent.patch

            session.system.os.name = os.family;
            session.system.os.version = os.major + '.' + os.minor + '.' + os.patch

            callback(null)
        }

        // .req :: .host, .url, .query, .ref
        function getReqData(callback){
            var sesreq = {}

            sesreq.host = req.hostname;
            sesreq.url  = req.url;

            for(var field in req.query){
                if(field === 'ref') sesreq.ref = req.query[field]
                else {
                    sesreq.query.push({
                        field: field
                      , value: req.query[field]
                    })
                }
            }

            session.reqs.push(sesreq)

            res.cookie('na_req_ind', session.reqs.length - 1, {
                    maxAge:     1000 * 60 * 15              // 15 mins
                  , httpOnly:   true
                })

            callback(null)
        }

        // CLIENT
        // resolution [once]
        // session_length [total]
        // time
        // reaches
        // pauses
        // clicks

        function saveSession(callback){
            // Update DB
            session.save(function(err){
                if(err) return callback(err)

                log.session(session, 'Session instantiated')
                callback(null)
            })
        }
    }
}

function mongoDB(){
    // Connect to MongoDB
    var db_url = 'mongodb://' + opts.db_host + ':' + opts.db_port + '/' + opts.db_name
    db = mongoose.connect(db_url).connection;
    db.on('error', function(err) {
        log.error('MongoDB error: Data will not be saved :: err:', err)
    });
    db.once('open', function() {
        log('MongoDB connection successful')
    });

    // Schema
    var Session_Schema = mongoose.Schema({
        user: { type: String, index: true }
      , date: { type: Date, default: Date.now }
      , ip: String
      , geo: {
                city:    { type: String, index: true }
              , state:   { type: String, index: true }
              , country: { type: String, index: true }
              , continent: { type: String, index: true }
              , time_zone: { type: String, index: true }
            }
      , system: {
                os: {
                    name: String
                  , version: String
                }
              , browser: {
                    name: String
                  , version: String
                }
            }
      , time: Number
      , resolution: {
                width: Number
              , height: Number
            }
      , reqs: [{
                host: String
              , url: { type: String, index: true }
              , query: [{ field: String, value: String }]
              , ref: { type: String, index: true }
              , time: Number
              , reaches: [String]
              , pauses: [{
                    section: String
                  , time: Number
                }]
              , clicks: [String]
           }]
    })

    Session = mongoose.model('Session', Session_Schema);
}

function geoDB(){
    // Check for mmdb
    if(opts.geo_ip){
        fs.stat(opts.mmdb, function(err, stats){
            if(err) return log.error(err, 'GeoIP DB file not found :: Path:', opts.mmdb)

            try { geo_lookup = maxmind.open(opts.mmdb) }
            catch(e){ return log.error('GeoIP DB read error :: err:', e) }

            log('GeoIP DB loaded successfully')
        })
    }
}

function socketInit(){
    io = s_io(opts.ws_port);
    io.use(function(socket, next){
        var cookies = cookie.parse(socket.handshake.headers.cookie || null);
        if(cookies && cookies.na_session) next();
        else log.error('Socket authentication error; no session cookie')
    })
    io.on('connection', function(socket) {
        var cookies = cookie.parse(socket.handshake.headers.cookie || '');
        var session_id = cookies.na_session;
        var req_index = parseInt(cookies.na_req_ind)
        var session;

        var session_start = Date.now();

        if(session_id){
            Session.findById(session_id, function(err, result){
                if(err) return log.error('Session find error :: id[socket]', session_id, err)
                if(!result) return log.error('Session not found :: id[socket]', session_id)

                session = result;
                log.session(session, 'Socket connected')
                socketResponse();
            })
        }

        function socketResponse(){
            socket.on('resolution', function(params){
                // Set only once
                if(!session.resolution){
                    session.resolution = params;
                    dbUpdate(session, 'resolution', params)
                }
            })
            socket.on('click', function(id){
                session.reqs[req_index].clicks.push(id);
                dbUpdate(session, 'click', id)
            })
            socket.on('reach', function(id){
                session.reqs[req_index].reaches.push(id);
                dbUpdate(session, 'reach', id)
            })
            socket.on('pause', function(params){
               session.reqs[req_index].pauses.push(params);
               dbUpdate(session, 'pause', params)
            })

            // Disconnection
            socket.on('disconnect', function() {
                // function is only to allow minimization
                (function(){
                    // group session time from req times; update
                    var t = (Date.now() - session_start) / 1000;
                    session.reqs[req_index].time = t;

                    var session_t = 0;
                    for(var i = 0; i < session.reqs.length; i++) session_t += session.reqs[i].time;
                    session.session_time = session_t;

                    dbUpdate(session, 'time', t);
                })();

                log.session(session, 'Socket disconnection');
            })
        }
    })

    log('Websocket server established');
}

function dbUpdate(session, service, data){
    session.save(function(err, saved, numAffected) {
        if(err) return log.error('socket', service, 'update error ::', err)
        log.session(saved, 'socket', service, 'updated [', data, ']')
    })
}

var log = function(){
    if(opts.log){
        var args = Array.prototype.slice.call(arguments);
        args = log.prefix(args)

        console.log.apply(console, args);
    }
}
log.error = function(){
    if(opts.error_log){
        var args = Array.prototype.slice.call(arguments);
        args = log.prefix(args, true)

        console.error.apply(console, args);
    }
}
log.session = function(session){
    if(opts.log){
        // build ident
        var user = session.user;
        var ident = user.substr(user.length - 6);

        if(session.geo){
            if(session.geo.city){
                ident += ' ' + session.geo.city
                if(session.geo.state) ident += ', ' + session.geo.state
            }
            else if(session.geo.state){
            ident += ' ' + session.geo.state
            if(session.geo.country) ident += ', ' + session.geo.country
        }
        }

        // substitute ident for session in args
        var args = Array.prototype.slice.call(arguments);
        args[0] = colours.blue(ident) + ' ||';

        // add prefix to start
        args = log.prefix(args)

        console.log.apply(console, args);
    }
}
log.prefix = function(args, error){
    // [0] => prefix, [1] => date
    args.unshift(logDate());

    if(error) args.unshift(opts.error_pre);
    else args.unshift(opts.log_pre);

    return args;

    function logDate(){
        var d = new Date();
        return  d.getFullYear() + '/' +
                fZ(d.getMonth() + 1) + '/' +
                fZ(d.getDate()) + ' ' +
                fZ(d.getHours()) + ':' +
                fZ(d.getMinutes()) + ':' +
                fZ(d.getSeconds()) + ' ' +
                tz(d)

        function fZ(v){ return ('0' + v).slice(-2); }
        function tz(d){
             var m = d.getTimezoneOffset() / 60;
             if(m >= 0) return 'GMT+' + m;
             return 'GMT' + m;
        }
    }
}<|endoftext|>


<commit_before>/*jslint indent: 2, nomen: true, maxlen: 100, white: true  plusplus: true, browser: true*/
/*global describe, beforeEach, afterEach, it, spyOn, expect*/
/*global arangoHelper*/

(function () {
    "use strict";

    describe("The document view", function () {

        var model, view, div;

        beforeEach(function () {
            div = document.createElement("div");
            div.id = "content";
            document.body.appendChild(div);

            model = new window.arangoDocument({
                _key: "123",
                _id: "v/123",
                _rev: "123",
                name: "alice"
            });

            view = new window.DocumentView({
                collection: new window.arangoDocument()
            });

            view.collection.add(model);
            view.render();
        });

        afterEach(function () {
            document.body.removeChild(div);
        });

        it("assert the basics", function () {
            expect(view.colid).toEqual(0);
            expect(view.colid).toEqual(0);
            expect(view.events).toEqual({
                "click #saveDocumentButton": "saveDocument",
                "dblclick #documentEditor tr" : "addProperty",
                "click #tableDiv .showHotkeyHelp" : "shortcutModal"
            });
        });

        it("should copy a model into editor", function () {
            spyOn(view.editor, "set");
            view.fillEditor();
            expect(view.editor.set).toHaveBeenCalled();
        });

        it("should save type document", function () {
            view.type = 'document';
            spyOn(view.collection, "saveDocument").andReturn(true);
            view.saveDocument();
            expect(view.collection.saveDocument).toHaveBeenCalled();
        });

        it("should save type edge", function () {
            view.type = 'edge';
            spyOn(view.collection, "saveEdge").andReturn(true);
            view.saveDocument();
            expect(view.collection.saveEdge).toHaveBeenCalled();
        });

        it("should save type edge and return error", function () {
            view.type = 'edge';
            spyOn(view.collection, "saveEdge").andReturn(false);
            spyOn(arangoHelper, "arangoError");
            view.saveDocument();
            expect(arangoHelper.arangoError).toHaveBeenCalled();
        });

        it("should save type document and return error", function () {
            view.type = 'document';
            spyOn(view.collection, "saveDocument").andReturn(false);
            spyOn(arangoHelper, "arangoError");
            view.saveDocument();
            expect(arangoHelper.arangoError).toHaveBeenCalled();
        });

        it("check document view type check positive", function () {
            view.collection.add(model);
            spyOn(view, "fillEditor");

            spyOn(view.collection, "getEdge").andReturn(true);
            spyOn(view.collection, "getDocument").andReturn(true);
            var result = view.typeCheck('edge');
            expect(result).toEqual(true);
            expect(view.collection.getEdge).toHaveBeenCalled();
            result = view.typeCheck('document');
            expect(view.collection.getDocument).toHaveBeenCalled();
            expect(result).toEqual(true);
        });

        it("check document view type check negative", function () {
            view.collection.add(model);
            spyOn(view, "fillEditor");

            var result = view.typeCheck('easddge');
            expect(result).not.toEqual(true);

            result = view.typeCheck(false);
            expect(result).not.toEqual(true);

            result = view.typeCheck(123);
            expect(result).not.toEqual(true);
        });

        it("should remove readonly keys", function () {
            var object = {
                    hello: 123,
                    wrong: true,
                    _key: "123",
                    _rev: "adasda",
                    _id: "paosdjfp1321"
                },
                shouldObject = {
                    hello: 123,
                    wrong: true
                },
                result = view.removeReadonlyKeys(object);

            expect(result).toEqual(shouldObject);
        });

        it("should modify the breadcrumb", function () {
            var bar = document.createElement("div"),
                emptyBar = document.createElement("div");
            bar.id = 'transparentHeader';

            view.breadcrumb();
            expect(emptyBar).not.toBe(bar);
        });

        it("escaped", function () {
            expect(view.escaped('&<>"\'')).toEqual("&<>"'");
        });

        it("should addProperty in existing json document", function () {

            var eDummy = {
                    currentTarget :  {
                        cells : [
                            "",
                            "",
                            {
                                childNodes : [
                                    {
                                        childNodes : [
                                            {
                                                childNodes : [
                                                    {
                                                        childNodes : [
                                                            "",
                                                            {
                                                                childNodes : [
                                                                    {
                                                                        textContent : "bla"

                                                                    }
                                                                ]
                                                            }
                                                        ]
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                }, editorDummy  = {

                node : {
                    search : function () {

                    }
                }

            }, nodeDummy1 =  {elem : "value", node : {_onInsertAfter : function () {}}},
            nodeDummy2 =  {elem : "field", node : {_onInsertAfter : function () {}}} ;
            spyOn(editorDummy.node, "search").andReturn([
                nodeDummy1,
                nodeDummy2,
                nodeDummy1
            ]);
            spyOn(nodeDummy1.node, "_onInsertAfter");
            spyOn(nodeDummy2.node, "_onInsertAfter");
            view.editor = editorDummy;

            view.addProperty(eDummy);

            expect(editorDummy.node.search).toHaveBeenCalledWith("bla");
            expect(nodeDummy1.node._onInsertAfter).not.toHaveBeenCalled();
            expect(nodeDummy2.node._onInsertAfter).toHaveBeenCalledWith(
                undefined, undefined, "auto");

        });

        it("should addProperty in existing json document in first position", function () {

            var eDummy = {
                    currentTarget :  {
                        cells : [
                            "",
                            "",
                            {
                                childNodes : [
                                    {
                                        childNodes : [
                                            {
                                                childNodes : [
                                                    {
                                                        childNodes : [
                                                            "",
                                                            {
                                                                childNodes : [
                                                                    {
                                                                        textContent : "object"

                                                                    }
                                                                ]
                                                            }
                                                        ]
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                }, editorDummy  = {

                    node : {
                        search : function () {

                        },
                        childs : [
                            {
                                focus : function () {},
                                _onInsertBefore : function () {

                                }
                            }
                        ]
                    },
                    get : function () {

                    },
                    set : function () {

                    }

                }, nodeDummy1 =  {elem : "value", _onInsertAfter : function () {}},
                nodeDummy2 =  {elem : "value", _onInsertAfter : function () {}} ;
            spyOn(editorDummy.node, "search").andReturn([
                nodeDummy1,
                nodeDummy2,
                nodeDummy1
            ]);
            spyOn(nodeDummy1, "_onInsertAfter");
            spyOn(nodeDummy2, "_onInsertAfter");

            spyOn(editorDummy, "get").andReturn([
                nodeDummy1,
                nodeDummy2,
                nodeDummy1
            ]);

            spyOn(editorDummy, "set");
            spyOn(editorDummy.node.childs[0], "_onInsertBefore");
            spyOn(editorDummy.node.childs[0], "focus");

            view.editor = editorDummy;

            view.addProperty(eDummy);

            expect(editorDummy.node.search).not.toHaveBeenCalled();
            expect(editorDummy.node.childs[0].focus).not.toHaveBeenCalled();
            expect(editorDummy.node.childs[0]._onInsertBefore).toHaveBeenCalledWith(
                undefined, undefined, "auto");
            expect(editorDummy.get).toHaveBeenCalled();
            expect(editorDummy.set).not.toHaveBeenCalled();

        });

        it("should addProperty in new json document in first position", function () {

            var eDummy = {
                    currentTarget :  {
                        cells : [
                            "",
                            "",
                            {
                                childNodes : [
                                    {
                                        childNodes : [
                                            {
                                                childNodes : [
                                                    {
                                                        childNodes : [
                                                            "",
                                                            {
                                                                childNodes : [
                                                                    {
                                                                        textContent : "object"

                                                                    }
                                                                ]
                                                            }
                                                        ]
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                }, editorDummy  = {

                    node : {
                        search : function () {

                        },
                        childs : [
                            {
                                focus : function () {},
                                _onInsertBefore : function () {

                                }
                            }
                        ]
                    },
                    get : function () {

                    },
                    set : function () {

                    }

                }, nodeDummy1 =  {elem : "value", _onInsertAfter : function () {}},
                nodeDummy2 =  {elem : "value", _onInsertAfter : function () {}} ;
            spyOn(editorDummy.node, "search").andReturn([
                nodeDummy1,
                nodeDummy2,
                nodeDummy1
            ]);
            spyOn(nodeDummy1, "_onInsertAfter");
            spyOn(nodeDummy2, "_onInsertAfter");

            spyOn(editorDummy, "get").andReturn(undefined);

            spyOn(editorDummy, "set");
            spyOn(editorDummy.node.childs[0], "_onInsertBefore");
            spyOn(editorDummy.node.childs[0], "focus");

            view.editor = editorDummy;

            view.addProperty(eDummy);

            expect(editorDummy.node.search).not.toHaveBeenCalled();
            expect(editorDummy.node.childs[0].focus).toHaveBeenCalledWith("field");
            expect(editorDummy.node.childs[0]._onInsertBefore).not.toHaveBeenCalled();
            expect(editorDummy.get).toHaveBeenCalled();
            expect(editorDummy.set).toHaveBeenCalledWith({
                "": ""
            });

        });

    });

}());
<commit_msg>removed .showHotkeyHelp<commit_after>/*jslint indent: 2, nomen: true, maxlen: 100, white: true  plusplus: true, browser: true*/
/*global describe, beforeEach, afterEach, it, spyOn, expect*/
/*global arangoHelper*/

(function () {
    "use strict";

    describe("The document view", function () {

        var model, view, div;

        beforeEach(function () {
            div = document.createElement("div");
            div.id = "content";
            document.body.appendChild(div);

            model = new window.arangoDocument({
                _key: "123",
                _id: "v/123",
                _rev: "123",
                name: "alice"
            });

            view = new window.DocumentView({
                collection: new window.arangoDocument()
            });

            view.collection.add(model);
            view.render();
        });

        afterEach(function () {
            document.body.removeChild(div);
        });

        it("assert the basics", function () {
            expect(view.colid).toEqual(0);
            expect(view.colid).toEqual(0);
            expect(view.events).toEqual({
                "click #saveDocumentButton": "saveDocument",
                "dblclick #documentEditor tr" : "addProperty"
            });
        });

        it("should copy a model into editor", function () {
            spyOn(view.editor, "set");
            view.fillEditor();
            expect(view.editor.set).toHaveBeenCalled();
        });

        it("should save type document", function () {
            view.type = 'document';
            spyOn(view.collection, "saveDocument").andReturn(true);
            view.saveDocument();
            expect(view.collection.saveDocument).toHaveBeenCalled();
        });

        it("should save type edge", function () {
            view.type = 'edge';
            spyOn(view.collection, "saveEdge").andReturn(true);
            view.saveDocument();
            expect(view.collection.saveEdge).toHaveBeenCalled();
        });

        it("should save type edge and return error", function () {
            view.type = 'edge';
            spyOn(view.collection, "saveEdge").andReturn(false);
            spyOn(arangoHelper, "arangoError");
            view.saveDocument();
            expect(arangoHelper.arangoError).toHaveBeenCalled();
        });

        it("should save type document and return error", function () {
            view.type = 'document';
            spyOn(view.collection, "saveDocument").andReturn(false);
            spyOn(arangoHelper, "arangoError");
            view.saveDocument();
            expect(arangoHelper.arangoError).toHaveBeenCalled();
        });

        it("check document view type check positive", function () {
            view.collection.add(model);
            spyOn(view, "fillEditor");

            spyOn(view.collection, "getEdge").andReturn(true);
            spyOn(view.collection, "getDocument").andReturn(true);
            var result = view.typeCheck('edge');
            expect(result).toEqual(true);
            expect(view.collection.getEdge).toHaveBeenCalled();
            result = view.typeCheck('document');
            expect(view.collection.getDocument).toHaveBeenCalled();
            expect(result).toEqual(true);
        });

        it("check document view type check negative", function () {
            view.collection.add(model);
            spyOn(view, "fillEditor");

            var result = view.typeCheck('easddge');
            expect(result).not.toEqual(true);

            result = view.typeCheck(false);
            expect(result).not.toEqual(true);

            result = view.typeCheck(123);
            expect(result).not.toEqual(true);
        });

        it("should remove readonly keys", function () {
            var object = {
                    hello: 123,
                    wrong: true,
                    _key: "123",
                    _rev: "adasda",
                    _id: "paosdjfp1321"
                },
                shouldObject = {
                    hello: 123,
                    wrong: true
                },
                result = view.removeReadonlyKeys(object);

            expect(result).toEqual(shouldObject);
        });

        it("should modify the breadcrumb", function () {
            var bar = document.createElement("div"),
                emptyBar = document.createElement("div");
            bar.id = 'transparentHeader';

            view.breadcrumb();
            expect(emptyBar).not.toBe(bar);
        });

        it("escaped", function () {
            expect(view.escaped('&<>"\'')).toEqual("&<>"'");
        });

        it("should addProperty in existing json document", function () {

            var eDummy = {
                    currentTarget :  {
                        cells : [
                            "",
                            "",
                            {
                                childNodes : [
                                    {
                                        childNodes : [
                                            {
                                                childNodes : [
                                                    {
                                                        childNodes : [
                                                            "",
                                                            {
                                                                childNodes : [
                                                                    {
                                                                        textContent : "bla"

                                                                    }
                                                                ]
                                                            }
                                                        ]
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                }, editorDummy  = {

                node : {
                    search : function () {

                    }
                }

            }, nodeDummy1 =  {elem : "value", node : {_onInsertAfter : function () {}}},
            nodeDummy2 =  {elem : "field", node : {_onInsertAfter : function () {}}} ;
            spyOn(editorDummy.node, "search").andReturn([
                nodeDummy1,
                nodeDummy2,
                nodeDummy1
            ]);
            spyOn(nodeDummy1.node, "_onInsertAfter");
            spyOn(nodeDummy2.node, "_onInsertAfter");
            view.editor = editorDummy;

            view.addProperty(eDummy);

            expect(editorDummy.node.search).toHaveBeenCalledWith("bla");
            expect(nodeDummy1.node._onInsertAfter).not.toHaveBeenCalled();
            expect(nodeDummy2.node._onInsertAfter).toHaveBeenCalledWith(
                undefined, undefined, "auto");

        });

        it("should addProperty in existing json document in first position", function () {

            var eDummy = {
                    currentTarget :  {
                        cells : [
                            "",
                            "",
                            {
                                childNodes : [
                                    {
                                        childNodes : [
                                            {
                                                childNodes : [
                                                    {
                                                        childNodes : [
                                                            "",
                                                            {
                                                                childNodes : [
                                                                    {
                                                                        textContent : "object"

                                                                    }
                                                                ]
                                                            }
                                                        ]
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                }, editorDummy  = {

                    node : {
                        search : function () {

                        },
                        childs : [
                            {
                                focus : function () {},
                                _onInsertBefore : function () {

                                }
                            }
                        ]
                    },
                    get : function () {

                    },
                    set : function () {

                    }

                }, nodeDummy1 =  {elem : "value", _onInsertAfter : function () {}},
                nodeDummy2 =  {elem : "value", _onInsertAfter : function () {}} ;
            spyOn(editorDummy.node, "search").andReturn([
                nodeDummy1,
                nodeDummy2,
                nodeDummy1
            ]);
            spyOn(nodeDummy1, "_onInsertAfter");
            spyOn(nodeDummy2, "_onInsertAfter");

            spyOn(editorDummy, "get").andReturn([
                nodeDummy1,
                nodeDummy2,
                nodeDummy1
            ]);

            spyOn(editorDummy, "set");
            spyOn(editorDummy.node.childs[0], "_onInsertBefore");
            spyOn(editorDummy.node.childs[0], "focus");

            view.editor = editorDummy;

            view.addProperty(eDummy);

            expect(editorDummy.node.search).not.toHaveBeenCalled();
            expect(editorDummy.node.childs[0].focus).not.toHaveBeenCalled();
            expect(editorDummy.node.childs[0]._onInsertBefore).toHaveBeenCalledWith(
                undefined, undefined, "auto");
            expect(editorDummy.get).toHaveBeenCalled();
            expect(editorDummy.set).not.toHaveBeenCalled();

        });

        it("should addProperty in new json document in first position", function () {

            var eDummy = {
                    currentTarget :  {
                        cells : [
                            "",
                            "",
                            {
                                childNodes : [
                                    {
                                        childNodes : [
                                            {
                                                childNodes : [
                                                    {
                                                        childNodes : [
                                                            "",
                                                            {
                                                                childNodes : [
                                                                    {
                                                                        textContent : "object"

                                                                    }
                                                                ]
                                                            }
                                                        ]
                                                    }
                                                ]
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                }, editorDummy  = {

                    node : {
                        search : function () {

                        },
                        childs : [
                            {
                                focus : function () {},
                                _onInsertBefore : function () {

                                }
                            }
                        ]
                    },
                    get : function () {

                    },
                    set : function () {

                    }

                }, nodeDummy1 =  {elem : "value", _onInsertAfter : function () {}},
                nodeDummy2 =  {elem : "value", _onInsertAfter : function () {}} ;
            spyOn(editorDummy.node, "search").andReturn([
                nodeDummy1,
                nodeDummy2,
                nodeDummy1
            ]);
            spyOn(nodeDummy1, "_onInsertAfter");
            spyOn(nodeDummy2, "_onInsertAfter");

            spyOn(editorDummy, "get").andReturn(undefined);

            spyOn(editorDummy, "set");
            spyOn(editorDummy.node.childs[0], "_onInsertBefore");
            spyOn(editorDummy.node.childs[0], "focus");

            view.editor = editorDummy;

            view.addProperty(eDummy);

            expect(editorDummy.node.search).not.toHaveBeenCalled();
            expect(editorDummy.node.childs[0].focus).toHaveBeenCalledWith("field");
            expect(editorDummy.node.childs[0]._onInsertBefore).not.toHaveBeenCalled();
            expect(editorDummy.get).toHaveBeenCalled();
            expect(editorDummy.set).toHaveBeenCalledWith({
                "": ""
            });

        });

    });

}());
<|endoftext|>


<commit_before>var _ = require('underscore'),
	moment = require('moment'),
	React = require('react'),
	Fields = require('../../fields'),
	FormHeading = require('../../components/FormHeading'),
	Toolbar = require('../../components/Toolbar'),
	InvalidFieldType = require('../../components/InvalidFieldType');

var EditForm = React.createClass({

	getInitialState: function() {
		return {
			values: _.clone(this.props.data.fields)
		};
	},

	getFieldProps: function(field) {
		var props = _.clone(field);
		props.value = this.state.values[field.path];
		props.values = this.state.values;
		props.onChange = this.handleChange;
		props.mode = 'edit';
		return props;
	},

	handleChange: function(event) {
		var values = this.state.values;
		values[event.path] = event.value;
		this.setState({
			values: values
		});
	},

	renderNameField: function() {

		var namePath = this.props.list.namePath,
			nameField = this.props.list.nameField,
			nameIsEditable = this.props.list.nameIsEditable;

		function wrapNameField(field) {
			return (

						{field}

			);
		}

		if (nameIsEditable) {

			var nameFieldProps = this.getFieldProps(nameField);
			nameFieldProps.className = 'item-name-field';
			nameFieldProps.placeholder = nameField.label;
			nameFieldProps.label = false;

			return wrapNameField(
				React.createElement(Fields[nameField.type], nameFieldProps)
			);

		} else if (nameField && nameField.type == 'name') {
			var nameValue = this.props.data.fields[nameField.path] || {};
			nameValue = _.compact([nameValue.first, nameValue.last]).join(' ');
			return wrapNameField(

{nameValue || '(no name)'}

			);
		} else {
			return wrapNameField(

{this.props.data.fields[namePath] || '(no name)'}

			);
		}
	},

	renderTrackingMeta: function() {

		if (!this.props.list.tracking) return null;

		var elements = {},
			data = {},
			label;

		if (this.props.list.tracking.createdAt) {
			data.createdAt = this.props.data.fields[this.props.list.tracking.createdAt];
			if (data.createdAt) {
				elements.createdAt = (

						Created
						{moment(data.createdAt).format('Do MMM YY h:mm:ssa')}

				);
			}
		}

		if (this.props.list.tracking.createdBy) {
			data.createdBy = this.props.data.fields[this.props.list.tracking.createdBy];
			if (data.createdBy) {
				label = data.createdAt ? 'by' : 'Created by';
				// todo: harden logic around user name
				elements.createdBy = (

						{label}
						{data.createdBy.name.first} {data.createdBy.name.last}

				);
			}
		}

		if (this.props.list.tracking.updatedAt) {
			data.updatedAt = this.props.data.fields[this.props.list.tracking.updatedAt];
			if (data.updatedAt && (!data.createdAt || data.createdAt !== data.updatedAt)) {
				elements.updatedAt = (

						Updated
						{moment(data.updatedAt).format('Do MMM YY h:mm:ssa')}

				);
			}
		}

		if (this.props.list.tracking.updatedBy) {
			data.updatedBy = this.props.data.fields[this.props.list.tracking.updatedBy];
			if (data.updatedBy && (!data.createdBy || data.createdBy.id !== data.updatedBy.id || elements.updatedAt)) {
				label = data.updatedAt ? 'by' : 'Created by';
				elements.updatedBy = (

						{label}
						{data.updatedBy.name.first} {data.updatedBy.name.last}

				);
			}
		}

		return
{elements}
;

	},

	renderFormElements: function() {

		var elements = {},
			headings = 0;

		_.each(this.props.list.uiElements, function(el) {

			if (el.type === 'heading') {

				headings++;
				elements['h-' + headings] = React.createElement(FormHeading, el);

			} else if (el.type === 'field') {

				var field = this.props.list.fields[el.field];

				if ('function' !== typeof Fields[field.type]) {
					elements[field.path] = React.createElement(InvalidFieldType, { type: field.type, path: field.path });
					return;
				}

				elements[field.path] = React.createElement(Fields[field.type], this.getFieldProps(field));

			}

		}, this);

		return elements;

	},

	renderToolbar: function() {

		var toolbar = {};

		if (!this.props.list.noedit) {
			toolbar.save = <button type="submit" className="btn btn-save">Save</button>;
			// TODO: Confirm: 'Are you sure you want to reset your changes?'
			toolbar.reset = reset changes;
		}

		if (!this.props.list.noedit) {
			// TODO: Confirm: 'Are you sure you want to delete this ' + list.singular.toLowerCase() + '?'
			toolbar.del = delete {this.props.list.singular.toLowerCase()};
		}

		return (
			<Toolbar>
				{toolbar}
			</Toolbar>
		);

	},

	render: function() {

		return (
			<form method="post" enctype="multipart/form-data" className="item-details">
				<input type="hidden" name="action" value="updateItem" />
				<input type="hidden" name={Keystone.csrf.key} value={Keystone.csrf.value} />
				{this.renderNameField()}
				{this.renderTrackingMeta()}
				{this.renderFormElements()}
				{this.renderToolbar()}
			</form>
		);
	}

});

module.exports = EditForm;
<commit_msg>Renamed attribute<commit_after>var _ = require('underscore'),
	moment = require('moment'),
	React = require('react'),
	Fields = require('../../fields'),
	FormHeading = require('../../components/FormHeading'),
	Toolbar = require('../../components/Toolbar'),
	InvalidFieldType = require('../../components/InvalidFieldType');

var EditForm = React.createClass({

	getInitialState: function() {
		return {
			values: _.clone(this.props.data.fields)
		};
	},

	getFieldProps: function(field) {
		var props = _.clone(field);
		props.value = this.state.values[field.path];
		props.values = this.state.values;
		props.onChange = this.handleChange;
		props.mode = 'edit';
		return props;
	},

	handleChange: function(event) {
		var values = this.state.values;
		values[event.path] = event.value;
		this.setState({
			values: values
		});
	},

	renderNameField: function() {

		var namePath = this.props.list.namePath,
			nameField = this.props.list.nameField,
			nameIsEditable = this.props.list.nameIsEditable;

		function wrapNameField(field) {
			return (

						{field}

			);
		}

		if (nameIsEditable) {

			var nameFieldProps = this.getFieldProps(nameField);
			nameFieldProps.className = 'item-name-field';
			nameFieldProps.placeholder = nameField.label;
			nameFieldProps.label = false;

			return wrapNameField(
				React.createElement(Fields[nameField.type], nameFieldProps)
			);

		} else if (nameField && nameField.type == 'name') {
			var nameValue = this.props.data.fields[nameField.path] || {};
			nameValue = _.compact([nameValue.first, nameValue.last]).join(' ');
			return wrapNameField(

{nameValue || '(no name)'}

			);
		} else {
			return wrapNameField(

{this.props.data.fields[namePath] || '(no name)'}

			);
		}
	},

	renderTrackingMeta: function() {

		if (!this.props.list.tracking) return null;

		var elements = {},
			data = {},
			label;

		if (this.props.list.tracking.createdAt) {
			data.createdAt = this.props.data.fields[this.props.list.tracking.createdAt];
			if (data.createdAt) {
				elements.createdAt = (

						Created
						{moment(data.createdAt).format('Do MMM YY h:mm:ssa')}

				);
			}
		}

		if (this.props.list.tracking.createdBy) {
			data.createdBy = this.props.data.fields[this.props.list.tracking.createdBy];
			if (data.createdBy) {
				label = data.createdAt ? 'by' : 'Created by';
				// todo: harden logic around user name
				elements.createdBy = (

						{label}
						{data.createdBy.name.first} {data.createdBy.name.last}

				);
			}
		}

		if (this.props.list.tracking.updatedAt) {
			data.updatedAt = this.props.data.fields[this.props.list.tracking.updatedAt];
			if (data.updatedAt && (!data.createdAt || data.createdAt !== data.updatedAt)) {
				elements.updatedAt = (

						Updated
						{moment(data.updatedAt).format('Do MMM YY h:mm:ssa')}

				);
			}
		}

		if (this.props.list.tracking.updatedBy) {
			data.updatedBy = this.props.data.fields[this.props.list.tracking.updatedBy];
			if (data.updatedBy && (!data.createdBy || data.createdBy.id !== data.updatedBy.id || elements.updatedAt)) {
				label = data.updatedAt ? 'by' : 'Created by';
				elements.updatedBy = (

						{label}
						{data.updatedBy.name.first} {data.updatedBy.name.last}

				);
			}
		}

		return
{elements}
;

	},

	renderFormElements: function() {

		var elements = {},
			headings = 0;

		_.each(this.props.list.uiElements, function(el) {

			if (el.type === 'heading') {

				headings++;
				elements['h-' + headings] = React.createElement(FormHeading, el);

			} else if (el.type === 'field') {

				var field = this.props.list.fields[el.field];

				if ('function' !== typeof Fields[field.type]) {
					elements[field.path] = React.createElement(InvalidFieldType, { type: field.type, path: field.path });
					return;
				}

				elements[field.path] = React.createElement(Fields[field.type], this.getFieldProps(field));

			}

		}, this);

		return elements;

	},

	renderToolbar: function() {

		var toolbar = {};

		if (!this.props.list.noedit) {
			toolbar.save = <button type="submit" className="btn btn-save">Save</button>;
			// TODO: Confirm: 'Are you sure you want to reset your changes?'
			toolbar.reset = reset changes;
		}

		if (!this.props.list.noedit) {
			// TODO: Confirm: 'Are you sure you want to delete this ' + list.singular.toLowerCase() + '?'
			toolbar.del = delete {this.props.list.singular.toLowerCase()};
		}

		return (
			<Toolbar>
				{toolbar}
			</Toolbar>
		);

	},

	render: function() {

		return (
			<form method="post" encType="multipart/form-data" className="item-details">
				<input type="hidden" name="action" value="updateItem" />
				<input type="hidden" name={Keystone.csrf.key} value={Keystone.csrf.value} />
				{this.renderNameField()}
				{this.renderTrackingMeta()}
				{this.renderFormElements()}
				{this.renderToolbar()}
			</form>
		);
	}

});

module.exports = EditForm;
<|endoftext|>


<commit_before>exports = module.exports;

exports.CreateTask = function() {
    var sql = "INSERT INTO `tasks` (text, notes, priority, created, completed, estimate, actual) VALUES ( ?, ?, ?, ?, ?, ?, ?)"

    return sql;
}

// LOL update is tough, actually
exports.UpdateTask = function(task) {
    if(!task.idtasks) {
        // no ID, no Update
        return "";
    }
    var fields = 0;
    var sql = "UPDATE `tasks` SET ";
    if(task.text) {
        sql += "text = ?";
    }
}

exports.CompleteTask = function(task) {
    if(!task.idtasks) {
        // no ID no completion
        return "";
    }
    var sql = "UPDATE `tasks` SET completed = ? WHERE `task`.`idtasks` = ?";
    return sql;
}

exports.DeleteTask = function(task) {
    if(!task.idtasks) {
        // no ID no deletion
    }
    var sql = "DELETE FROM `tasks` WHERE idtasks = ?";
    return sql;
}

exports.FindTask = function(task) {
    if(!task.idtasks) {
        // no ID, not sure how to find it
        // TODO: Search function
    }
}<commit_msg>some newline stuff, I think<commit_after>exports = module.exports;

exports.CreateTask = function() {
    var sql = "INSERT INTO `tasks` (text, notes, priority, created, completed, estimate, actual) VALUES ( ?, ?, ?, ?, ?, ?, ?)"

    return sql;
}

// LOL update is tough, actually
exports.UpdateTask = function(task) {
    if(!task.idtasks) {
        // no ID, no Update
        return "";
    }
    var fields = 0;
    var sql = "UPDATE `tasks` SET ";
    if(task.text) {
        sql += "text = ?";
    }
}

exports.CompleteTask = function(task) {
    if(!task.idtasks) {
        // no ID no completion
        return "";
    }
    var sql = "UPDATE `tasks` SET completed = ? WHERE `task`.`idtasks` = ?";
    return sql;
}

exports.DeleteTask = function(task) {
    if(!task.idtasks) {
        // no ID no deletion
    }
    var sql = "DELETE FROM `tasks` WHERE idtasks = ?";
    return sql;
}

exports.FindTask = function(task) {
    if(!task.idtasks) {
        // no ID, not sure how to find it
        // TODO: Search function
    }
}

exports.FindTasks = function(task) {
    var sql = "SELECT FROM `tasks`"
    return sql;
}<|endoftext|>