<commit_before>"use strict";

var dummy
  , hotplate = require('hotplate')

  , declare = require('simpledeclare')

  , async = require( 'async' )
  , SimpleDbLayer = require( 'simpledblayer' )
  , SimpleSchema = require( 'simpleschema' )
  , JsonRestStores = require( 'jsonreststores' )
  , hotCoreJsonRestStores = require( 'hotCoreJsonRestStores' )

  , hotCoreStoreConfig = require( 'hotCoreStoreConfig' )
  , hotCoreStoreRegistry = require( 'hotCoreStoreRegistry' )
  , debug = require('debug')('hotplate:hotCoreComet')
;

var TABLIFESPAN = 18000;
var CLEANUPINTERVAL = 30000;

hotplate.hotEvents.onCollect( 'stores', 'hotCoreComet', hotplate.cachable( function( done ){

  var stores = {};

  // This module only uses JsonRestStores as a way to access the DB and expose methods,
  // it doesn't mixin with hotJsonRestStores (which would do Comet event emission etc.)

   hotCoreJsonRestStores.get( function( err, s ){
    if( err ) return done( err );

    var BasicDbStore = s.BasicDbStore;
    var BasicSchema = s.BasicSchema;

    // ***********************************
    // *** OPEN TABS   *******************
    // ***********************************

    var Tabs = declare( BasicDbStore, {

      schema: new BasicSchema({
        userId:        { type: 'id', required: true, searchable: true },
        lastSync:      { type: 'date', searchable: true, default: function(){ return new Date() } },
      }),

      handlePost: true,
      handleDelete: true,

      storeName:  'tabs',

      publicURL: '/tabs/:id',
      hotExpose: true,
      type: 'uncached',

      prepareBody: function( request, method, body, cb ){

        // Make a (shallow) copy
        var body = this._co( body );

        if( method == 'post' && request.remote ){
          if( request.session.userId ){
            body.userId = request.session.userId;
          }
        }
        cb( null, body );
      },

      // Delete ALL entries in TabMessages when a tab gets deleted
      afterEverything: function( request, method, cb ){

        // Only deal with delete
        if( method !== 'delete' ) return cb( null );

        // Delete the tab messages
        stores.tabMessages.dbLayer.delete( { type: 'eq', args: [ 'tabId', request.body.tabId ] }, cb );
      },

    });
    stores.tabs = new Tabs();

    // Internal store, only used via API
    var TabMessages = declare( BasicDbStore, {

      schema: new BasicSchema({
        fromUserId:    { type: 'id', searchable: true, required: false },
        message:       { type: 'serialize', required: true },
        added:         { type: 'date', searchable: true, default: function() { return new Date() } },
      }),

      storeName:  'tabMessages',
      paramIds: [ 'tabId', 'id' ],
      type: 'uncached',

    });
    stores.tabMessages = new TabMessages();

    // NON-DB STORE, sends an object out with all of the messages
    // It will return an array of unserialised messages in TabMessages for
    // a specific tabId, and then it will DELETE those messages
    var TabMessageDispatcher = declare( JsonRestStores, JsonRestStores.HTTPMixin, {

      schema: new SimpleSchema({
        tabId:         { type: 'blob' }, // This is 'blob' as SimpleSchema has numbers for IDs by default
        messages:      { type: 'none' },
      }),
      chainErrors: 'all',

      storeName:  'tabMessageDispatcher',

      publicURL: '/tabs/dispatcher/:tabId',
      hotExpose: true,
      type: 'uncached',

      handleGet: true,
      checkPermissions: function( request, method, done ){

        // Check that the remote user requesting the tab is indeed the tab's owner
        var self = this;

        // Only check for 'get'
        if( method !== 'get' ) return done( null, true );

        // User needs to be logged in
        if( ! request.session.userId ) return done( null, false );

        // TODO: Optimise a little here. Since checkPermissions is called before driverAllDbFetch,
        // try and cache this result
        stores.tabs.dbLayer.select( { conditions: {
          type: 'and',
          args: [
            {
              type: 'gte',
              args: [ 'lastSync', new Date() - TABLIFESPAN ]
            },
            {
              type: 'eq',
              args: [ 'id', request.params.tabId ]
            }
          ] } }, function( err, tabs ){
          if( err ) return done( err );

          // This may seem strange, but always pass authentication if the tab is not
          // there, as the store will need to accept the get and return the "storeReset"
          // message after returning the new tab
          if( tabs.length == 0 ){
             done( null, true );
          } else {
            done( null, tabs[0].userId.toString() === request.session.userId.toString() );
          }

        });

      },

      implementFetchOne: function( request, done ){

        var messages = [];
        var self = this;

        // If it's not a remote call, always return empty result
        if( ! request.remote ) return done( null, { messages: [] } );

        var headersWorkspaceId = request._req.headers[ 'x-hotplate-workspaceid' ];

        // User is not logged in -- goodbye
        if( ! request.session.loggedIn || ! request.session.userId ){
          debug("A non-logged in user tried to fetch tabId %s for workspaceId %s, denying...", request.params.tabId, headersWorkspaceId );
          return done( new self.UnauthorizedError() );
        }

        //debug("Looking for tab %s owned by user %s, x-workspaceId is %s, expiration is %s, now is %s", request.params.tabId, request.session.userId, headersWorkspaceId, new Date( new Date() - TABLIFESPAN ), new Date() );

        //stores.tabs.apiGetQuery( { conditions: { id: request.params.tabId, userId: request.session.userId, fromLastSync: new Date() - TABLIFESPAN } }, function( err, tab ){
        stores.tabs.dbLayer.select( { conditions: {
          type: 'and',
          args: [
            {
              type: 'gte',
              args: [ 'lastSync', new Date() - TABLIFESPAN ]
            },

            {
              type: 'eq',
              args: [ 'id', request.params.tabId ]
            },

            {
              type: 'eq',
              args: [ 'userId', request.session.userId ]
            },

          ] } }, function( err, tabs ){

          if( err ) return  done( err );

          if( tabs.length == 0 ){

            debug("Fetching messages for tab, but tab not found:");
            debug( tabs );

            debug("Tab was NOT present. Trying to understand if I should create one");

            // At this point, the tab wasn't found. If workspaceId was passed via headers,
            // the person will be returned the configuration for that workspace. We need to check
            // that the user actually has access to that workspaceId.

            hotCoreStoreRegistry.getAllStores( function( err, storesData ){
              if( err ) return done( err );

              debug("Checking that user has access to the workspaceId she is trying to register for");
              storesData.usersWorkspaces.dbLayer.selectByHash( { conditions: { userId: request.session.userId, workspaceId: headersWorkspaceId } }, { DEBUGME: true }, function( err, uwDocs){
                if( err ) return done( err );

                if( uwDocs.length == 0 ){
                  debug("No access -- user needs to (re?)login!");
                  return done( new self.UnauthorizedError() );
                }

                debug("OK, access is cleared, creating the tab for the user...");
                debug( request.session.userId );
                stores.tabs.dbLayer.insert( { userId: request.session.userId }, function( err, tab ){
                  if( err ) return done( err );

                  debug("...and ALSO returning the workspace configuration for that userId");
                  hotCoreStoreConfig.getConfigRecords( headersWorkspaceId, request.session.userId, function( err, storeRecords ){
                    if( err ) return done( err );

                    done( null, { messages: [ { fromUserId: request.session.userId, message: { type: 'resetStores', tabId: tab.id, storeRecords: storeRecords } } ] } );
                  });
                });
              });
            });

          } else {

            // Write the new access time onto the tab's record;
            var tab = tabs[ 0 ];
            tab.lastSync = new Date();

            stores.tabs.dbLayer.updateById( tab.id, { lastSync: tab.lastSync }, function( err, tab ){
              if( err ) return done( err );

              // Return all messages for that tab, REMOVING after fetching
              var cursor = stores.tabMessages.dbLayer.select( { sort: { added: 1 }, conditions: {
                type: 'and',
                args: [
                  {
                    type: 'gte',
                    args: [ 'added', new Date() - TABLIFESPAN ]
                  },
                  {
                    type: 'eq',
                    args: [ 'tabId', request.params.tabId ]
                  },
                ]
              } }, { delete: true, useCursor: true }, function( err, tabMessagesCursor ){
                if( err ) return done( err );

                tabMessagesCursor.each(

                  function( tabMessage, cb ){
                    delete tabMessage.messageId;
                    delete tabMessage.tabId;
                    messages.push( tabMessage);
                    cb( null );
                  },

                  function( err ){
                    if( err ) return done( err );

                    done( null, { messages: messages } );
                  }

                );

              });
            });

           }

        });

      },

    });
    stores.tabMessageDispatcher = new TabMessageDispatcher();

    done( null, stores );
  });

}))

hotplate.hotEvents.onCollect( 'run', hotplate.cachable( function( done ){

  hotCoreStoreRegistry.getAllStores( function( err, storesData ){
    if( err ){
      done( err );
    } else {
      // Clean up Tabs collection every 30 seconds, so that
      // dead tabs are gotten rid of
      var cleaningUp = false;

      setInterval( function(){
        if( cleaningUp ) return;
        cleaningUp = true;

        debug("Cleaning up unused tabs, where date is less than: %s", new Date( new Date() - TABLIFESPAN ) );

        //debug( "Cleaning up expired tabs and tab messages..." );

        storesData.tabs.dbLayer.delete( { type: 'lte', args: [ 'lastSync', new Date( new Date() - TABLIFESPAN ) ] }, { multi: true }, function( err, howMany ){
          debug( 'Tabs: error and howMany: ', err, howMany );
          //TODO: Log error

          storesData.tabMessages.dbLayer.delete( { type: 'lte', args: [ 'added', new Date( new Date() - TABLIFESPAN ) ] }, { multi: true }, function( err, howMany ){
             debug( 'TabMessages: error and howMany: ', err, howMany );
            // TODO: Log error
            cleaningUp = false;
          });
        });

      }, CLEANUPINTERVAL );

    }
  });

  done( null );
}));

hotplate.hotEvents.onCollect('cometBroadcast', 'hotCoreComet', function( userId, tabId, makeTabIdHash, message, done ){

  var self = this;

  debug("Asked to broadcast!");

  hotCoreStoreRegistry.getAllStores( function( err, storesData ){
    if( err ){
      done( err );
    } else {

      // makeTabId function wasn't passed: just use the stock "send it to all tabs" function
      if( ! makeTabIdHash ){
        debug("Using allTabIdHash!");
        makeTabIdHash = allTabIdHash;
      } else {
        debug("Using PASSED makeTabIdHash!");
      }

      //console.log("Passing parameters:", userId, tabId, message );
      // Get the list of tab ids
      makeTabIdHash( userId, tabId, message, function( err, tabIdHash ){
        if( err ) return done( err );

        debug("Result:", err, tabIdHash );
        async.each(

          Object.keys( tabIdHash ),

          function( tabId, cb){

            // Make up the object. fromUserId is not required, so it will only be added if needed
            var obj = {
              tabId: tabId,
              message: message
            };
            if( userId ) obj.fromUserId = userId;

            // OK, post it to the messages
            storesData.tabMessages.dbLayer.insert( obj, cb );
          },

          function( err ){
            if( err ) return done( err );

            done( null );
          }
        );

      })
    }

  });

});

function allTabIdHash( userId, tabId, message, done ){

  debug("In allTabIdHash!" );

  hotCoreStoreRegistry.getAllStores( function( err, storesData ){
    if( err ){
      done( err );
    } else {

      var tabIdHash = {};

      // Just return all active tabs

      //storesData.tabs.apiGetQuery( { conditions: { fromLastSync: new Date() - TABLIFESPAN  }  }, function( err, docs ){

      stores.tabs.dbLayer.select( { conditions: {
        type: 'and',
        args: [
          {
            type: 'gte',
            args: [ 'lastSync', new Date() - TABLIFESPAN ]
          },
        ] } }, function( err, tabs ){

        if( err ) return done( err );

        tabs.forEach( function( i ){
          if( i.id != tabId ) tabIdHash[ i.id ] = true;
        });

        done( null, tabIdHash );
      });
    }
  });

}

<commit_msg>Fixed indentation<commit_after>"use strict";

var dummy
  , hotplate = require('hotplate')

  , declare = require('simpledeclare')

  , async = require( 'async' )
  , SimpleDbLayer = require( 'simpledblayer' )
  , SimpleSchema = require( 'simpleschema' )
  , JsonRestStores = require( 'jsonreststores' )
  , hotCoreJsonRestStores = require( 'hotCoreJsonRestStores' )

  , hotCoreStoreConfig = require( 'hotCoreStoreConfig' )
  , hotCoreStoreRegistry = require( 'hotCoreStoreRegistry' )
  , debug = require('debug')('hotplate:hotCoreComet')
;

var TABLIFESPAN = 18000;
var CLEANUPINTERVAL = 30000;

hotplate.hotEvents.onCollect( 'stores', 'hotCoreComet', hotplate.cachable( function( done ){

  var stores = {};

   // This module only uses JsonRestStores as a way to access the DB and expose methods,
   // it doesn't mixin with hotJsonRestStores (which would do Comet event emission etc.)

   hotCoreJsonRestStores.get( function( err, s ){
    if( err ) return done( err );

    var BasicDbStore = s.BasicDbStore;
    var BasicSchema = s.BasicSchema;

    // ***********************************
    // *** OPEN TABS   *******************
    // ***********************************

    var Tabs = declare( BasicDbStore, {

      schema: new BasicSchema({
        userId:        { type: 'id', required: true, searchable: true },
        lastSync:      { type: 'date', searchable: true, default: function(){ return new Date() } },
      }),

      handlePost: true,
      handleDelete: true,

      storeName:  'tabs',

      publicURL: '/tabs/:id',
      hotExpose: true,
      type: 'uncached',

      prepareBody: function( request, method, body, cb ){

        // Make a (shallow) copy
        var body = this._co( body );

        if( method == 'post' && request.remote ){
          if( request.session.userId ){
            body.userId = request.session.userId;
          }
        }
        cb( null, body );
      },

      // Delete ALL entries in TabMessages when a tab gets deleted
      afterEverything: function( request, method, cb ){

        // Only deal with delete
        if( method !== 'delete' ) return cb( null );

        // Delete the tab messages
        stores.tabMessages.dbLayer.delete( { type: 'eq', args: [ 'tabId', request.body.tabId ] }, cb );
      },

    });
    stores.tabs = new Tabs();

    // Internal store, only used via API
    var TabMessages = declare( BasicDbStore, {

      schema: new BasicSchema({
        fromUserId:    { type: 'id', searchable: true, required: false },
        message:       { type: 'serialize', required: true },
        added:         { type: 'date', searchable: true, default: function() { return new Date() } },
      }),

      storeName:  'tabMessages',
      paramIds: [ 'tabId', 'id' ],
      type: 'uncached',

    });
    stores.tabMessages = new TabMessages();

    // NON-DB STORE, sends an object out with all of the messages
    // It will return an array of unserialised messages in TabMessages for
    // a specific tabId, and then it will DELETE those messages
    var TabMessageDispatcher = declare( JsonRestStores, JsonRestStores.HTTPMixin, {

      schema: new SimpleSchema({
        tabId:         { type: 'blob' }, // This is 'blob' as SimpleSchema has numbers for IDs by default
        messages:      { type: 'none' },
      }),
      chainErrors: 'all',

      storeName:  'tabMessageDispatcher',

      publicURL: '/tabs/dispatcher/:tabId',
      hotExpose: true,
      type: 'uncached',

      handleGet: true,
      checkPermissions: function( request, method, done ){

        // Check that the remote user requesting the tab is indeed the tab's owner
        var self = this;

        // Only check for 'get'
        if( method !== 'get' ) return done( null, true );

        // User needs to be logged in
        if( ! request.session.userId ) return done( null, false );

        // TODO: Optimise a little here. Since checkPermissions is called before driverAllDbFetch,
        // try and cache this result
        stores.tabs.dbLayer.select( { conditions: {
          type: 'and',
          args: [
            {
              type: 'gte',
              args: [ 'lastSync', new Date() - TABLIFESPAN ]
            },
            {
              type: 'eq',
              args: [ 'id', request.params.tabId ]
            }
          ] } }, function( err, tabs ){
          if( err ) return done( err );

          // This may seem strange, but always pass authentication if the tab is not
          // there, as the store will need to accept the get and return the "storeReset"
          // message after returning the new tab
          if( tabs.length == 0 ){
             done( null, true );
          } else {
            done( null, tabs[0].userId.toString() === request.session.userId.toString() );
          }

        });

      },

      implementFetchOne: function( request, done ){

        var messages = [];
        var self = this;

        // If it's not a remote call, always return empty result
        if( ! request.remote ) return done( null, { messages: [] } );

        var headersWorkspaceId = request._req.headers[ 'x-hotplate-workspaceid' ];

        // User is not logged in -- goodbye
        if( ! request.session.loggedIn || ! request.session.userId ){
          debug("A non-logged in user tried to fetch tabId %s for workspaceId %s, denying...", request.params.tabId, headersWorkspaceId );
          return done( new self.UnauthorizedError() );
        }

        //debug("Looking for tab %s owned by user %s, x-workspaceId is %s, expiration is %s, now is %s", request.params.tabId, request.session.userId, headersWorkspaceId, new Date( new Date() - TABLIFESPAN ), new Date() );

        //stores.tabs.apiGetQuery( { conditions: { id: request.params.tabId, userId: request.session.userId, fromLastSync: new Date() - TABLIFESPAN } }, function( err, tab ){
        stores.tabs.dbLayer.select( { conditions: {
          type: 'and',
          args: [
            {
              type: 'gte',
              args: [ 'lastSync', new Date() - TABLIFESPAN ]
            },

            {
              type: 'eq',
              args: [ 'id', request.params.tabId ]
            },

            {
              type: 'eq',
              args: [ 'userId', request.session.userId ]
            },

          ] } }, function( err, tabs ){

          if( err ) return  done( err );

          if( tabs.length == 0 ){

            debug("Fetching messages for tab, but tab not found:");
            debug( tabs );

            debug("Tab was NOT present. Trying to understand if I should create one");

            // At this point, the tab wasn't found. If workspaceId was passed via headers,
            // the person will be returned the configuration for that workspace. We need to check
            // that the user actually has access to that workspaceId.

            hotCoreStoreRegistry.getAllStores( function( err, storesData ){
              if( err ) return done( err );

              debug("Checking that user has access to the workspaceId she is trying to register for");
              storesData.usersWorkspaces.dbLayer.selectByHash( { conditions: { userId: request.session.userId, workspaceId: headersWorkspaceId } }, { DEBUGME: true }, function( err, uwDocs){
                if( err ) return done( err );

                if( uwDocs.length == 0 ){
                  debug("No access -- user needs to (re?)login!");
                  return done( new self.UnauthorizedError() );
                }

                debug("OK, access is cleared, creating the tab for the user...");
                debug( request.session.userId );
                stores.tabs.dbLayer.insert( { userId: request.session.userId }, function( err, tab ){
                  if( err ) return done( err );

                  debug("...and ALSO returning the workspace configuration for that userId");
                  hotCoreStoreConfig.getConfigRecords( headersWorkspaceId, request.session.userId, function( err, storeRecords ){
                    if( err ) return done( err );

                    done( null, { messages: [ { fromUserId: request.session.userId, message: { type: 'resetStores', tabId: tab.id, storeRecords: storeRecords } } ] } );
                  });
                });
              });
            });

          } else {

            // Write the new access time onto the tab's record;
            var tab = tabs[ 0 ];
            tab.lastSync = new Date();

            stores.tabs.dbLayer.updateById( tab.id, { lastSync: tab.lastSync }, function( err, tab ){
              if( err ) return done( err );

              // Return all messages for that tab, REMOVING after fetching
              var cursor = stores.tabMessages.dbLayer.select( { sort: { added: 1 }, conditions: {
                type: 'and',
                args: [
                  {
                    type: 'gte',
                    args: [ 'added', new Date() - TABLIFESPAN ]
                  },
                  {
                    type: 'eq',
                    args: [ 'tabId', request.params.tabId ]
                  },
                ]
              } }, { delete: true, useCursor: true }, function( err, tabMessagesCursor ){
                if( err ) return done( err );

                tabMessagesCursor.each(

                  function( tabMessage, cb ){
                    delete tabMessage.messageId;
                    delete tabMessage.tabId;
                    messages.push( tabMessage);
                    cb( null );
                  },

                  function( err ){
                    if( err ) return done( err );

                    done( null, { messages: messages } );
                  }

                );

              });
            });

           }

        });

      },

    });
    stores.tabMessageDispatcher = new TabMessageDispatcher();

    done( null, stores );
  });

}))

hotplate.hotEvents.onCollect( 'run', hotplate.cachable( function( done ){

  hotCoreStoreRegistry.getAllStores( function( err, storesData ){
    if( err ){
      done( err );
    } else {
      // Clean up Tabs collection every 30 seconds, so that
      // dead tabs are gotten rid of
      var cleaningUp = false;

      setInterval( function(){
        if( cleaningUp ) return;
        cleaningUp = true;

        debug("Cleaning up unused tabs, where date is less than: %s", new Date( new Date() - TABLIFESPAN ) );

        //debug( "Cleaning up expired tabs and tab messages..." );

        storesData.tabs.dbLayer.delete( { type: 'lte', args: [ 'lastSync', new Date( new Date() - TABLIFESPAN ) ] }, { multi: true }, function( err, howMany ){
          debug( 'Tabs: error and howMany: ', err, howMany );
          //TODO: Log error

          storesData.tabMessages.dbLayer.delete( { type: 'lte', args: [ 'added', new Date( new Date() - TABLIFESPAN ) ] }, { multi: true }, function( err, howMany ){
             debug( 'TabMessages: error and howMany: ', err, howMany );
            // TODO: Log error
            cleaningUp = false;
          });
        });

      }, CLEANUPINTERVAL );

    }
  });

  done( null );
}));

hotplate.hotEvents.onCollect('cometBroadcast', 'hotCoreComet', function( userId, tabId, makeTabIdHash, message, done ){

  var self = this;

  debug("Asked to broadcast!");

  hotCoreStoreRegistry.getAllStores( function( err, storesData ){
    if( err ) return done( err );

    // makeTabId function wasn't passed: just use the stock "send it to all tabs" function
    if( ! makeTabIdHash ){
      debug("Using allTabIdHash!");
      makeTabIdHash = allTabIdHash;
    } else {
      debug("Using PASSED makeTabIdHash!");
    }

    // Get the list of tab ids
    makeTabIdHash( userId, tabId, message, function( err, tabIdHash ){
      if( err ) return done( err );

      async.eachSeries(

        Object.keys( tabIdHash ),

        function( tabId, cb){

          // Make up the object. fromUserId is not required, so it will only be added if needed
          var obj = {
            tabId: tabId,
            message: message
          };
          if( userId ) obj.fromUserId = userId;

          // OK, post it to the messages
          storesData.tabMessages.dbLayer.insert( obj, cb );
        },

        function( err ){
          if( err ) return done( err );

          done( null );
        }
      );

    })

  });

});

function allTabIdHash( userId, tabId, message, done ){

  debug("In allTabIdHash!" );

  hotCoreStoreRegistry.getAllStores( function( err, storesData ){
    if( err ){
      done( err );
    } else {

      var tabIdHash = {};

      // Just return all active tabs

      //storesData.tabs.apiGetQuery( { conditions: { fromLastSync: new Date() - TABLIFESPAN  }  }, function( err, docs ){

      stores.tabs.dbLayer.select( { conditions: {
        type: 'and',
        args: [
          {
            type: 'gte',
            args: [ 'lastSync', new Date() - TABLIFESPAN ]
          },
        ] } }, function( err, tabs ){

        if( err ) return done( err );

        tabs.forEach( function( i ){
          if( i.id != tabId ) tabIdHash[ i.id ] = true;
        });

        done( null, tabIdHash );
      });
    }
  });

}

<|endoftext|>


<commit_before><commit_msg>Another attempt...<commit_after><|endoftext|>


<commit_before>import '/resources/polymer/@polymer/polymer/polymer-legacy.js';

import '/resources/polymer/@polymer/iron-flex-layout/iron-flex-layout.js';
import '/resources/polymer/@polymer/iron-flex-layout/iron-flex-layout-classes.js';

import "/resources/polymer/@polymer/paper-styles/shadow.js";

import '/resources/element_loader/tg-element-loader.js';
import { TgReflector } from '/app/tg-reflector.js';

import { Polymer } from '/resources/polymer/@polymer/polymer/lib/legacy/polymer-fn.js';
import { html } from '/resources/polymer/@polymer/polymer/lib/utils/html-tag.js';
import { IronResizableBehavior } from '/resources/polymer/@polymer/iron-resizable-behavior/iron-resizable-behavior.js';

const template = html`

    <template is="dom-if" if="[[menuItem.view]]" restamp>
        <template is="dom-if" if="[[!_isCentre(menuItem)]]" on-dom-change="_viewWasDefined" restamp>

                    <tg-element-loader id="elementToLoad" class="layout vertical" style="min-height:0;" auto="[[autoLoad]]" import="[[menuItem.view.htmlImport]]" element-name="[[menuItem.view.elementName]]" attrs="[[menuItem.view.attrs]]" on-after-load="_afterLoadListener"></tg-element-loader>

        </template>
        <template is="dom-if" if="[[_isCentre(menuItem)]]" on-dom-change="_viewWasDefined" restamp>
            <tg-element-loader id="elementToLoad" auto="[[autoLoad]]" import="[[menuItem.view.htmlImport]]" element-name="[[menuItem.view.elementName]]" attrs="[[menuItem.view.attrs]]" on-after-load="_afterLoadListener"></tg-element-loader>
        </template>
    </template>
    <template is="dom-if" if="[[!menuItem.view]]" on-dom-change="_viewWasDefined" restamp>

            Please specify view for [[menuItem.key]].

    </template>`;

function isPropertyDefined (obj, property) {
    var dotIndex = property.indexOf(".");
    var firstProp;
    if (!obj) {
        return false;
    } else if (dotIndex < 0) {
        return !!obj[property]
    } else {
        firstProp = property.substr(0, dotIndex);
        return isPropertyDefined(obj[firstProp], property.slice(dotIndex + 1))
    }
};

Polymer({

    _template: template,

    is: "tg-menu-item-view",

    properties: {
        menuItem: Object,
        autoLoad: {
            type: Boolean,
            value: false,
            reflectToAttribute: true
        },
        moduleId: String,
        submoduleId: String,
        prefDim: {
            type: Object,
            observer: "_prefDimChanged"
        }
    },

    behaviors: [IronResizableBehavior],

    hostAttributes: {
        "tabindex": "0"
    },

    created: function () {
        this._reflector = new TgReflector();
    },

    // ready: function () {
    //     This setting could be helpful in case where master / centre views will need to be supported.
    //     this._afterInitLoading = true;
    // },

    attached: function () {
        var self = this;
        self.async(function () {
            if (isPropertyDefined(self, "menuItem.view.attrs")) {
                self.menuItem.view.attrs.uuid = self.moduleId + (self.submoduleId ? '/' + self.submoduleId : '');
                self.menuItem.view.attrs.moduleId = self.moduleId;
            }
        }, 1);
    },

    focusLoadedView: function () {
        var elementToLoad = this.shadowRoot.querySelector("#elementToLoad");
        if (elementToLoad && elementToLoad.wasLoaded && typeof elementToLoad.loadedElement.focusView === 'function') {
            elementToLoad.loadedElement.focusView();
        }
    },

    /**
     * Indicates whether this tg-menu-item-view contains centre inside.
     */
    _isCentre: function (menuItem) {
        return menuItem.view.viewType === 'centre';
    },

    /**
     * Retrieves loaded centre criteria if centre element has been loaded previously (except situation where exactly same config without link parameters is tried to be loaded).
     * Provides parameters for tg-element-loader if centre element has not been loaded previously.
     */
    _retrieveCentreWithParams: function (queryPart, unknownSubpath) {
        const elementLoader = this.shadowRoot.querySelector("#elementToLoad")
        const centre = elementLoader.loadedElement;
        if (centre) {
            const loadCentreFreezed = centre.$ && centre.$.selection_criteria && centre.$.selection_criteria.loadCentreFreezed;
            if (!loadCentreFreezed && (queryPart // perform reloading in case where new ?crit=val params appear ...
             || unknownSubpath !== centre.configUuid // ... or new configUuid is different from already loaded ...
             || !centre._criteriaLoaded // ... or where criteria was not loaded (e.g. errors during previous loading attempts)
            )) {
                const initiateCentreLoading = () => this.async(() => { // load new centre config only after current loading completed; this can be achieved by putting next loading in the end of queue; this is useful for link-configurations
                    centre._selectedView = 0;
                    centre.queryPart = queryPart;
                    centre.configUuid = unknownSubpath;
                    this._loadCentre.bind(this)(centre, false);
                });
                if (centre._criteriaLoaded && centre.configUuid === '') { // for fully loaded default configuration we intercept loading with special invisible CentreConfigLoadAction
                    if (!centre.loadActionFromUri) { // create it if not yet created
                        const originalLoadAction = centre.$.dom.$.selectionView.$.loadAction; // copy from standard CentreConfigLoadAction
                        centre.loadActionFromUri = document.createElement('tg-ui-action');
                        centre.loadActionFromUri.componentUri = originalLoadAction.componentUri;
                        centre.loadActionFromUri.elementName = originalLoadAction.elementName;
                        centre.loadActionFromUri.showDialog = originalLoadAction.showDialog;
                        centre.loadActionFromUri.createContextHolder = originalLoadAction.createContextHolder;
                        centre.loadActionFromUri.attrs = originalLoadAction.attrs;
                        centre.loadActionFromUri.requireSelectionCriteria = originalLoadAction.requireSelectionCriteria;
                        centre.loadActionFromUri.requireSelectedEntities = originalLoadAction.requireSelectedEntities;
                        centre.loadActionFromUri.requireMasterEntity = originalLoadAction.requireMasterEntity;
                        centre.loadActionFromUri.chosenProperty = 'load-from-uri'; // this will be used to distinguish from standard action
                        centre.loadActionFromUri._masterReferenceForTestingChanged = function (master) {
                            master.addEventListener('continuaton-completed-without-success', event => { // listen for CANCEL AcknowledgeWarnings action completion
                                history.back(); // move back from already recorded history transition (made explicitly by user by pasting URI into address bar)
                            });
                        };
                        centre.appendChild(centre.loadActionFromUri); // must be attached to DOM tree to trigger observers and other stuff
                    }
                    centre.loadActionFromUri.postActionSuccess = initiateCentreLoading; // completely override postActionSuccess to provide different behavior
                    centre.loadActionFromUri._run();
                } else {
                    initiateCentreLoading();
                }
            }
        } else {
            if (elementLoader.attrs) {
                elementLoader.attrs.configUuid = unknownSubpath;
            }
        }
    },

    /**
     * Loads criteria entity for the centre and initiates running for autoRun / link centres.
     */
    _loadCentre: function (centre, isFirstTime) {
        const self = this;
        const oldPostRetrieved = centre.postRetrieved;
        centre.postRetrieved = function (entity, bindingEntity, customObject) {
            if (oldPostRetrieved) {
                oldPostRetrieved(entity, bindingEntity, customObject);
            }
            centre._setQueryParams();
            if (centre.autoRun || centre.queryPart) {
                if (centre._selectedView === 0) {
                    centre.async(() => {
                        centre._selectedView = centre.preferredView;
                    }, 100);
                }
                centre.run(!centre.queryPart); // identify autoRunning situation only in case where centre has autoRun as true but does not represent 'link' centre (has no URI criteria values)
                delete centre.queryPart;
            }
            if (isFirstTime) {
                self.fire("menu-item-view-loaded", self.menuItem);
            }
            centre.postRetrieved = oldPostRetrieved;
        };
        return centre.retrieve().catch(error => {});
    },

    /**
     * Listener for tg-element-loader after successful loading of menu-item view.
     */
    _afterLoadListener: function (e, detail, view) {
        const self = this;
        if (e.target === this.shadowRoot.querySelector("#elementToLoad")) {
            if (this._isCentre(this.menuItem)) {
                self._loadCentre.bind(this)(detail, true);
            } else if (this.menuItem.view.viewType === 'master') {
                detail.postRetrieved = function (entity, bindingEntity, customObject) {
                    self.fire("menu-item-view-loaded", self.menuItem);
                };
                detail.postValidated = function (validatedEntity, bindingEntity, customObject) {};
                detail.postSaved = function (potentiallySavedOrNewEntity, newBindingEntity) {};
                detail.retrieve().then(function(ironRequest) {
                    if (detail.saveOnActivation === true) {
                        return detail.save(); // saving promise
                    }
                    return Promise.resolve(ironRequest); // retrieval promise; resolves immediately
                }).catch(error => {});
            } else {
                self.fire("menu-item-view-loaded", self.menuItem);
            }
        }
    },

    /**
     * The callback that is invoked after template was defined and it's dom was changed.
     */
    _viewWasDefined: function (e, detail) {
        var elementToLoad;
        if (this._afterInitLoading) {
            elementToLoad = this.shadowRoot.querySelector("#elementToLoad");
            if (elementToLoad) {
                if (!elementToLoad.wasLoaded) {
                    elementToLoad.load();
                }
                delete this._afterInitLoading;
            }
        }
    },

    _setQueryIfPossible: function (queryPart) {
        if (this.menuItem.view.viewType === "centre" && queryPart) {
            this.menuItem.view.attrs.queryPart = queryPart;
        }
    },

    _prefDimChanged: function (prefDim, oldPrefDim) {
        if (prefDim && !this._isCentre(this.menuItem)) {
            const width = (typeof prefDim.width === 'function' ? prefDim.width() : prefDim.width) + prefDim.widthUnit;
            const height = (typeof prefDim.height === 'function' ? prefDim.height() : prefDim.height) + prefDim.heightUnit;
            const viewContainer = this.shadowRoot.querySelector("#customViewContainer");
            viewContainer.style.width = width;
            viewContainer.style.height = height;
        }
    },

    load: function (queryPart) {
        const elementToLoad = this.shadowRoot.querySelector("#elementToLoad");
        if (elementToLoad && !elementToLoad.wasLoaded) {
            this._setQueryIfPossible(queryPart);
            elementToLoad.load();
        }
    },

    wasLoaded: function () {
        const elementToLoad = this.shadowRoot.querySelector("#elementToLoad");
        if (elementToLoad) {
            return elementToLoad.wasLoaded;
        }
        return true;
    },

    canLeave: function () {
        const elementToLoad = this.shadowRoot.querySelector("#elementToLoad");
        if (elementToLoad && elementToLoad.wasLoaded && typeof elementToLoad.loadedElement.canLeave === 'function') {
            return elementToLoad.loadedElement.canLeave();
        }
        return undefined;
    }
});<commit_msg>#1806 Previously queryPart was a simple state that was used in centre and quickly removed by Javascript operator 'delete'. However, it was converted to Polymer property that is now bound downwardly to selection criteria and used by corresponding AJAX retriever. 'delete' operator does not work for such Polymer property. This commit fixes actual deletion of queryPart from centre.<commit_after>import '/resources/polymer/@polymer/polymer/polymer-legacy.js';

import '/resources/polymer/@polymer/iron-flex-layout/iron-flex-layout.js';
import '/resources/polymer/@polymer/iron-flex-layout/iron-flex-layout-classes.js';

import "/resources/polymer/@polymer/paper-styles/shadow.js";

import '/resources/element_loader/tg-element-loader.js';
import { TgReflector } from '/app/tg-reflector.js';

import { Polymer } from '/resources/polymer/@polymer/polymer/lib/legacy/polymer-fn.js';
import { html } from '/resources/polymer/@polymer/polymer/lib/utils/html-tag.js';
import { IronResizableBehavior } from '/resources/polymer/@polymer/iron-resizable-behavior/iron-resizable-behavior.js';

const template = html`

    <template is="dom-if" if="[[menuItem.view]]" restamp>
        <template is="dom-if" if="[[!_isCentre(menuItem)]]" on-dom-change="_viewWasDefined" restamp>

                    <tg-element-loader id="elementToLoad" class="layout vertical" style="min-height:0;" auto="[[autoLoad]]" import="[[menuItem.view.htmlImport]]" element-name="[[menuItem.view.elementName]]" attrs="[[menuItem.view.attrs]]" on-after-load="_afterLoadListener"></tg-element-loader>

        </template>
        <template is="dom-if" if="[[_isCentre(menuItem)]]" on-dom-change="_viewWasDefined" restamp>
            <tg-element-loader id="elementToLoad" auto="[[autoLoad]]" import="[[menuItem.view.htmlImport]]" element-name="[[menuItem.view.elementName]]" attrs="[[menuItem.view.attrs]]" on-after-load="_afterLoadListener"></tg-element-loader>
        </template>
    </template>
    <template is="dom-if" if="[[!menuItem.view]]" on-dom-change="_viewWasDefined" restamp>

            Please specify view for [[menuItem.key]].

    </template>`;

function isPropertyDefined (obj, property) {
    var dotIndex = property.indexOf(".");
    var firstProp;
    if (!obj) {
        return false;
    } else if (dotIndex < 0) {
        return !!obj[property]
    } else {
        firstProp = property.substr(0, dotIndex);
        return isPropertyDefined(obj[firstProp], property.slice(dotIndex + 1))
    }
};

Polymer({

    _template: template,

    is: "tg-menu-item-view",

    properties: {
        menuItem: Object,
        autoLoad: {
            type: Boolean,
            value: false,
            reflectToAttribute: true
        },
        moduleId: String,
        submoduleId: String,
        prefDim: {
            type: Object,
            observer: "_prefDimChanged"
        }
    },

    behaviors: [IronResizableBehavior],

    hostAttributes: {
        "tabindex": "0"
    },

    created: function () {
        this._reflector = new TgReflector();
    },

    // ready: function () {
    //     This setting could be helpful in case where master / centre views will need to be supported.
    //     this._afterInitLoading = true;
    // },

    attached: function () {
        var self = this;
        self.async(function () {
            if (isPropertyDefined(self, "menuItem.view.attrs")) {
                self.menuItem.view.attrs.uuid = self.moduleId + (self.submoduleId ? '/' + self.submoduleId : '');
                self.menuItem.view.attrs.moduleId = self.moduleId;
            }
        }, 1);
    },

    focusLoadedView: function () {
        var elementToLoad = this.shadowRoot.querySelector("#elementToLoad");
        if (elementToLoad && elementToLoad.wasLoaded && typeof elementToLoad.loadedElement.focusView === 'function') {
            elementToLoad.loadedElement.focusView();
        }
    },

    /**
     * Indicates whether this tg-menu-item-view contains centre inside.
     */
    _isCentre: function (menuItem) {
        return menuItem.view.viewType === 'centre';
    },

    /**
     * Retrieves loaded centre criteria if centre element has been loaded previously (except situation where exactly same config without link parameters is tried to be loaded).
     * Provides parameters for tg-element-loader if centre element has not been loaded previously.
     */
    _retrieveCentreWithParams: function (queryPart, unknownSubpath) {
        const elementLoader = this.shadowRoot.querySelector("#elementToLoad")
        const centre = elementLoader.loadedElement;
        if (centre) {
            const loadCentreFreezed = centre.$ && centre.$.selection_criteria && centre.$.selection_criteria.loadCentreFreezed;
            if (!loadCentreFreezed && (queryPart // perform reloading in case where new ?crit=val params appear ...
             || unknownSubpath !== centre.configUuid // ... or new configUuid is different from already loaded ...
             || !centre._criteriaLoaded // ... or where criteria was not loaded (e.g. errors during previous loading attempts)
            )) {
                const initiateCentreLoading = () => this.async(() => { // load new centre config only after current loading completed; this can be achieved by putting next loading in the end of queue; this is useful for link-configurations
                    centre._selectedView = 0;
                    centre.queryPart = queryPart;
                    centre.configUuid = unknownSubpath;
                    this._loadCentre.bind(this)(centre, false);
                });
                if (centre._criteriaLoaded && centre.configUuid === '') { // for fully loaded default configuration we intercept loading with special invisible CentreConfigLoadAction
                    if (!centre.loadActionFromUri) { // create it if not yet created
                        const originalLoadAction = centre.$.dom.$.selectionView.$.loadAction; // copy from standard CentreConfigLoadAction
                        centre.loadActionFromUri = document.createElement('tg-ui-action');
                        centre.loadActionFromUri.componentUri = originalLoadAction.componentUri;
                        centre.loadActionFromUri.elementName = originalLoadAction.elementName;
                        centre.loadActionFromUri.showDialog = originalLoadAction.showDialog;
                        centre.loadActionFromUri.createContextHolder = originalLoadAction.createContextHolder;
                        centre.loadActionFromUri.attrs = originalLoadAction.attrs;
                        centre.loadActionFromUri.requireSelectionCriteria = originalLoadAction.requireSelectionCriteria;
                        centre.loadActionFromUri.requireSelectedEntities = originalLoadAction.requireSelectedEntities;
                        centre.loadActionFromUri.requireMasterEntity = originalLoadAction.requireMasterEntity;
                        centre.loadActionFromUri.chosenProperty = 'load-from-uri'; // this will be used to distinguish from standard action
                        centre.loadActionFromUri._masterReferenceForTestingChanged = function (master) {
                            master.addEventListener('continuaton-completed-without-success', event => { // listen for CANCEL AcknowledgeWarnings action completion
                                history.back(); // move back from already recorded history transition (made explicitly by user by pasting URI into address bar)
                            });
                        };
                        centre.appendChild(centre.loadActionFromUri); // must be attached to DOM tree to trigger observers and other stuff
                    }
                    centre.loadActionFromUri.postActionSuccess = initiateCentreLoading; // completely override postActionSuccess to provide different behavior
                    centre.loadActionFromUri._run();
                } else {
                    initiateCentreLoading();
                }
            }
        } else {
            if (elementLoader.attrs) {
                elementLoader.attrs.configUuid = unknownSubpath;
            }
        }
    },

    /**
     * Loads criteria entity for the centre and initiates running for autoRun / link centres.
     */
    _loadCentre: function (centre, isFirstTime) {
        const self = this;
        const oldPostRetrieved = centre.postRetrieved;
        centre.postRetrieved = function (entity, bindingEntity, customObject) {
            if (oldPostRetrieved) {
                oldPostRetrieved(entity, bindingEntity, customObject);
            }
            centre._setQueryParams();
            if (centre.autoRun || centre.queryPart) {
                if (centre._selectedView === 0) {
                    centre.async(() => {
                        centre._selectedView = centre.preferredView;
                    }, 100);
                }
                centre.run(!centre.queryPart); // identify autoRunning situation only in case where centre has autoRun as true but does not represent 'link' centre (has no URI criteria values)
                centre.queryPart = null;
            }
            if (isFirstTime) {
                self.fire("menu-item-view-loaded", self.menuItem);
            }
            centre.postRetrieved = oldPostRetrieved;
        };
        return centre.retrieve().catch(error => {});
    },

    /**
     * Listener for tg-element-loader after successful loading of menu-item view.
     */
    _afterLoadListener: function (e, detail, view) {
        const self = this;
        if (e.target === this.shadowRoot.querySelector("#elementToLoad")) {
            if (this._isCentre(this.menuItem)) {
                self._loadCentre.bind(this)(detail, true);
            } else if (this.menuItem.view.viewType === 'master') {
                detail.postRetrieved = function (entity, bindingEntity, customObject) {
                    self.fire("menu-item-view-loaded", self.menuItem);
                };
                detail.postValidated = function (validatedEntity, bindingEntity, customObject) {};
                detail.postSaved = function (potentiallySavedOrNewEntity, newBindingEntity) {};
                detail.retrieve().then(function(ironRequest) {
                    if (detail.saveOnActivation === true) {
                        return detail.save(); // saving promise
                    }
                    return Promise.resolve(ironRequest); // retrieval promise; resolves immediately
                }).catch(error => {});
            } else {
                self.fire("menu-item-view-loaded", self.menuItem);
            }
        }
    },

    /**
     * The callback that is invoked after template was defined and it's dom was changed.
     */
    _viewWasDefined: function (e, detail) {
        var elementToLoad;
        if (this._afterInitLoading) {
            elementToLoad = this.shadowRoot.querySelector("#elementToLoad");
            if (elementToLoad) {
                if (!elementToLoad.wasLoaded) {
                    elementToLoad.load();
                }
                delete this._afterInitLoading;
            }
        }
    },

    _setQueryIfPossible: function (queryPart) {
        if (this.menuItem.view.viewType === "centre" && queryPart) {
            this.menuItem.view.attrs.queryPart = queryPart;
        }
    },

    _prefDimChanged: function (prefDim, oldPrefDim) {
        if (prefDim && !this._isCentre(this.menuItem)) {
            const width = (typeof prefDim.width === 'function' ? prefDim.width() : prefDim.width) + prefDim.widthUnit;
            const height = (typeof prefDim.height === 'function' ? prefDim.height() : prefDim.height) + prefDim.heightUnit;
            const viewContainer = this.shadowRoot.querySelector("#customViewContainer");
            viewContainer.style.width = width;
            viewContainer.style.height = height;
        }
    },

    load: function (queryPart) {
        const elementToLoad = this.shadowRoot.querySelector("#elementToLoad");
        if (elementToLoad && !elementToLoad.wasLoaded) {
            this._setQueryIfPossible(queryPart);
            elementToLoad.load();
        }
    },

    wasLoaded: function () {
        const elementToLoad = this.shadowRoot.querySelector("#elementToLoad");
        if (elementToLoad) {
            return elementToLoad.wasLoaded;
        }
        return true;
    },

    canLeave: function () {
        const elementToLoad = this.shadowRoot.querySelector("#elementToLoad");
        if (elementToLoad && elementToLoad.wasLoaded && typeof elementToLoad.loadedElement.canLeave === 'function') {
            return elementToLoad.loadedElement.canLeave();
        }
        return undefined;
    }
});<|endoftext|>


<commit_before>import * as types from '../constants/ActionTypes';

import { SOURCEMAP_SEPARATE, SOURCEMAP_OFF } from '../containers/OptionsContainer'

const initialState = {
  compact: true,
  selfDefending: false,
  disableConsoleOutput: false,

  debugProtection: false,
  debugProtectionInterval: false,

  stringArray: true,

  rotateStringArray: true,
  rotateStringArrayEnabled: true,

  stringArrayThreshold: 0.8,
  stringArrayThresholdEnabled: true,

  stringArrayEncoding: 'false',
  stringArrayEncodingEnabled: true,

  sourceMap: false,
  sourceMapMode: 'off',
  sourceMapBaseUrl: '',
  sourceMapFileName: '',
  sourceMapSeparate: false,

  domainLock: [],
  reservedNames: [],

  seed: 0,

  controlFlowFlatteningThreshold: 0.75,
  controlFlowFlattening: false,

  deadCodeInjectionThreshold: 0.4,
  deadCodeInjection: false,

  mangle: false,
}

export const options = (state = initialState, action) => {

  // this is necessary because new options need to be filled with their
  // default values and them merged with the state from the localStorage
  // which can be outdated when new options are added
  // from https://github.com/reactjs/redux/issues/433#issuecomment-129188687
  if (!state.hydrated) {
    state = { ...initialState, ...state, hydrated: true };
  }

  switch (action.type) {

    case types.TOGGLE_COMPACT_CODE: {
      const compact = !state.compact;
      return {
        ...state,
        compact,
        selfDefending: state.selfDefending && compact,
      }
    }

    case types.TOGGLE_SELF_DEFENDING: {
      const selfDefending = !state.selfDefending;
      return {
        ...state,
        selfDefending,
        compact: state.compact || selfDefending,
      }
    }

    case types.TOGGLE_DISABLE_CONSOLE_OUTPUT:
      return {
        ...state,
        disableConsoleOutput: !state.disableConsoleOutput,
      }

    case types.TOGGLE_DEBUG_PROTECTION: {
      const debugProtection = !state.debugProtection;
      return {
        ...state,
        debugProtection,
        debugProtectionInterval: state.debugProtectionInterval && debugProtection,
      }
    }

    case types.TOGGLE_DEBUG_PROTECTION_INTERVAL:
      return {
        ...state,
        debugProtectionInterval: !state.debugProtectionInterval,
      }

    case types.TOGGLE_STRING_ARRAY: {
      // Also change the TOGGLE_DEAD_CODE_INJECTION below if changed
      const stringArray = !state.stringArray;
      return {
        ...state,
        stringArray,
        rotateStringArrayEnabled: stringArray,
        stringArrayThresholdEnabled: stringArray,
        stringArrayEncodingEnabled: stringArray,
      }
    }

    case types.TOGGLE_ROTATE_STRING_ARRAY:
      return {
        ...state,
        rotateStringArray: !state.rotateStringArray
      }

    case types.SET_STRING_ARRAY_ENCODING:
      return {
        ...state,
        stringArrayEncoding: action.encoding
      }

    case types.SET_STRING_ARRAY_THRESHOLD:
      return {
        ...state,
        stringArrayThreshold: action.threshold
      }

    case types.SET_SOURCEMAP_MODE: {
      const mode = action.mode;
      return {
        ...state,
        sourceMap: mode !== SOURCEMAP_OFF,
        sourceMapMode: mode,
        sourceMapSeparate: mode === SOURCEMAP_SEPARATE
      }
    }

    case types.SET_SOURCEMAP_BASE_URL:
      return {
        ...state,
        sourceMapBaseUrl: action.baseUrl
      }

    case types.SET_SOURCEMAP_FILE_NAME:
      return {
        ...state,
        sourceMapFileName: action.fileName
      }

    case types.ADD_DOMAIN_LOCK: {
      const domain = action.domain;
      if (state.domainLock.indexOf(domain) !== -1)
        return state;

      return {
        ...state,
        domainLock: [...state.domainLock, domain],
      }
    }

    case types.REMOVE_DOMAIN_LOCK:
      return {
        ...state,
        domainLock: state.domainLock.filter((domain) => domain !== action.domain),
      }

    case types.ADD_RESERVED_NAME: {
      const name = action.name;
      if (state.reservedNames.indexOf(name) !== -1)
        return state;

      return {
        ...state,
        reservedNames: [...state.reservedNames, name],
      }
    }

    case types.REMOVE_RESERVED_NAME:
      return {
        ...state,
        reservedNames: state.reservedNames.filter((name) => name !== action.name),
      }

    case types.SET_SEED:
      return {
        ...state,
        seed: action.seed
      }

    case types.SET_CONTROL_FLOW_FLATTENING_THRESHOLD:
      return {
        ...state,
        controlFlowFlatteningThreshold: action.threshold
      }

    case types.TOGGLE_CONTROL_FLOW_FLATTENING:
      return {
        ...state,
        controlFlowFlattening: !state.controlFlowFlattening
      }

    case types.SET_DEAD_CODE_INJECTION_THRESHOLD:
      return {
        ...state,
        deadCodeInjectionThreshold: action.threshold
      }

    case types.TOGGLE_DEAD_CODE_INJECTION: {
      // Also change the TOGGLE_STRING_ARRAY above if changed
      const deadCodeInjection = !state.deadCodeInjection;
      const stringArray = state.stringArray || deadCodeInjection;
      return {
        ...state,
        stringArray,
        deadCodeInjection: deadCodeInjection,
        rotateStringArrayEnabled: stringArray,
        stringArrayThresholdEnabled: stringArray,
        stringArrayEncodingEnabled: stringArray
      }
    }

    case types.TOGGLE_MANGLE:
      return {
        ...state,
        mangle: !state.mangle
      }

    default:
      return state
  }

}
<commit_msg>Switching two lines for better organization.<commit_after>import * as types from '../constants/ActionTypes';

import { SOURCEMAP_SEPARATE, SOURCEMAP_OFF } from '../containers/OptionsContainer'

const initialState = {
  compact: true,
  selfDefending: false,
  disableConsoleOutput: false,

  debugProtection: false,
  debugProtectionInterval: false,

  stringArray: true,

  rotateStringArray: true,
  rotateStringArrayEnabled: true,

  stringArrayThreshold: 0.8,
  stringArrayThresholdEnabled: true,

  stringArrayEncoding: 'false',
  stringArrayEncodingEnabled: true,

  sourceMap: false,
  sourceMapMode: 'off',
  sourceMapBaseUrl: '',
  sourceMapFileName: '',
  sourceMapSeparate: false,

  domainLock: [],
  reservedNames: [],

  seed: 0,

  controlFlowFlatteningThreshold: 0.75,
  controlFlowFlattening: false,

  deadCodeInjectionThreshold: 0.4,
  deadCodeInjection: false,

  mangle: false,
}

export const options = (state = initialState, action) => {

  // this is necessary because new options need to be filled with their
  // default values and them merged with the state from the localStorage
  // which can be outdated when new options are added
  // from https://github.com/reactjs/redux/issues/433#issuecomment-129188687
  if (!state.hydrated) {
    state = { ...initialState, ...state, hydrated: true };
  }

  switch (action.type) {

    case types.TOGGLE_COMPACT_CODE: {
      const compact = !state.compact;
      return {
        ...state,
        compact,
        selfDefending: state.selfDefending && compact,
      }
    }

    case types.TOGGLE_SELF_DEFENDING: {
      const selfDefending = !state.selfDefending;
      return {
        ...state,
        selfDefending,
        compact: state.compact || selfDefending,
      }
    }

    case types.TOGGLE_DISABLE_CONSOLE_OUTPUT:
      return {
        ...state,
        disableConsoleOutput: !state.disableConsoleOutput,
      }

    case types.TOGGLE_DEBUG_PROTECTION: {
      const debugProtection = !state.debugProtection;
      return {
        ...state,
        debugProtection,
        debugProtectionInterval: state.debugProtectionInterval && debugProtection,
      }
    }

    case types.TOGGLE_DEBUG_PROTECTION_INTERVAL:
      return {
        ...state,
        debugProtectionInterval: !state.debugProtectionInterval,
      }

    case types.TOGGLE_STRING_ARRAY: {
      // Also change the TOGGLE_DEAD_CODE_INJECTION below if changed
      const stringArray = !state.stringArray;
      return {
        ...state,
        stringArray,
        rotateStringArrayEnabled: stringArray,
        stringArrayThresholdEnabled: stringArray,
        stringArrayEncodingEnabled: stringArray,
      }
    }

    case types.TOGGLE_ROTATE_STRING_ARRAY:
      return {
        ...state,
        rotateStringArray: !state.rotateStringArray
      }

    case types.SET_STRING_ARRAY_ENCODING:
      return {
        ...state,
        stringArrayEncoding: action.encoding
      }

    case types.SET_STRING_ARRAY_THRESHOLD:
      return {
        ...state,
        stringArrayThreshold: action.threshold
      }

    case types.SET_SOURCEMAP_MODE: {
      const mode = action.mode;
      return {
        ...state,
        sourceMap: mode !== SOURCEMAP_OFF,
        sourceMapMode: mode,
        sourceMapSeparate: mode === SOURCEMAP_SEPARATE
      }
    }

    case types.SET_SOURCEMAP_BASE_URL:
      return {
        ...state,
        sourceMapBaseUrl: action.baseUrl
      }

    case types.SET_SOURCEMAP_FILE_NAME:
      return {
        ...state,
        sourceMapFileName: action.fileName
      }

    case types.ADD_DOMAIN_LOCK: {
      const domain = action.domain;
      if (state.domainLock.indexOf(domain) !== -1)
        return state;

      return {
        ...state,
        domainLock: [...state.domainLock, domain],
      }
    }

    case types.REMOVE_DOMAIN_LOCK:
      return {
        ...state,
        domainLock: state.domainLock.filter((domain) => domain !== action.domain),
      }

    case types.ADD_RESERVED_NAME: {
      const name = action.name;
      if (state.reservedNames.indexOf(name) !== -1)
        return state;

      return {
        ...state,
        reservedNames: [...state.reservedNames, name],
      }
    }

    case types.REMOVE_RESERVED_NAME:
      return {
        ...state,
        reservedNames: state.reservedNames.filter((name) => name !== action.name),
      }

    case types.SET_SEED:
      return {
        ...state,
        seed: action.seed
      }

    case types.SET_CONTROL_FLOW_FLATTENING_THRESHOLD:
      return {
        ...state,
        controlFlowFlatteningThreshold: action.threshold
      }

    case types.TOGGLE_CONTROL_FLOW_FLATTENING:
      return {
        ...state,
        controlFlowFlattening: !state.controlFlowFlattening
      }

    case types.SET_DEAD_CODE_INJECTION_THRESHOLD:
      return {
        ...state,
        deadCodeInjectionThreshold: action.threshold
      }

    case types.TOGGLE_DEAD_CODE_INJECTION: {
      // Also change the TOGGLE_STRING_ARRAY above if changed
      const deadCodeInjection = !state.deadCodeInjection;
      const stringArray = state.stringArray || deadCodeInjection;
      return {
        ...state,
        deadCodeInjection: deadCodeInjection,
        stringArray,
        rotateStringArrayEnabled: stringArray,
        stringArrayThresholdEnabled: stringArray,
        stringArrayEncodingEnabled: stringArray
      }
    }

    case types.TOGGLE_MANGLE:
      return {
        ...state,
        mangle: !state.mangle
      }

    default:
      return state
  }

}
<|endoftext|>


<commit_before>"use strict";

(function ($) {
  var jsav = new JSAV("pagedBSTCON1", {"animationMode": "none"});

  var bst = jsav.ds.bintree({width: 500, height: 500, nodegap: 40, anchor: "left top"});
  bst.css("padding-bottom", "20px");

  bst.root("");
  genNodes(bst.root(), 0)

  function genNodes(root, level) {
    if (level > 2) {
      return;
    } else {

      genNodes(root.left(""), level + 1);
      genNodes(root.right(""), level + 1);
    }
  }

  bst.layout();

  var x_base = Math.floor(parseInt(bst.css("left")));
  var y_base = 45;
  jsav.g.rect(x_base + 70, y_base - 5, 520, 150, 30, {"stroke-width": 2});
  jsav.g.rect(x_base - 15 , y_base + 160, 160, 165, 30, {"stroke-width": 2});
  jsav.g.rect(x_base + 160, y_base + 160, 160, 165, 30, {"stroke-width": 2});
  jsav.g.rect(x_base + 335, y_base + 160, 160, 165, 30, {"stroke-width": 2});
  jsav.g.rect(x_base + 510, y_base + 160, 160, 165, 30, {"stroke-width": 2});

}(jQuery));

(function ($) {
  var jsav = new JSAV("balanceBSTCON2", {"animationMode": "none"});
  var bst = jsav.ds.bintree({anchor: "left top"});
  var bbst = jsav.ds.bintree({anchor: "right top"});

  bst.root("5");

  bst.root().left("3");
  bst.root().left().left("2");
  bst.root().left().right("4");

  bst.root().right("7");
  bst.root().right().left("6");

  bbst.root("4");

  bbst.root().left("2");
  bbst.root().left().left("1");
  bbst.root().left().right("3");

  bbst.root().right("6");
  bbst.root().right().left("5");
  bbst.root().right().right("7");

  bst.layout();
  bbst.layout();

}(jQuery));
<commit_msg>Made changes to validate with JsHint<commit_after>"use strict";

(function ($) {

  function genNodes(root, level) {
    if (level > 2) {
      return;
    } else {

      genNodes(root.left(""), level + 1);
      genNodes(root.right(""), level + 1);
    }
  }

  var jsav = new JSAV("pagedBSTCON1", {"animationMode": "none"});

  var bst = jsav.ds.bintree({width: 500, height: 500, nodegap: 40, anchor: "left top"});
  bst.css("padding-bottom", "20px");

  bst.root("");
  genNodes(bst.root(), 0);

  bst.layout();

  var x_base = Math.floor(parseInt(bst.css("left")));
  var y_base = 45;
  jsav.g.rect(x_base + 70, y_base - 5, 520, 150, 30, {"stroke-width": 2});
  jsav.g.rect(x_base - 15, y_base + 160, 160, 165, 30, {"stroke-width": 2});
  jsav.g.rect(x_base + 160, y_base + 160, 160, 165, 30, {"stroke-width": 2});
  jsav.g.rect(x_base + 335, y_base + 160, 160, 165, 30, {"stroke-width": 2});
  jsav.g.rect(x_base + 510, y_base + 160, 160, 165, 30, {"stroke-width": 2});

}(jQuery));

(function ($) {
  var jsav = new JSAV("balanceBSTCON2", {"animationMode": "none"});
  var bst = jsav.ds.bintree({anchor: "left top"});
  var bbst = jsav.ds.bintree({anchor: "right top"});

  bst.root("5");

  bst.root().left("3");
  bst.root().left().left("2");
  bst.root().left().right("4");

  bst.root().right("7");
  bst.root().right().left("6");

  bbst.root("4");

  bbst.root().left("2");
  bbst.root().left().left("1");
  bbst.root().left().right("3");

  bbst.root().right("6");
  bbst.root().right().left("5");
  bbst.root().right().right("7");

  bst.layout();
  bbst.layout();

}(jQuery));
<|endoftext|>


<commit_before>// This file is based on  smg-web-design-system/src/js/components/audioplayer
// but requiry instead of importy

var Plyr = require("plyr/dist/plyr.polyfilled.js");

module.exports = () => {
  var player = new Plyr(".c-audioplayer audio");
  var stateClass = document.querySelector(".c-audioplayer").classList;
  player.on("playing", event => {
    stateClass.add("c-audioplayer--playing");
  });
  player.on("pause", event => {
    stateClass.remove("c-audioplayer--playing");
  });
  player.on("ended", event => {
    stateClass.remove("c-audioplayer--playing");
  });

  var button = document.querySelector(".c-audioplayer__button");
  button.addEventListener("click", function(e) {
    player.togglePlay();
  });
};
<commit_msg>update audioplayer js<commit_after>// This file is based on  smg-web-design-system/src/js/components/audioplayer
// but requiry instead of importy

var Plyr = require("plyr/dist/plyr.polyfilled.js");

module.exports = () => {
  var audioplayer = document.querySelector(".c-audioplayer");
  if (audioplayer !== null) {
    var player = new Plyr(".c-audioplayer audio");
    var button = document.querySelector(".c-audioplayer__button");
    player.on("playing", event => {
      audioplayer.classList.add("c-audioplayer--playing");
      button.setAttribute("aria-label", "Pause audio");
    });
    player.on("pause", event => {
      audioplayer.classList.remove("c-audioplayer--playing");
      button.setAttribute("aria-label", "Play audio");
    });
    player.on("ended", event => {
      audioplayer.classList.remove("c-audioplayer--playing");
      button.setAttribute("aria-label", "Play audio");
    });

    button.addEventListener("click", event => {
      player.togglePlay();
    });
  }
};
<|endoftext|>


<commit_before>(function () {
  'use strict';

  angular
    .module('porttare.services')
    .factory('PusherService', PusherService);

  function PusherService($q, $auth, ENV) {
    var serviceLoaded = false,
        loadDeferred,
        headersDeferred,
        pusherClient,
        Authenticator;

    var service = {
      load: loadLibrary,
      listen: listenChannel,
      unlisten: unlistenChannel
    };

    Authenticator = {
      outgoing: function (message, callback) {
        var currentUser = $auth.user;
        message.ext = {
          authorizations: {
            uid: currentUser.uid,
            auth_token: currentUser.auth_token, // jshint ignore:line
            client_id: currentUser.client_id // jshint ignore:line
          }
        };
        callback(message);
      }
    };

    return service;

    function listenChannel(channelName, eventName, callback) {
      var channel = pusherClient.subscribe(channelName);
      channel.bind(eventName, callback);
    }

    function unlistenChannel(channelName) {
      pusherClient.unsubscribe(channelName);
    }

    function loadLibrary() {
      if (serviceLoaded) {
        return updateAuthHeaders();
      } else {
        loadDeferred = $q.defer();
        appendPusherScript();
        return loadDeferred.promise;
      }
    }

    function appendPusherScript() {
      var pusherURI = 'https://js.pusher.com/3.2/pusher.min.js';
      var script = document.createElement('script');
      script.src = pusherURI;
      script.type = 'text/javascript';
      script.onload = pusherLoaded;
      document.getElementsByTagName('head')[0].appendChild(script);
    }

    function pusherLoaded() {
      serviceLoaded = true;
      newPusherClient();
    }

    function updateAuthHeaders() {
      return getAuthHeaders().then(function (headers) {
        pusherClient.config.auth = headers;
      });
    }

    function newPusherClient() {
      getAuthHeaders().then(function (headers) {
        pusherClient = new Pusher(ENV.pusherKey, {
          encrypted: true,
          auth: headers,
          authEndpoint: ENV.apiHost + '/api/pusher_auth', // jshint ignore:line
        });
        loadDeferred.resolve();
      });
    }

    function getAuthHeaders() {
      headersDeferred = $q.defer();
      $auth.validateUser().then(function () {
        var authHeaders = $auth.retrieveData('auth_headers'); // jshint ignore:line
        authHeaders.Accept = 'application/json';
        headersDeferred.resolve({ headers: authHeaders });
      });
      return headersDeferred.promise;
    }
  }
})();
<commit_msg>unused stuff<commit_after>(function () {
  'use strict';

  angular
    .module('porttare.services')
    .factory('PusherService', PusherService);

  function PusherService($q, $auth, ENV) {
    var serviceLoaded = false,
        loadDeferred,
        headersDeferred,
        pusherClient;

    var service = {
      load: loadLibrary,
      listen: listenChannel,
      unlisten: unlistenChannel
    };

    return service;

    function listenChannel(channelName, eventName, callback) {
      var channel = pusherClient.subscribe(channelName);
      channel.bind(eventName, callback);
    }

    function unlistenChannel(channelName) {
      pusherClient.unsubscribe(channelName);
    }

    function loadLibrary() {
      if (serviceLoaded) {
        return updateAuthHeaders();
      } else {
        loadDeferred = $q.defer();
        appendPusherScript();
        return loadDeferred.promise;
      }
    }

    function appendPusherScript() {
      var pusherURI = 'https://js.pusher.com/3.2/pusher.min.js';
      var script = document.createElement('script');
      script.src = pusherURI;
      script.type = 'text/javascript';
      script.onload = pusherLoaded;
      document.getElementsByTagName('head')[0].appendChild(script);
    }

    function pusherLoaded() {
      serviceLoaded = true;
      newPusherClient();
    }

    function updateAuthHeaders() {
      return getAuthHeaders().then(function (headers) {
        pusherClient.config.auth = headers;
      });
    }

    function newPusherClient() {
      getAuthHeaders().then(function (headers) {
        pusherClient = new Pusher(ENV.pusherKey, {
          encrypted: true,
          auth: headers,
          authEndpoint: ENV.apiHost + '/api/pusher_auth', // jshint ignore:line
        });
        loadDeferred.resolve();
      });
    }

    function getAuthHeaders() {
      headersDeferred = $q.defer();
      $auth.validateUser().then(function () {
        var authHeaders = $auth.retrieveData('auth_headers'); // jshint ignore:line
        authHeaders.Accept = 'application/json';
        headersDeferred.resolve({ headers: authHeaders });
      });
      return headersDeferred.promise;
    }
  }
})();
<|endoftext|>


<commit_before>// Create the canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 800;
canvas.height = 600;
document.body.appendChild(canvas);

var ProtoBuf = dcodeIO.ProtoBuf;
var builder = ProtoBuf.loadJsonFile("js/superstellar_proto.json");
var Space = builder.build("superstellar.Space");

// Background image
var shipReady = false;
var shipThrustReady = false;

var shipImage = new Image();
shipImage.onload = function () {
	shipReady = true;
};
shipImage.src = "images/ship.png";

var shipThrustImage = new Image();
shipThrustImage.onload = function () {
	shipThrustReady = true;
};
shipThrustImage.src = "images/ship_thrust.png";

var ships = {};

var viewport = {vx: 0, vy: 0, width: 800, height: 600}

var ws = new WebSocket("ws://" + window.location.host + "/superstellar");

ws.onmessage = function(e) {
	var fileReader = new FileReader();
	fileReader.onload = function() {
			ships = Space.decode(this.result).spaceships
	};

	fileReader.readAsArrayBuffer(e.data);
};

// Handle keyboard controls
var keysDown = {};

addEventListener("keydown", function (e) {
	var direction
	switch (e.keyCode) {
		case 38:
			direction = "up"
			break;
		case 40:
			direction = "down"
			break;
		case 37:
			direction = "left"
			break;
		case 39:
			direction = "right"
			break;
	}

	ws.send($.toJSON({client_id: 1, direction: direction}))
}, false);

var translateToViewport = function (x, y, viewport) {
  var newX = x + viewport.vx + viewport.width / 2;
	var newY = -y - viewport.vy + viewport.height / 2;
	return {x: newX, y: newY}
}

// Draw everything
var render = function () {
	ctx.beginPath();
	ctx.rect(0, 0, 800, 600);
	ctx.fillStyle = "black";
	ctx.fill();

	if (shipReady) {
		for (var shipID in ships) {
			var ship = ships[shipID]

			image = ship.input_thrust ? shipThrustImage : shipImage

			var translatedPosition = translateToViewport(ship.position.x/100, ship.position.y/100, viewport)

			ctx.translate(translatedPosition.x, translatedPosition.y);
			ctx.fillStyle = "rgb(250, 250, 250)";
			ctx.font = "18px Helvetica";
			ctx.fillText(ship.id, -35, -60);

			ctx.rotate(ship.facing);
			ctx.drawImage(image, -30, -22);
			ctx.rotate(-ship.facing);
			ctx.translate(-translatedPosition.x, -translatedPosition.y);
		}
	}

	// Score
	ctx.fillStyle = "rgb(250, 250, 250)";
	ctx.font = "24px Helvetica";
	ctx.textAlign = "left";
	ctx.textBaseline = "top";
	ctx.fillText("Ships: " + ships.length, 580, 10);
};

// The main game loop
var main = function () {
	var now = Date.now();
	var delta = now - then;

	render();

	then = now;

	// Request to do this again ASAP
	requestAnimationFrame(main);
};

// Let's play this game!
var then = Date.now();
main();
<commit_msg>Add viewport following after own ship<commit_after>// Create the canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 800;
canvas.height = 600;
document.body.appendChild(canvas);

var ProtoBuf = dcodeIO.ProtoBuf;
var builder = ProtoBuf.loadJsonFile("js/superstellar_proto.json");
var Space = builder.build("superstellar.Space");

// Background image
var shipReady = false;
var shipThrustReady = false;

var shipImage = new Image();
shipImage.onload = function () {
	shipReady = true;
};
shipImage.src = "images/ship.png";

var shipThrustImage = new Image();
shipThrustImage.onload = function () {
	shipThrustReady = true;
};
shipThrustImage.src = "images/ship_thrust.png";

var ships = [];

var viewport = {vx: 0, vy: 0, width: 800, height: 600}

var ws = new WebSocket("ws://" + window.location.host + "/superstellar");

ws.onmessage = function(e) {
	var fileReader = new FileReader();
	fileReader.onload = function() {
			ships = Space.decode(this.result).spaceships
	};

	fileReader.readAsArrayBuffer(e.data);
};

// Handle keyboard controls
var keysDown = {};

addEventListener("keydown", function (e) {
	var direction
	switch (e.keyCode) {
		case 38:
			direction = "up"
			break;
		case 40:
			direction = "down"
			break;
		case 37:
			direction = "left"
			break;
		case 39:
			direction = "right"
			break;
	}

	ws.send($.toJSON({client_id: 1, direction: direction}))
}, false);

var translateToViewport = function (x, y, viewport) {
  var newX = x - viewport.vx + viewport.width / 2;
	var newY = -y + viewport.vy + viewport.height / 2;
	return {x: newX, y: newY}
}

// Draw everything
var render = function () {
	ctx.beginPath();

	if (ships.length > 0) {
        var ownPosition = {x: ships[0].position.x/100, y: ships[0].position.y/100};
        viewport = {vx: ownPosition.x, vy: ownPosition.y, width: 800, height: 600};
    }

	ctx.rect(0, 0, 800, 600);
	ctx.fillStyle = "black";
	ctx.fill();

	if (shipReady) {
		for (var shipID in ships) {
			var ship = ships[shipID]

			image = ship.input_thrust ? shipThrustImage : shipImage

			var translatedPosition = translateToViewport(ship.position.x/100, ship.position.y/100, viewport)

			ctx.translate(translatedPosition.x, translatedPosition.y);
			ctx.fillStyle = "rgb(250, 250, 250)";
			ctx.font = "18px Helvetica";
			ctx.fillText(ship.id, -35, -60);

			ctx.rotate(ship.facing);
			ctx.drawImage(image, -30, -22);
			ctx.rotate(-ship.facing);
			ctx.translate(-translatedPosition.x, -translatedPosition.y);
		}
	}

	// Score
	ctx.fillStyle = "rgb(250, 250, 250)";
	ctx.font = "24px Helvetica";
	ctx.textAlign = "left";
	ctx.textBaseline = "top";
	ctx.fillText("Ships: " + ships.length, 580, 10);
};

// The main game loop
var main = function () {
	var now = Date.now();
	var delta = now - then;

	render();

	then = now;

	// Request to do this again ASAP
	requestAnimationFrame(main);
};

// Let's play this game!
var then = Date.now();
main();
<|endoftext|>