<commit_before>/*
options = {
    onPlayerConnect: function()
    onPlayerDisconnect: function (),
    onPlayerReconnect: function (),
    onPlayerUpdate: function (state),
    onEntityUpdate: function (state),
    onPlayerRemove: function (id),
    onEntityRemove: function (id),
    onEvent: function (data),
    onWorldState: function (state),
    onPing: function (pingDelay),
    onUpdatePlayerPhysics: function (state, inputs, deltaTime),
    onInterpolation: function(previousState, targetState, amount)
    logging: true
}
api methods
    initializeGarageServer(path, options)
    addInput({})
    getStates(function (playerStates, entityStates))
    getId() : 0
*/
var GarageServerIO = (function (socketio) {

    "use strict";

    function StateController() {
        this.clientTime = 0;
        this.renderTime = 0;
        this.physicsDelta = 0.0;
        this.id = '';
        this.pingDelay = 100;
        this.interpolationDelay = 100;
        this.interpolation = false;
        this.pingInterval = 2000;
        this.clientSidePrediction = false;
        this.smoothingFactor = 0.3;
        this.worldState = {};
    }
    StateController.prototype = {
        setTime: function (serverTime) {
            this.clientTime = serverTime;
            this.renderTime = this.clientTime - this.interpolationDelay;
        }
    };

    function Input(input, seq) {
        this.input = input;
        this.seq = seq;
    }

    function InputController() {
        this.inputs = [];
        this.sequenceNumber = 1;
    }
    InputController.prototype = {
        any: function () {
            return this.inputs.length > 0;
        },
        add: function (input) {
            this.sequenceNumber += 1;
            this.inputs.push(new Input(input, this.sequenceNumber));
        },
        remove: function (seq) {
            for (var i = 0; i < this.inputs.length; i ++) {
                if (this.inputs[i].seq === seq) {
                    this.inputs.splice(0, i + 1);
                    break;
                }
            }
        }
    };

    function Update(state, seq, time) {
        this.state = state;
        this.seq = seq;
        this.time = time;
    }

    function Entity(id) {
        this.updates = [];
        this.id = id;
        this.state = {};
        this.inputController = new InputController();
    }
    Entity.prototype = {
        addUpdate: function (state, seq, time) {
            var newUpdate = new Update(state, seq, time);
            if (this.updates.length === 0) {
                this.state = newUpdate.state;
            }
            this.updates.push(newUpdate);
            if (this.updates.length > 120) {
                this.updates.splice(0, 1);
            }
        },
        updateState: function (state, seq, time) {
            var updateFound = false;
            this.updates.some(function (update) {
               if (update.seq === seq) {
                   update.state = state;
                   updateFound = true;
                   return true;
               }
            });
            if (!updateFound) {
                this.addUpdate(state, seq, time);
            }
        },
        anyUpdates: function () {
            return this.updates.length > 0;
        },
        latestUpdate: function () {
            return this.updates[this.updates.length - 1];
        },
        surroundingPositions: function (time) {
            var positions = {};
            for (var i = 0; i < this.updates.length; i ++) {
                var previous = this.updates[i],
                    target = this.updates[i + 1];

                if (previous && target && time > previous.time && time < target.time) {
                    positions.previous = previous;
                    positions.target = target;
                    break;
                }
            }
            return positions;
        }
    };

    function Player(id) {
        Entity.call(this, id);
    }
    Player.prototype = Object.create(Entity.prototype);

    function EntityController() {
        this.entities = [];
    }
    EntityController.prototype = {
        add: function (id) {
            var entity = new Entity(id);
            this.entities.push(entity);
            return entity;
        },
        remove: function (id) {
            for (var i = 0; i < this.entities.length; i ++) {
                if (this.entities[i].id === id) {
                    this.entities.splice(i, 1);
                    return;
                }
            }
        }
    };

    function PlayerController() {
        EntityController.call(this);
    }
    PlayerController.prototype = Object.create(EntityController.prototype);
    PlayerController.prototype.add = function (id) {
        var player = new Player(id);
        this.entities.push(player);
        return player;
    };

    var _io = socketio,
        _socket = null,
        _options = null,
        _stateController = new StateController(),
        _playerController = new PlayerController(),
        _entityController = new EntityController(),

        initializeGarageServer = function (path, options) {
            _options = options;
            _socket = _io.connect(path + '/garageserver.io');
            registerSocketEvents();
        },

        registerSocketEvents = function () {
            _socket.on('connect', function () {
                _stateController.id = _socket.socket.sessionid;
                _playerController.add(_stateController.id);
                if (_options.onPlayerConnect) {
                    _options.onPlayerConnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket connect');
                }
            });
            _socket.on('state', function (data) {
                if (_options.onWorldState) {
                    _options.onWorldState(data.worldState);
                }
                _stateController.physicsDelta = data.physicsDelta;
                _stateController.interpolation = data.interpolation;
                _stateController.interpolationDelay = data.interpolationDelay;
                _stateController.pingInterval = data.pingInterval;
                _stateController.clientSidePrediction = data.clientSidePrediction;
                _stateController.smoothingFactor = data.smoothingFactor;
                _stateController.worldState = data.worldState;

                setInterval(function (){
                    _socket.emit('ping', new Date().getTime());
                }, _stateController.pingInterval);
            });
            _socket.on('disconnect', function () {
                if (_options.onPlayerDisconnect) {
                    _options.onPlayerDisconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket disconnect');
                }
            });
            _socket.on('reconnect', function () {
                if (_options.onPlayerReconnect) {
                    _options.onPlayerReconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket reconnect');
                }
            });
            _socket.on('update', function (data) {
                update(data);
            });
            _socket.on('ping', function (data) {
                _stateController.pingDelay = new Date().getTime() - data;
                if (_options.onPing) {
                    _options.onPing(_stateController.pingDelay);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket ping delay ' + _stateController.pingDelay);
                }
            });
            _socket.on('removePlayer', function (id) {
                removePlayer(id);
                if (_options.onPlayerRemove) {
                    _options.onPlayerRemove(id);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket removePlayer ' + id);
                }
            });
            _socket.on('removeEntity', function (id) {
                removeEntity(id);
                if (_options.onEntityRemove) {
                    _options.onEntityRemove(id);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket removeEntity ' + id);
                }
            });
            _socket.on('event', function(data) {
                if (_options.onEvent) {
                    _options.onEvent(data);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket event ' + data);
                }
            });
        },

        getId = function () {
            return _stateController.id;
        },

        addInput = function (clientInput) {
            _playerController.entities.some(function (player) {
                if (player.id === _stateController.id) {
                    if (_stateController.clientSidePrediction && _options.onUpdatePlayerPhysics) {
                        player.inputController.add(clientInput);
                        player.state = _options.onUpdatePlayerPhysics(player.state, [{ input: clientInput }], _stateController.physicsDelta);
                    }
                    _socket.emit('input', [ clientInput, player.inputController.sequenceNumber, _stateController.renderTime ]);
                }
            });
        },

        getStates = function (stateCallback) {
            if (_stateController.interpolation && _options.onInterpolation) {
                processEntityStatesInterpolated(_entityController);
                processEntityStatesInterpolated(_playerController);
            }
            else {
                processEntityStatesCurrent(_entityController);
                processEntityStatesCurrent(_playerController);
            }
            stateCallback(_playerController.entities, _entityController.entities);
        },

        removePlayer = function (id) {
            _playerController.remove(id);
        },

        removeEntity = function (id) {
            _entityController.remove(id);
        },

        update = function (data) {
            _stateController.setTime(data.time);

            updatePlayers(data);
            updateEntities(data);
        },

        updatePlayers = function (data) {
            data.playerStates.forEach(function (playerState) {
                updateEntity(_playerController, playerState, data.time);

                if (_options.onPlayerUpdate) {
                    _options.onPlayerUpdate(playerState[1]);
                }
            });
        },

        updateEntities = function (data) {
            data.entityStates.forEach(function (entityState) {
                updateEntity(_entityController, entityState, data.time);

                if (_options.onEntityUpdate) {
                    _options.onEntityUpdate(entityState[1]);
                }
            });
        },

        updateEntity = function (entityController, entityState, time) {
            var entityFound = false;
            entityController.entities.some(function (entity) {
                if (entity.id === entityState[0]) {
                    entityFound = true;
                    entity.updateState(entityState[1], entityState[2], time);
                    return true;
                }
            });
            if (!entityFound) {
                var newEntity = entityController.add(entityState[0]);
                newEntity.addUpdate(entityState[1], entityState[2], time);
            }
        },

        processEntityStatesCurrent = function (entityController) {
            entityController.entities.forEach(function (entity) {
                if (entity.anyUpdates() && !entity.inputController.any()) {
                    entity.state = entity.latestUpdate().state;
                }
            });
        },

        processEntityStatesInterpolated = function (entityController) {
            var positions, amount, newState;
            entityController.entities.forEach(function (entity) {
                if (entity.anyUpdates() && !entity.inputController.any()) {
                    positions = entity.surroundingPositions(_stateController.renderTime);
                    if (positions.previous && positions.target) {
                        amount = getInterpolatedAmount(positions.previous.time, positions.target.time);
                        newState = _options.onInterpolation(positions.previous.state, positions.target.state, amount);
                        entity.state = newState = _options.onInterpolation(entity.state, newState, _stateController.smoothingFactor);
                    }
                }
            });
        },

        getInterpolatedAmount = function (previousTime, targetTime) {
            var range = targetTime - previousTime,
                difference = _stateController.renderTime - previousTime,
                amount = parseFloat((difference / range).toFixed(3));

            return amount;
        };

    return {
        initializeGarageServer: initializeGarageServer,
        addInput: addInput,
        getStates: getStates,
        getId: getId
    };

}) (io);<commit_msg>Changed getStates to return list of literals - quick fix to avoid exposing methods<commit_after>/*
options = {
    onPlayerConnect: function()
    onPlayerDisconnect: function (),
    onPlayerReconnect: function (),
    onPlayerUpdate: function (state),
    onEntityUpdate: function (state),
    onPlayerRemove: function (id),
    onEntityRemove: function (id),
    onEvent: function (data),
    onWorldState: function (state),
    onPing: function (pingDelay),
    onUpdatePlayerPhysics: function (state, inputs, deltaTime),
    onInterpolation: function(previousState, targetState, amount)
    logging: true
}
api methods
    initializeGarageServer(path, options)
    addInput({})
    getStates(function ([playerStates], [entityStates]))
    getId() : 'playerid'
*/
var GarageServerIO = (function (socketio) {

    "use strict";

    function StateController() {
        this.clientTime = 0;
        this.renderTime = 0;
        this.physicsDelta = 0.0;
        this.id = '';
        this.pingDelay = 100;
        this.interpolationDelay = 100;
        this.interpolation = false;
        this.pingInterval = 2000;
        this.clientSidePrediction = false;
        this.smoothingFactor = 0.3;
        this.worldState = {};
    }
    StateController.prototype = {
        setTime: function (serverTime) {
            this.clientTime = serverTime;
            this.renderTime = this.clientTime - this.interpolationDelay;
        }
    };

    function Input(input, seq) {
        this.input = input;
        this.seq = seq;
    }

    function InputController() {
        this.inputs = [];
        this.sequenceNumber = 1;
    }
    InputController.prototype = {
        any: function () {
            return this.inputs.length > 0;
        },
        add: function (input) {
            this.sequenceNumber += 1;
            this.inputs.push(new Input(input, this.sequenceNumber));
        },
        remove: function (seq) {
            for (var i = 0; i < this.inputs.length; i ++) {
                if (this.inputs[i].seq === seq) {
                    this.inputs.splice(0, i + 1);
                    break;
                }
            }
        }
    };

    function Update(state, seq, time) {
        this.state = state;
        this.seq = seq;
        this.time = time;
    }

    function Entity(id) {
        this.updates = [];
        this.id = id;
        this.state = {};
        this.inputController = new InputController();
    }
    Entity.prototype = {
        addUpdate: function (state, seq, time) {
            var newUpdate = new Update(state, seq, time);
            if (this.updates.length === 0) {
                this.state = newUpdate.state;
            }
            this.updates.push(newUpdate);
            if (this.updates.length > 120) {
                this.updates.splice(0, 1);
            }
        },
        updateState: function (state, seq, time) {
            var updateFound = false;
            this.updates.some(function (update) {
               if (update.seq === seq) {
                   update.state = state;
                   updateFound = true;
                   return true;
               }
            });
            if (!updateFound) {
                this.addUpdate(state, seq, time);
            }
        },
        anyUpdates: function () {
            return this.updates.length > 0;
        },
        latestUpdate: function () {
            return this.updates[this.updates.length - 1];
        },
        surroundingPositions: function (time) {
            var positions = {};
            for (var i = 0; i < this.updates.length; i ++) {
                var previous = this.updates[i],
                    target = this.updates[i + 1];

                if (previous && target && time > previous.time && time < target.time) {
                    positions.previous = previous;
                    positions.target = target;
                    break;
                }
            }
            return positions;
        }
    };

    function Player(id) {
        Entity.call(this, id);
    }
    Player.prototype = Object.create(Entity.prototype);

    function EntityController() {
        this.entities = [];
    }
    EntityController.prototype = {
        add: function (id) {
            var entity = new Entity(id);
            this.entities.push(entity);
            return entity;
        },
        remove: function (id) {
            for (var i = 0; i < this.entities.length; i ++) {
                if (this.entities[i].id === id) {
                    this.entities.splice(i, 1);
                    return;
                }
            }
        }
    };

    function PlayerController() {
        EntityController.call(this);
    }
    PlayerController.prototype = Object.create(EntityController.prototype);
    PlayerController.prototype.add = function (id) {
        var player = new Player(id);
        this.entities.push(player);
        return player;
    };

    var _io = socketio,
        _socket = null,
        _options = null,
        _stateController = new StateController(),
        _playerController = new PlayerController(),
        _entityController = new EntityController(),

        initializeGarageServer = function (path, options) {
            _options = options;
            _socket = _io.connect(path + '/garageserver.io');
            registerSocketEvents();
        },

        registerSocketEvents = function () {
            _socket.on('connect', function () {
                _stateController.id = _socket.socket.sessionid;
                _playerController.add(_stateController.id);
                if (_options.onPlayerConnect) {
                    _options.onPlayerConnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket connect');
                }
            });
            _socket.on('state', function (data) {
                if (_options.onWorldState) {
                    _options.onWorldState(data.worldState);
                }
                _stateController.physicsDelta = data.physicsDelta;
                _stateController.interpolation = data.interpolation;
                _stateController.interpolationDelay = data.interpolationDelay;
                _stateController.pingInterval = data.pingInterval;
                _stateController.clientSidePrediction = data.clientSidePrediction;
                _stateController.smoothingFactor = data.smoothingFactor;
                _stateController.worldState = data.worldState;

                setInterval(function (){
                    _socket.emit('ping', new Date().getTime());
                }, _stateController.pingInterval);
            });
            _socket.on('disconnect', function () {
                if (_options.onPlayerDisconnect) {
                    _options.onPlayerDisconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket disconnect');
                }
            });
            _socket.on('reconnect', function () {
                if (_options.onPlayerReconnect) {
                    _options.onPlayerReconnect();
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket reconnect');
                }
            });
            _socket.on('update', function (data) {
                update(data);
            });
            _socket.on('ping', function (data) {
                _stateController.pingDelay = new Date().getTime() - data;
                if (_options.onPing) {
                    _options.onPing(_stateController.pingDelay);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket ping delay ' + _stateController.pingDelay);
                }
            });
            _socket.on('removePlayer', function (id) {
                removePlayer(id);
                if (_options.onPlayerRemove) {
                    _options.onPlayerRemove(id);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket removePlayer ' + id);
                }
            });
            _socket.on('removeEntity', function (id) {
                removeEntity(id);
                if (_options.onEntityRemove) {
                    _options.onEntityRemove(id);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket removeEntity ' + id);
                }
            });
            _socket.on('event', function(data) {
                if (_options.onEvent) {
                    _options.onEvent(data);
                }
                if (_options.logging) {
                    console.log('garageserver.io:: socket event ' + data);
                }
            });
        },

        getId = function () {
            return _stateController.id;
        },

        addInput = function (clientInput) {
            _playerController.entities.some(function (player) {
                if (player.id === _stateController.id) {
                    if (_stateController.clientSidePrediction && _options.onUpdatePlayerPhysics) {
                        player.inputController.add(clientInput);
                        player.state = _options.onUpdatePlayerPhysics(player.state, [{ input: clientInput }], _stateController.physicsDelta);
                    }
                    _socket.emit('input', [ clientInput, player.inputController.sequenceNumber, _stateController.renderTime ]);
                }
            });
        },

        getStates = function (stateCallback) {
            var playerStates = [], entityStates = [];

            if (_stateController.interpolation && _options.onInterpolation) {
                processEntityStatesInterpolated(_entityController);
                processEntityStatesInterpolated(_playerController);
            }
            else {
                processEntityStatesCurrent(_entityController);
                processEntityStatesCurrent(_playerController);
            }

            _playerController.entities.forEach(function(player) {
               playerStates.push({ state: player.state });
            });
            _entityController.entities.forEach(function(entity) {
               entityStates.push({ state: entity.state });
            });

            stateCallback(playerStates, entityStates);
        },

        removePlayer = function (id) {
            _playerController.remove(id);
        },

        removeEntity = function (id) {
            _entityController.remove(id);
        },

        update = function (data) {
            _stateController.setTime(data.time);

            updatePlayers(data);
            updateEntities(data);
        },

        updatePlayers = function (data) {
            data.playerStates.forEach(function (playerState) {
                updateEntity(_playerController, playerState, data.time);

                if (_options.onPlayerUpdate) {
                    _options.onPlayerUpdate(playerState[1]);
                }
            });
        },

        updateEntities = function (data) {
            data.entityStates.forEach(function (entityState) {
                updateEntity(_entityController, entityState, data.time);

                if (_options.onEntityUpdate) {
                    _options.onEntityUpdate(entityState[1]);
                }
            });
        },

        updateEntity = function (entityController, entityState, time) {
            var entityFound = false;
            entityController.entities.some(function (entity) {
                if (entity.id === entityState[0]) {
                    entityFound = true;
                    entity.updateState(entityState[1], entityState[2], time);
                    return true;
                }
            });
            if (!entityFound) {
                var newEntity = entityController.add(entityState[0]);
                newEntity.addUpdate(entityState[1], entityState[2], time);
            }
        },

        processEntityStatesCurrent = function (entityController) {
            entityController.entities.forEach(function (entity) {
                if (entity.anyUpdates() && !entity.inputController.any()) {
                    entity.state = entity.latestUpdate().state;
                }
            });
        },

        processEntityStatesInterpolated = function (entityController) {
            var positions, amount, newState;
            entityController.entities.forEach(function (entity) {
                if (entity.anyUpdates() && !entity.inputController.any()) {
                    positions = entity.surroundingPositions(_stateController.renderTime);
                    if (positions.previous && positions.target) {
                        amount = getInterpolatedAmount(positions.previous.time, positions.target.time);
                        newState = _options.onInterpolation(positions.previous.state, positions.target.state, amount);
                        entity.state = newState = _options.onInterpolation(entity.state, newState, _stateController.smoothingFactor);
                    }
                }
            });
        },

        getInterpolatedAmount = function (previousTime, targetTime) {
            var range = targetTime - previousTime,
                difference = _stateController.renderTime - previousTime,
                amount = parseFloat((difference / range).toFixed(3));

            return amount;
        };

    return {
        initializeGarageServer: initializeGarageServer,
        addInput: addInput,
        getStates: getStates,
        getId: getId
    };

}) (io);<|endoftext|>


<commit_before>require("./vendor/opal-compiler.js")

import process from "process"
import loaderUtils from "loader-utils"
import { SourceNode, SourceMapConsumer, SourceMapGenerator } from 'source-map'
import path from "path"
import fs from "fs";

const LOAD_PATH = process.env.OPAL_LOAD_PATH ? process.env.OPAL_LOAD_PATH.split(":") : [];

if (LOAD_PATH.length === 0) {
  console.warn("OPAL_LOAD_PATH environment variable is not set")
  console.warn("By default, loader will only load from path relative to current source")
}

function getCurrentLoader(loaderContext) {
  return loaderContext.loaders[loaderContext.loaderIndex];
}

function resolveFilename(loaderContext, filename) {
  let rubyFileName = filename.replace(/(\.rb)?$/, ".rb");

  // FIXME
  // Workaround to make "require 'opal'" work, original opal will try to concate raw js
  if (filename == 'corelib/runtime') {
    rubyFileName = 'corelib/runtime.js'
  }

  let result = null;
  if (rubyFileName.match(/^\./)) {
    // Resolve in current directory
    let fullPath = path.resolve(loaderContext.context, rubyFileName);
    if (fs.existsSync(fullPath)) {
      result = fullPath;
    }
  } else {
    // Resolve in LOAD_PATH
    for (var dir of LOAD_PATH) {
      let fullPath = path.resolve(dir, rubyFileName);
      if (fs.existsSync(fullPath)) {
        result = fullPath;
        break;
      }
    }
  }

  if (result) {
    return result;
  } else {
    throw new Error(`Cannot load file - ${filename}`);
  }
}

module.exports = function(source) {
  const callback = this.async();
  if(!callback) throw new Error("Sync mode not supported");

  const compilerOptions = Object.assign({file: this.resourcePath}, loaderUtils.parseQuery(this.query));
  const compiler = Opal.Opal.$$scope.Compiler.$new(source, Opal.hash(compilerOptions));
  const currentLoader = getCurrentLoader(this).path;

  this.cacheable && this.cacheable()

  compiler.$compile();

  const result = compiler.$result();

  /*
    Workaround to make IO work,
    webpack polyfill global "process" module by default,
    while Opal::IO rely on it to deterimine in node environment or not
  */
  let prepend = ['process = undefined;'];

  compiler.$requires().forEach(filename => {
    var resolved = resolveFilename(this, filename);
    if (resolved.match(/\.js$/)) {
      prepend.push(`require('${require.resolve('imports-loader')}!${resolved}');`);
      prepend.push(`Opal.loaded('${filename}');`)
    } else {
      prepend.push(`require('!!${currentLoader}?file=${filename}&requirable=true!${resolved}');`);
    }
  })

  let combinedResult = prepend.join(" ") + "\n" + result;

  if (this.sourceMap) {
    let rawMap = JSON.parse(compiler.$source_map().$as_json().$to_json());

    // Since it's compiled from the current resource
    rawMap.sources = [this.resourcePath];

    // Set source content
    let consumer = new SourceMapConsumer(rawMap)
    let map = SourceMapGenerator.fromSourceMap(consumer);
    map.setSourceContent(this.resourcePath, source);

    // Prepend the chunk of our injected script
    let node = SourceNode.fromStringWithSourceMap(result, new SourceMapConsumer(map.toString()));
    node.prepend(prepend.join(" "));

    callback(null, combinedResult, JSON.parse(node.toStringWithSourceMap().map.toString()));
  } else {
    callback(null, combinedResult);
  }
};
<commit_msg>this has been replaced<commit_after><|endoftext|>


<commit_before>///////////
// Webpack.config.js
//
// Uses Webpack to build our javascript assets. Creates source maps
// as well.
//
// All considerit servers serve javascript compiled through this
// script, both dev and production.
//
// In development, you can have Webpack watch for changes so that the
// compilation is automatic:
//    > node_modules/webpack/bin/webpack.js --progress --colors --watch
//    (shortcut:   >  bin/webpack )
//
// To compile for production, set the environment variable
//      BUILD_PRODUCTION
//
// In production, if we're using a non-local asset host like Cloudfront,
// this will also upload the built javascript (and images) to the configured
// host.
//
// This process replaces Rails' asset pipeline.

///////////////////////
// Input
//
// These variables are pulled out because they're most likely to be configured
// as part of writing an application.

/////
// Global variables
// These variables will be made global on Window for use in all js files
global_variables = {
  '_': "vendor/underscore",
  $: "vendor/jquery",
  jQuery: "vendor/jquery",
  React: "vendor/react"
}

/////
// Entry points
// These are the different entry points to the application that will be
// compiled. All required files starting from the entry point will be compiled
// into the respective build.
entry_points = {
  franklin: './@client/franklin.coffee',
  saas_landing_page: './@client/saas_landing_page.coffee'
}

////////////////////////////////////////
// Innards

var webpack = require('webpack'),
    path = require('path'),
    is_dev = !JSON.parse(process.env.BUILD_PRODUCTION || 'false');

config = {

  debug : is_dev,
  displayErrorDetails : true,
  outputPathinfo : true,
  devtool : is_dev ? 'eval' : 'sourcemap',

  entry: entry_points,

  // We output build javascript to public/build.
  // A cache-busting digest is appended when we're in production
  output: {
    path: './public/build',
    filename: is_dev ? "[name].js" : "[name].[chunkhash].js"
  },

  module: {

    // Enables compilation of coffee into java
    loaders: [
      { test: /\.coffee$/, loader: 'coffee-loader' },
    ],

    // Files that have already been compiled with webpack
    // or something like browserify can be skipped. If they're
    // not, they can sometimes cause Webpack problems...
    noParse: [
      /react\.js$/,
      /jquery\.js$/,
      /quill\.js$/
    ]
  },

  resolve: {
    root: [__dirname, '@client'].join('/'),
    extensions: ['', '.js', '.json', '.coffee']
           // don't have to specify .js etc in requires statements
  },

  plugins: [

    ///////////////
    // DefinePlugin
    //
    // Each instance of a key will be replaced with the
    // value in the build.
    new webpack.DefinePlugin({
      __DEV__: is_dev,
      __PRODUCTION__: !is_dev
    }),

    ///////////////
    // ProvidePlugin
    //
    // Makes these variables global across all javascript files
    new webpack.ProvidePlugin(global_variables),

    ////////////
    // Creates a public/build/manifest.json file that maps from
    // each entry point to the compiled version of it. Useful in
    // particular when the compiled filename includes a digest
    function() {
      this.plugin("done", function(stats) {
        manifest = {}

        for (var prop in config.entry){
          if(prop){
            file = stats.toJson().assetsByChunkName[prop]
            if (file && typeof file !== 'string')
              file = file[0]

            manifest[prop] = path.join("build", file)
          }
        }
        require("fs").writeFileSync(
          path.join(__dirname, "public", "build", "manifest.json"),
          JSON.stringify(manifest));
      });
    }
  ]
}

/////
// Additional work when we're compiling for production
if(!is_dev){

  var s3 = require('s3'),
      YAML = require('yamljs'),
      CompressionPlugin = require("compression-webpack-plugin")

  config.plugins.push(

    //////
    // Uglify
    // Further compression. Unexpectedly cuts gzipped main js file size in half.
    // This is the slowest part of the process of building.
    new webpack.optimize.UglifyJsPlugin(),

    new webpack.optimize.OccurenceOrderPlugin(),

    //////
    // Compression
    // Gzip the js/sourcemaps coming through the pipeline. Note that the file
    // extension doesn't change. Web servers need to have Content-Encoding gzip
    // set for these files.
    new CompressionPlugin({
        asset: "{file}",
        algorithm: "gzip"
    }),

    /////////
    // Upload to s3 (and as a consequence, Cloudfront)
    //
    // Finally we'll upload these compiled js/source maps to s3, along with
    // syncing the public/images directory.
    function() {
      this.plugin("done", function(stats) {

        local = YAML.load('config/local_environment.yml').default
        if (!local.aws) return // do nothing if aws isn't configured

        var s3_client = s3.createClient({
            s3Options : {
              accessKeyId: local.aws.access_key_id,
              secretAccessKey: local.aws.secret_access_key,
            }})

        // Syncs a directory from this host to s3.
        // set is_gzipped if you want to set the Content-Encoding to gzip for all
        // files in this directory.
        var uploadDir = function(src, dest, is_gzipped) {

          s3_params = {
            Bucket: local.aws.s3_bucket,
            Prefix: dest,
            Expires: new Date(new Date().setYear(new Date().getFullYear() + 1)),
            CacheControl: 'public, max-age=31557600'
          }

          if (is_gzipped)
            s3_params.ContentEncoding = 'gzip'

          var uploader = s3_client.uploadDir({
            localDir: src,
            deleteRemoved: false, // remove s3 objects that lack
                                  // a corresponding local file.
            s3Params: s3_params
          })

          uploader.on('error', function(err) {
            console.error("unable to sync:", err.stack)
          })
          uploader.on('end', function() {
            console.log("done uploading")
          })
          uploader.on('fileUploadEnd', function(path, key) {
            console.log("UPLOADED ", path)
          })
        }

        // sync js
        uploadDir( 'public/build', 'build', true)

        // sync images
        uploadDir( 'public/images', 'images')

      })
    }
  )
}

console.log("BUILDING FOR " + (is_dev ? "DEVELOPMENT" : "PRODUCTION"))

module.exports = config<commit_msg>handle common errors better<commit_after>///////////
// Webpack.config.js
//
// Uses Webpack to build our javascript assets. Creates source maps
// as well.
//
// All considerit servers serve javascript compiled through this
// script, both dev and production.
//
// In development, you can have Webpack watch for changes so that the
// compilation is automatic:
//    > node_modules/webpack/bin/webpack.js --progress --colors --watch
//    (shortcut:   >  bin/webpack )
//
// To compile for production, set the environment variable
//      BUILD_PRODUCTION
//
// In production, if we're using a non-local asset host like Cloudfront,
// this will also upload the built javascript (and images) to the configured
// host.
//
// This process replaces Rails' asset pipeline.

///////////////////////
// Input
//
// These variables are pulled out because they're most likely to be configured
// as part of writing an application.

/////
// Global variables
// These variables will be made global on Window for use in all js files
global_variables = {
  '_': "vendor/underscore",
  $: "vendor/jquery",
  jQuery: "vendor/jquery",
  React: "vendor/react"
}

/////
// Entry points
// These are the different entry points to the application that will be
// compiled. All required files starting from the entry point will be compiled
// into the respective build.
entry_points = {
  franklin: './@client/franklin.coffee',
  saas_landing_page: './@client/saas_landing_page.coffee'
}

////////////////////////////////////////
// Innards

var webpack = require('webpack'),
    path = require('path'),
    is_dev = !JSON.parse(process.env.BUILD_PRODUCTION || 'false'),
    directory = __dirname;

config = {

  debug : is_dev,
  displayErrorDetails : true,
  outputPathinfo : true,
  devtool : is_dev ? 'eval' : 'sourcemap',

  entry: entry_points,

  // We output build javascript to public/build.
  // A cache-busting digest is appended when we're in production
  output: {
    path: './public/build',
    filename: is_dev ? "[name].js" : "[name].[chunkhash].js"
  },

  module: {

    // Enables compilation of coffee into java
    loaders: [
      { test: /\.coffee$/, loader: 'coffee-loader' },
    ],

    // Files that have already been compiled with webpack
    // or something like browserify can be skipped. If they're
    // not, they can sometimes cause Webpack problems...
    noParse: [
      /react\.js$/,
      /jquery\.js$/,
      /quill\.js$/
    ]
  },

  resolve: {
    root: [directory, '@client'].join('/'),
    extensions: ['', '.js', '.json', '.coffee']
           // don't have to specify .js etc in requires statements
  },

  plugins: [

    ///////////////
    // DefinePlugin
    //
    // Each instance of a key will be replaced with the
    // value in the build.
    new webpack.DefinePlugin({
      __DEV__: is_dev,
      __PRODUCTION__: !is_dev
    }),

    ///////////////
    // ProvidePlugin
    //
    // Makes these variables global across all javascript files
    new webpack.ProvidePlugin(global_variables),

    ////////////
    // Creates a public/build/manifest.json file that maps from
    // each entry point to the compiled version of it. Useful in
    // particular when the compiled filename includes a digest
    function() {
      this.plugin("done", function(stats) {
        manifest = {}

        for (var prop in config.entry){
          if(prop){
            file = stats.toJson().assetsByChunkName[prop]
            if (file && typeof file !== 'string')
              file = file[0]

            if (file) {
              manifest[prop] = path.join("build", file)
            } else {
              console.log("BAD FILE", prop, stats.toJson().assetsByChunkName)
            }
          }
        }
        require("fs").writeFileSync(
          path.join(directory, "public", "build", "manifest.json"),
          JSON.stringify(manifest));
      });
    }
  ]
}

/////
// Additional work when we're compiling for production
if(!is_dev){

  var s3 = require('s3'),
      YAML = require('yamljs'),
      CompressionPlugin = require("compression-webpack-plugin")

  config.plugins.push(

    //////
    // Uglify
    // Further compression. Unexpectedly cuts gzipped main js file size in half.
    // This is the slowest part of the process of building.
    new webpack.optimize.UglifyJsPlugin(),

    new webpack.optimize.OccurenceOrderPlugin(),

    //////
    // Compression
    // Gzip the js/sourcemaps coming through the pipeline. Note that the file
    // extension doesn't change. Web servers need to have Content-Encoding gzip
    // set for these files.
    new CompressionPlugin({
        asset: "{file}",
        algorithm: "gzip"
    }),

    /////////
    // Upload to s3 (and as a consequence, Cloudfront)
    //
    // Finally we'll upload these compiled js/source maps to s3, along with
    // syncing the public/images directory.
    function() {
      this.plugin("done", function(stats) {

        local = YAML.load('config/local_environment.yml').default
        if (!local.aws) return // do nothing if aws isn't configured

        var s3_client = s3.createClient({
            s3Options : {
              accessKeyId: local.aws.access_key_id,
              secretAccessKey: local.aws.secret_access_key,
            }})

        // Syncs a directory from this host to s3.
        // set is_gzipped if you want to set the Content-Encoding to gzip for all
        // files in this directory.
        var uploadDir = function(src, dest, is_gzipped) {

          s3_params = {
            Bucket: local.aws.s3_bucket,
            Prefix: dest,
            Expires: new Date(new Date().setYear(new Date().getFullYear() + 1)),
            CacheControl: 'public, max-age=31557600'
          }

          if (is_gzipped)
            s3_params.ContentEncoding = 'gzip'

          var uploader = s3_client.uploadDir({
            localDir: src,
            deleteRemoved: false, // remove s3 objects that lack
                                  // a corresponding local file.
            s3Params: s3_params
          })

          uploader.on('error', function(err) {
            console.error("unable to sync:", err.stack)
          })
          uploader.on('end', function() {
            console.log("done uploading")
          })
          uploader.on('fileUploadEnd', function(path, key) {
            console.log("UPLOADED ", path)
          })
        }

        // sync js
        uploadDir( 'public/build', 'build', true)

        // sync images
        uploadDir( 'public/images', 'images')

      })
    }
  )
}

console.log("BUILDING FOR " + (is_dev ? "DEVELOPMENT" : "PRODUCTION"))

module.exports = config<|endoftext|>


<commit_before>import macro from 'vtk.js/Sources/macro';
import vtkOpenGLHardwareSelector from 'vtk.js/Sources/Rendering/OpenGL/HardwareSelector';
import { FieldAssociations } from 'vtk.js/Sources/Common/DataModel/DataSet/Constants';
import Constants from 'vtk.js/Sources/Widgets/Core/WidgetManager/Constants';
import vtkSVGRepresentation from 'vtk.js/Sources/Widgets/SVG/SVGRepresentation';

import { diff } from './vdom';

const { ViewTypes, RenderingTypes, CaptureOn } = Constants;
const { vtkErrorMacro } = macro;
const { createSvgElement, createSvgDomElement } = vtkSVGRepresentation;

let viewIdCount = 1;

// ----------------------------------------------------------------------------
// Helper
// ----------------------------------------------------------------------------

export function extractRenderingComponents(renderer) {
  const camera = renderer.getActiveCamera();
  const renderWindow = renderer.getRenderWindow();
  const interactor = renderWindow.getInteractor();
  const openGLRenderWindow = interactor.getView();
  return { renderer, renderWindow, interactor, openGLRenderWindow, camera };
}

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

function createSvgRoot(id) {
  const wrapper = document.createElement('div');
  wrapper.setAttribute(
    'style',
    'position: absolute; top: 0; left: 0; width: 100%; height: 100%;'
  );

  const svgRoot = createSvgDomElement('svg');
  svgRoot.setAttribute('style', 'width: 100%; height: 100%;');
  svgRoot.setAttribute('version', '1.1');
  svgRoot.setAttribute('baseProfile', 'full');

  wrapper.appendChild(svgRoot);

  return { svgWrapper: wrapper, svgRoot };
}

// ----------------------------------------------------------------------------
// vtkWidgetManager methods
// ----------------------------------------------------------------------------

function vtkWidgetManager(publicAPI, model) {
  if (!model.viewId) {
    model.viewId = `view-${viewIdCount++}`;
  }
  model.classHierarchy.push('vtkWidgetManager');
  const propsWeakMap = new WeakMap();
  const widgetToSvgMap = new WeakMap();
  const svgVTrees = new WeakMap();
  const subscriptions = [];

  // --------------------------------------------------------------------------
  // Internal variable
  // --------------------------------------------------------------------------

  model.selector = vtkOpenGLHardwareSelector.newInstance();
  model.selector.setFieldAssociation(
    FieldAssociations.FIELD_ASSOCIATION_POINTS
  );

  const svgContainers = createSvgRoot(model.viewId);
  model.svgWrapper = svgContainers.svgWrapper;
  model.svgRoot = svgContainers.svgRoot;

  // --------------------------------------------------------------------------
  // API internal
  // --------------------------------------------------------------------------

  function updateWidgetWeakMap(widget) {
    const representations = widget.getRepresentations();
    for (let i = 0; i < representations.length; i++) {
      const representation = representations[i];
      const origin = { widget, representation };
      const actors = representation.getActors();
      for (let j = 0; j < actors.length; j++) {
        const actor = actors[j];
        propsWeakMap.set(actor, origin);
      }
    }
  }

  function getViewWidget(widget) {
    return (
      widget &&
      (widget.isA('vtkAbstractWidget')
        ? widget
        : widget.getWidgetForView({ viewId: model.viewId }))
    );
  }

  // --------------------------------------------------------------------------
  // internal SVG API
  // --------------------------------------------------------------------------

  function enableSvgLayer() {
    const container = model.openGLRenderWindow.getReferenceByName('el');
    const canvas = model.openGLRenderWindow.getCanvas();
    container.insertBefore(model.svgWrapper, canvas.nextSibling);
  }

  function disableSvgLayer() {
    const container = model.openGLRenderWindow.getReferenceByName('el');
    container.removeChild(model.svgWrapper);
  }

  function removeFromSvgLayer(viewWidget) {
    const group = widgetToSvgMap.get(viewWidget);
    if (group) {
      widgetToSvgMap.delete(viewWidget);
      svgVTrees.delete(viewWidget);
      model.svgRoot.removeChild(group);
    }
  }

  function updateSvg() {
    if (model.useSvgLayer) {
      const [cwidth, cheight] = model.openGLRenderWindow.getSize();
      const ratio = window.devicePixelRatio || 1;
      const bwidth = cwidth / ratio;
      const bheight = cheight / ratio;
      const viewBox = `0 0 ${cwidth} ${cheight}`;
      model.svgRoot.setAttribute('width', bwidth);
      model.svgRoot.setAttribute('height', bheight);
      model.svgRoot.setAttribute('viewBox', viewBox);

      for (let i = 0; i < model.widgets.length; i++) {
        const widget = model.widgets[i];
        const svgReps = widget
          .getRepresentations()
          .filter((r) => r.isA('vtkSVGRepresentation'));

        if (widget.getVisibility() && svgReps.length) {
          const pendingContent = svgReps
            .filter((r) => r.getVisibility())
            .map((r) => r.render());
          Promise.all(pendingContent).then((vnodes) => {
            const oldVTree = svgVTrees.get(widget);
            const newVTree = createSvgElement('g');
            for (let ni = 0; ni < vnodes.length; ni++) {
              newVTree.appendChild(vnodes[ni]);
            }

            const widgetGroup = widgetToSvgMap.get(widget);
            let node = widgetGroup;

            const patchFns = diff(oldVTree, newVTree);
            for (let j = 0; j < patchFns.length; j++) {
              node = patchFns[j](node);
            }

            if (!widgetGroup && node) {
              // add
              model.svgRoot.appendChild(node);
              widgetToSvgMap.set(widget, node);
            } else if (widgetGroup && !node) {
              // delete
              widgetGroup.remove();
              widgetToSvgMap.delete(widget);
            }

            svgVTrees.set(widget, newVTree);
          });
        } else {
          const g = widgetToSvgMap.get(widget);
          if (g) {
            g.innerHTML = '';
          }
        }
      }
    }
  }

  // --------------------------------------------------------------------------
  // API public
  // --------------------------------------------------------------------------

  function updateWidgetForRender(w) {
    w.updateRepresentationForRender(model.renderingType);
  }

  publicAPI.enablePicking = () => {
    model.pickingEnabled = true;

    model.renderingType = RenderingTypes.PICKING_BUFFER;
    model.widgets.forEach(updateWidgetForRender);

    if (model.captureOn === CaptureOn.MOUSE_RELEASE) {
      const [w, h] = model.openGLRenderWindow.getSize();
      model.selector.setArea(0, 0, w, h);
      model.selector.releasePixBuffers();
      model.pickingAvailable = model.selector.captureBuffers();
      model.previousSelectedData = null;
    } else {
      model.pickingAvailable = true;
    }

    model.renderingType = RenderingTypes.FRONT_BUFFER;
    model.widgets.forEach(updateWidgetForRender);

    publicAPI.modified();
  };

  publicAPI.disablePicking = () => {
    model.pickingEnabled = false;
    model.pickingAvailable = false;
  };

  publicAPI.setRenderer = (renderer) => {
    Object.assign(model, extractRenderingComponents(renderer));
    while (subscriptions.length) {
      subscriptions.pop().unsubscribe();
    }

    model.selector.attach(model.openGLRenderWindow, model.renderer);

    subscriptions.push(model.interactor.onRenderEvent(updateSvg));

    subscriptions.push(
      model.interactor.onStartAnimation(() => {
        model.isAnimating = true;
      })
    );
    subscriptions.push(
      model.interactor.onEndAnimation(() => {
        model.isAnimating = false;
        if (model.pickingEnabled) {
          publicAPI.enablePicking();
        }
      })
    );

    subscriptions.push(
      model.interactor.onMouseMove(({ position }) => {
        if (model.isAnimating || !model.pickingAvailable) {
          return;
        }
        publicAPI.updateSelectionFromXY(position.x, position.y);
        const {
          requestCount,
          selectedState,
          representation,
          widget,
        } = publicAPI.getSelectedData();

        if (requestCount) {
          // Call activate only once
          return;
        }

        // Default cursor behavior
        model.openGLRenderWindow.setCursor(widget ? 'pointer' : 'default');

        if (model.widgetInFocus === widget && widget.hasFocus()) {
          widget.activateHandle({ selectedState, representation });
          // Ken FIXME
          model.interactor.render();
          model.interactor.render();
        } else {
          for (let i = 0; i < model.widgets.length; i++) {
            const w = model.widgets[i];
            if (w === widget && w.getPickable()) {
              w.activateHandle({ selectedState, representation });
              model.activeWidget = w;
            } else {
              w.deactivateAllHandles();
            }
          }
          // Ken FIXME
          model.interactor.render();
          model.interactor.render();
        }
      })
    );

    publicAPI.modified();

    if (model.pickingEnabled) {
      publicAPI.enablePicking();
    }

    if (model.useSvgLayer) {
      enableSvgLayer();
    }
  };

  publicAPI.addWidget = (widget, viewType, initialValues) => {
    if (!model.renderer) {
      vtkErrorMacro(
        'Widget manager MUST BE link to a view before registering widgets'
      );
      return null;
    }
    const { viewId, renderer } = model;
    const w = widget.getWidgetForView({
      viewId,
      renderer,
      viewType: viewType || ViewTypes.DEFAULT,
      initialValues,
    });

    if (model.widgets.indexOf(w) === -1) {
      model.widgets.push(w);
      w.setWidgetManager(publicAPI);
      updateWidgetWeakMap(w);

      // Register to renderer
      model.renderer.addActor(w);

      publicAPI.modified();
    }

    return w;
  };

  publicAPI.removeWidget = (widget) => {
    const viewWidget = getViewWidget(widget);
    const index = model.widgets.indexOf(viewWidget);
    if (index !== -1) {
      model.widgets.splice(index, 1);
      model.renderer.removeActor(viewWidget);
      model.renderer
        .getRenderWindow()
        .getInteractor()
        .render();
      publicAPI.enablePicking();

      removeFromSvgLayer(viewWidget);

      if (model.widgetInFocus === viewWidget) {
        publicAPI.releaseFocus();
      }

      // free internal model + unregister it from its parent
      viewWidget.delete();
    }
  };

  publicAPI.updateSelectionFromXY = (x, y) => {
    let pickingAvailable = model.pickingAvailable;

    if (model.captureOn === CaptureOn.MOUSE_MOVE) {
      model.renderingType = RenderingTypes.PICKING_BUFFER;
      model.widgets.forEach(updateWidgetForRender);

      model.selector.setArea(x, y, x, y);
      model.selector.releasePixBuffers();
      pickingAvailable = model.selector.captureBuffers();
      model.previousSelectedData = null;

      model.renderingType = RenderingTypes.FRONT_BUFFER;
      model.widgets.forEach(updateWidgetForRender);
    }

    if (pickingAvailable) {
      model.selections = model.selector.generateSelection(x, y, x, y);
    }
  };

  publicAPI.updateSelectionFromMouseEvent = (event) => {
    const { pageX, pageY } = event;
    const {
      top,
      left,
      height,
    } = model.openGLRenderWindow.getCanvas().getBoundingClientRect();
    const x = pageX - left;
    const y = height - (pageY - top);
    publicAPI.updateSelectionFromXY(x, y);
  };

  publicAPI.getSelectedData = () => {
    if (!model.selections || !model.selections.length) {
      model.previousSelectedData = null;
      return {};
    }
    const { propID, compositeID, prop } = model.selections[0].getProperties();
    if (
      model.previousSelectedData &&
      model.previousSelectedData.prop === prop &&
      model.previousSelectedData.compositeID === compositeID
    ) {
      model.previousSelectedData.requestCount++;
      return model.previousSelectedData;
    }

    if (!propsWeakMap.has(prop)) {
      return {};
    }

    const { widget, representation } = propsWeakMap.get(prop);
    if (widget && representation) {
      const selectedState = representation.getSelectedState(prop, compositeID);
      model.previousSelectedData = {
        requestCount: 0,
        propID,
        compositeID,
        prop,
        widget,
        representation,
        selectedState,
      };
      return model.previousSelectedData;
    }
    model.previousSelectedData = null;
    return {};
  };

  publicAPI.grabFocus = (widget) => {
    const viewWidget = getViewWidget(widget);
    if (model.widgetInFocus && model.widgetInFocus !== viewWidget) {
      model.widgetInFocus.loseFocus();
    }
    model.widgetInFocus = viewWidget;
    if (model.widgetInFocus) {
      model.widgetInFocus.grabFocus();
    }
  };

  publicAPI.releaseFocus = () => publicAPI.grabFocus(null);

  publicAPI.setUseSvgLayer = (useSvgLayer) => {
    if (useSvgLayer !== model.useSvgLayer) {
      model.useSvgLayer = useSvgLayer;

      if (useSvgLayer) {
        if (model.renderer) {
          enableSvgLayer();
          // force a render so svg widgets can be drawn
          updateSvg();
        } else {
          disableSvgLayer();
        }
      }

      return true;
    }
    return false;
  };
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  viewId: null,
  widgets: [],
  renderer: [],
  viewType: ViewTypes.DEFAULT,
  pickingAvailable: false,
  isAnimating: false,
  pickingEnabled: true,
  selections: null,
  previousSelectedData: null,
  widgetInFocus: null,
  useSvgLayer: true,
  captureOn: CaptureOn.MOUSE_RELEASE,
};

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

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  macro.obj(publicAPI, model);
  macro.setGet(publicAPI, model, [
    'captureOn',
    { type: 'enum', name: 'viewType', enum: ViewTypes },
  ]);
  macro.get(publicAPI, model, [
    'selections',
    'widgets',
    'viewId',
    'pickingEnabled',
    'useSvgLayer',
  ]);

  // Object specific methods
  vtkWidgetManager(publicAPI, model);
}

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

export const newInstance = macro.newInstance(extend, 'vtkWidgetManager');

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

export default { newInstance, extend, Constants };
<commit_msg>fix(WidgetManager): Incorrect handling of widget visibility with vdom<commit_after>import macro from 'vtk.js/Sources/macro';
import vtkOpenGLHardwareSelector from 'vtk.js/Sources/Rendering/OpenGL/HardwareSelector';
import { FieldAssociations } from 'vtk.js/Sources/Common/DataModel/DataSet/Constants';
import Constants from 'vtk.js/Sources/Widgets/Core/WidgetManager/Constants';
import vtkSVGRepresentation from 'vtk.js/Sources/Widgets/SVG/SVGRepresentation';

import { diff } from './vdom';

const { ViewTypes, RenderingTypes, CaptureOn } = Constants;
const { vtkErrorMacro } = macro;
const { createSvgElement, createSvgDomElement } = vtkSVGRepresentation;

let viewIdCount = 1;

// ----------------------------------------------------------------------------
// Helper
// ----------------------------------------------------------------------------

export function extractRenderingComponents(renderer) {
  const camera = renderer.getActiveCamera();
  const renderWindow = renderer.getRenderWindow();
  const interactor = renderWindow.getInteractor();
  const openGLRenderWindow = interactor.getView();
  return { renderer, renderWindow, interactor, openGLRenderWindow, camera };
}

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

function createSvgRoot(id) {
  const wrapper = document.createElement('div');
  wrapper.setAttribute(
    'style',
    'position: absolute; top: 0; left: 0; width: 100%; height: 100%;'
  );

  const svgRoot = createSvgDomElement('svg');
  svgRoot.setAttribute('style', 'width: 100%; height: 100%;');
  svgRoot.setAttribute('version', '1.1');
  svgRoot.setAttribute('baseProfile', 'full');

  wrapper.appendChild(svgRoot);

  return { svgWrapper: wrapper, svgRoot };
}

// ----------------------------------------------------------------------------
// vtkWidgetManager methods
// ----------------------------------------------------------------------------

function vtkWidgetManager(publicAPI, model) {
  if (!model.viewId) {
    model.viewId = `view-${viewIdCount++}`;
  }
  model.classHierarchy.push('vtkWidgetManager');
  const propsWeakMap = new WeakMap();
  const widgetToSvgMap = new WeakMap();
  const svgVTrees = new WeakMap();
  const subscriptions = [];

  // --------------------------------------------------------------------------
  // Internal variable
  // --------------------------------------------------------------------------

  model.selector = vtkOpenGLHardwareSelector.newInstance();
  model.selector.setFieldAssociation(
    FieldAssociations.FIELD_ASSOCIATION_POINTS
  );

  const svgContainers = createSvgRoot(model.viewId);
  model.svgWrapper = svgContainers.svgWrapper;
  model.svgRoot = svgContainers.svgRoot;

  // --------------------------------------------------------------------------
  // API internal
  // --------------------------------------------------------------------------

  function updateWidgetWeakMap(widget) {
    const representations = widget.getRepresentations();
    for (let i = 0; i < representations.length; i++) {
      const representation = representations[i];
      const origin = { widget, representation };
      const actors = representation.getActors();
      for (let j = 0; j < actors.length; j++) {
        const actor = actors[j];
        propsWeakMap.set(actor, origin);
      }
    }
  }

  function getViewWidget(widget) {
    return (
      widget &&
      (widget.isA('vtkAbstractWidget')
        ? widget
        : widget.getWidgetForView({ viewId: model.viewId }))
    );
  }

  // --------------------------------------------------------------------------
  // internal SVG API
  // --------------------------------------------------------------------------

  function enableSvgLayer() {
    const container = model.openGLRenderWindow.getReferenceByName('el');
    const canvas = model.openGLRenderWindow.getCanvas();
    container.insertBefore(model.svgWrapper, canvas.nextSibling);
  }

  function disableSvgLayer() {
    const container = model.openGLRenderWindow.getReferenceByName('el');
    container.removeChild(model.svgWrapper);
  }

  function removeFromSvgLayer(viewWidget) {
    const group = widgetToSvgMap.get(viewWidget);
    if (group) {
      widgetToSvgMap.delete(viewWidget);
      svgVTrees.delete(viewWidget);
      model.svgRoot.removeChild(group);
    }
  }

  function updateSvg() {
    if (model.useSvgLayer) {
      const [cwidth, cheight] = model.openGLRenderWindow.getSize();
      const ratio = window.devicePixelRatio || 1;
      const bwidth = cwidth / ratio;
      const bheight = cheight / ratio;
      const viewBox = `0 0 ${cwidth} ${cheight}`;
      model.svgRoot.setAttribute('width', bwidth);
      model.svgRoot.setAttribute('height', bheight);
      model.svgRoot.setAttribute('viewBox', viewBox);

      for (let i = 0; i < model.widgets.length; i++) {
        const widget = model.widgets[i];
        const svgReps = widget
          .getRepresentations()
          .filter((r) => r.isA('vtkSVGRepresentation'));

        let pendingContent = [];
        if (widget.getVisibility()) {
          pendingContent = svgReps
            .filter((r) => r.getVisibility())
            .map((r) => r.render());
        }

        Promise.all(pendingContent).then((vnodes) => {
          const oldVTree = svgVTrees.get(widget);
          const newVTree = createSvgElement('g');
          for (let ni = 0; ni < vnodes.length; ni++) {
            newVTree.appendChild(vnodes[ni]);
          }

          const widgetGroup = widgetToSvgMap.get(widget);
          let node = widgetGroup;

          const patchFns = diff(oldVTree, newVTree);
          for (let j = 0; j < patchFns.length; j++) {
            node = patchFns[j](node);
          }

          if (!widgetGroup && node) {
            // add
            model.svgRoot.appendChild(node);
            widgetToSvgMap.set(widget, node);
          } else if (widgetGroup && !node) {
            // delete
            widgetGroup.remove();
            widgetToSvgMap.delete(widget);
          }

          svgVTrees.set(widget, newVTree);
        });
      }
    }
  }

  // --------------------------------------------------------------------------
  // API public
  // --------------------------------------------------------------------------

  function updateWidgetForRender(w) {
    w.updateRepresentationForRender(model.renderingType);
  }

  publicAPI.enablePicking = () => {
    model.pickingEnabled = true;

    model.renderingType = RenderingTypes.PICKING_BUFFER;
    model.widgets.forEach(updateWidgetForRender);

    if (model.captureOn === CaptureOn.MOUSE_RELEASE) {
      const [w, h] = model.openGLRenderWindow.getSize();
      model.selector.setArea(0, 0, w, h);
      model.selector.releasePixBuffers();
      model.pickingAvailable = model.selector.captureBuffers();
      model.previousSelectedData = null;
    } else {
      model.pickingAvailable = true;
    }

    model.renderingType = RenderingTypes.FRONT_BUFFER;
    model.widgets.forEach(updateWidgetForRender);

    publicAPI.modified();
  };

  publicAPI.disablePicking = () => {
    model.pickingEnabled = false;
    model.pickingAvailable = false;
  };

  publicAPI.setRenderer = (renderer) => {
    Object.assign(model, extractRenderingComponents(renderer));
    while (subscriptions.length) {
      subscriptions.pop().unsubscribe();
    }

    model.selector.attach(model.openGLRenderWindow, model.renderer);

    subscriptions.push(model.interactor.onRenderEvent(updateSvg));

    subscriptions.push(
      model.interactor.onStartAnimation(() => {
        model.isAnimating = true;
      })
    );
    subscriptions.push(
      model.interactor.onEndAnimation(() => {
        model.isAnimating = false;
        if (model.pickingEnabled) {
          publicAPI.enablePicking();
        }
      })
    );

    subscriptions.push(
      model.interactor.onMouseMove(({ position }) => {
        if (model.isAnimating || !model.pickingAvailable) {
          return;
        }
        publicAPI.updateSelectionFromXY(position.x, position.y);
        const {
          requestCount,
          selectedState,
          representation,
          widget,
        } = publicAPI.getSelectedData();

        if (requestCount) {
          // Call activate only once
          return;
        }

        // Default cursor behavior
        model.openGLRenderWindow.setCursor(widget ? 'pointer' : 'default');

        if (model.widgetInFocus === widget && widget.hasFocus()) {
          widget.activateHandle({ selectedState, representation });
          // Ken FIXME
          model.interactor.render();
          model.interactor.render();
        } else {
          for (let i = 0; i < model.widgets.length; i++) {
            const w = model.widgets[i];
            if (w === widget && w.getPickable()) {
              w.activateHandle({ selectedState, representation });
              model.activeWidget = w;
            } else {
              w.deactivateAllHandles();
            }
          }
          // Ken FIXME
          model.interactor.render();
          model.interactor.render();
        }
      })
    );

    publicAPI.modified();

    if (model.pickingEnabled) {
      publicAPI.enablePicking();
    }

    if (model.useSvgLayer) {
      enableSvgLayer();
    }
  };

  publicAPI.addWidget = (widget, viewType, initialValues) => {
    if (!model.renderer) {
      vtkErrorMacro(
        'Widget manager MUST BE link to a view before registering widgets'
      );
      return null;
    }
    const { viewId, renderer } = model;
    const w = widget.getWidgetForView({
      viewId,
      renderer,
      viewType: viewType || ViewTypes.DEFAULT,
      initialValues,
    });

    if (model.widgets.indexOf(w) === -1) {
      model.widgets.push(w);
      w.setWidgetManager(publicAPI);
      updateWidgetWeakMap(w);

      // Register to renderer
      model.renderer.addActor(w);

      publicAPI.modified();
    }

    return w;
  };

  publicAPI.removeWidget = (widget) => {
    const viewWidget = getViewWidget(widget);
    const index = model.widgets.indexOf(viewWidget);
    if (index !== -1) {
      model.widgets.splice(index, 1);
      model.renderer.removeActor(viewWidget);
      model.renderer
        .getRenderWindow()
        .getInteractor()
        .render();
      publicAPI.enablePicking();

      removeFromSvgLayer(viewWidget);

      if (model.widgetInFocus === viewWidget) {
        publicAPI.releaseFocus();
      }

      // free internal model + unregister it from its parent
      viewWidget.delete();
    }
  };

  publicAPI.updateSelectionFromXY = (x, y) => {
    let pickingAvailable = model.pickingAvailable;

    if (model.captureOn === CaptureOn.MOUSE_MOVE) {
      model.renderingType = RenderingTypes.PICKING_BUFFER;
      model.widgets.forEach(updateWidgetForRender);

      model.selector.setArea(x, y, x, y);
      model.selector.releasePixBuffers();
      pickingAvailable = model.selector.captureBuffers();
      model.previousSelectedData = null;

      model.renderingType = RenderingTypes.FRONT_BUFFER;
      model.widgets.forEach(updateWidgetForRender);
    }

    if (pickingAvailable) {
      model.selections = model.selector.generateSelection(x, y, x, y);
    }
  };

  publicAPI.updateSelectionFromMouseEvent = (event) => {
    const { pageX, pageY } = event;
    const {
      top,
      left,
      height,
    } = model.openGLRenderWindow.getCanvas().getBoundingClientRect();
    const x = pageX - left;
    const y = height - (pageY - top);
    publicAPI.updateSelectionFromXY(x, y);
  };

  publicAPI.getSelectedData = () => {
    if (!model.selections || !model.selections.length) {
      model.previousSelectedData = null;
      return {};
    }
    const { propID, compositeID, prop } = model.selections[0].getProperties();
    if (
      model.previousSelectedData &&
      model.previousSelectedData.prop === prop &&
      model.previousSelectedData.compositeID === compositeID
    ) {
      model.previousSelectedData.requestCount++;
      return model.previousSelectedData;
    }

    if (!propsWeakMap.has(prop)) {
      return {};
    }

    const { widget, representation } = propsWeakMap.get(prop);
    if (widget && representation) {
      const selectedState = representation.getSelectedState(prop, compositeID);
      model.previousSelectedData = {
        requestCount: 0,
        propID,
        compositeID,
        prop,
        widget,
        representation,
        selectedState,
      };
      return model.previousSelectedData;
    }
    model.previousSelectedData = null;
    return {};
  };

  publicAPI.grabFocus = (widget) => {
    const viewWidget = getViewWidget(widget);
    if (model.widgetInFocus && model.widgetInFocus !== viewWidget) {
      model.widgetInFocus.loseFocus();
    }
    model.widgetInFocus = viewWidget;
    if (model.widgetInFocus) {
      model.widgetInFocus.grabFocus();
    }
  };

  publicAPI.releaseFocus = () => publicAPI.grabFocus(null);

  publicAPI.setUseSvgLayer = (useSvgLayer) => {
    if (useSvgLayer !== model.useSvgLayer) {
      model.useSvgLayer = useSvgLayer;

      if (useSvgLayer) {
        if (model.renderer) {
          enableSvgLayer();
          // force a render so svg widgets can be drawn
          updateSvg();
        } else {
          disableSvgLayer();
        }
      }

      return true;
    }
    return false;
  };
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  viewId: null,
  widgets: [],
  renderer: [],
  viewType: ViewTypes.DEFAULT,
  pickingAvailable: false,
  isAnimating: false,
  pickingEnabled: true,
  selections: null,
  previousSelectedData: null,
  widgetInFocus: null,
  useSvgLayer: true,
  captureOn: CaptureOn.MOUSE_RELEASE,
};

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

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  macro.obj(publicAPI, model);
  macro.setGet(publicAPI, model, [
    'captureOn',
    { type: 'enum', name: 'viewType', enum: ViewTypes },
  ]);
  macro.get(publicAPI, model, [
    'selections',
    'widgets',
    'viewId',
    'pickingEnabled',
    'useSvgLayer',
  ]);

  // Object specific methods
  vtkWidgetManager(publicAPI, model);
}

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

export const newInstance = macro.newInstance(extend, 'vtkWidgetManager');

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

export default { newInstance, extend, Constants };
<|endoftext|>


<commit_before>module.exports = {
  title: 'Science Museum Group Collection',
  titlePage: 'Science Museum Group Collection',
  strap: 'Search our collection',
  intro: 'Explore over 250,000 objects and archives from the Science Museum, Museum of Science and Industry, National Science and Media Museum and National Railway Museum.',
  headerImages: [
    'handley.jpg',
    'textile-sign.jpg',
    'switches.jpg',
    '1878-0005_0004_A163_0002.jpg',
    'cd0634_030_100924_1988_242.jpg',
    'cd0274_008_070301.jpg'
  ],
  collectionGroup: [{
    title: 'See what’s on display',
    collection: [{
      type: 'collection',
      title: 'Science Museum',
      description: 'The home of human ingenuity, exploring stories of scientific achievement',
      figure: '/assets/img/home/collections/sciencemuseum.jpg',
      columns: 'small-6 medium-3',
      link: '/search/museum/science-museum'
    }, {
      type: 'collection',
      title: 'Museum of Science and Industry',
      figure: '/assets/img/home/collections/msi.jpg',
      description: 'The story of Manchester’s scientific and industrial past, present and future',
      columns: 'small-6 medium-3',
      link: '/search/museum/museum-of-science-and-industry'
    }, {
      type: 'collection',
      title: 'National Science and Media Museum',
      figure: '/assets/img/home/collections/nmm.jpg',
      description: 'The science and culture of light and sound technologies, and their impact on our lives',
      columns: 'small-6 medium-3',
      link: '/search/museum/national-media-museum'
    }, {
      type: 'collection',
      title: 'National Railway Museum',
      figure: '/assets/img/home/collections/nrm.jpg',
      description: 'Home to the largest collection of railway objects in the world',
      columns: 'small-6 medium-3',
      link: '/search/museum/national-railway-museum'
    }]
  }, {
    title: 'Themes from our collection',
    collection: [{
      type: 'collection',
      title: 'Information Age',
      description: 'Search across a snapshot of objects representing the development of the internet',
      figure: '/assets/img/home/collections/informationage.jpg',
      columns: 'small-6 medium-3',
      link: '/search/gallery/information-age-gallery%3A-web'
    }, {
      type: 'collection',
      title: 'Cinematography',
      figure: '/assets/img/home/collections/cinematography.jpg',
      description: 'Objects representing the history of cinema: from magic lanterns to digital technologies',
      columns: 'small-6 medium-3',
      link: '/search/categories/cinematography/images'
    }, {
      type: 'collection',
      title: 'The Babbage Papers',
      figure: '/assets/img/home/collections/babbage.jpg',
      description: 'The drawings and papers of mathematician and computer pioneer Charles Babbage',
      columns: 'small-6 medium-3',
      link: '/documents/aa110000003/the-babbage-papers'
    },
    {
      type: 'collection',
      title: 'Railway Photographic Collection',
      figure: '/assets/img/home/collections/railway-photographic-collections.jpg',
      description: 'Explore railway history—from technological to social change—using our photographic collection',
      columns: 'small-6 medium-3',
      link: '/search/categories/photographic-collections-(railway)/images'
    }
    /*
    {
      type: 'collection',
      title: 'Railway Posters',
      description: 'Explore our collection of historic railway posters',
      figure: '/assets/img/home/collections/railway-posters.jpg',
      columns: 'small-6 medium-3',
      link: '/search/images/categories/railway-posters,-notices-&-handbills'
    }
    */
    ]
  }]
};
<commit_msg>use unicode for emdash<commit_after>module.exports = {
  title: 'Science Museum Group Collection',
  titlePage: 'Science Museum Group Collection',
  strap: 'Search our collection',
  intro: 'Explore over 250,000 objects and archives from the Science Museum, Museum of Science and Industry, National Science and Media Museum and National Railway Museum.',
  headerImages: [
    'handley.jpg',
    'textile-sign.jpg',
    'switches.jpg',
    '1878-0005_0004_A163_0002.jpg',
    'cd0634_030_100924_1988_242.jpg',
    'cd0274_008_070301.jpg'
  ],
  collectionGroup: [{
    title: 'See what’s on display',
    collection: [{
      type: 'collection',
      title: 'Science Museum',
      description: 'The home of human ingenuity, exploring stories of scientific achievement',
      figure: '/assets/img/home/collections/sciencemuseum.jpg',
      columns: 'small-6 medium-3',
      link: '/search/museum/science-museum'
    }, {
      type: 'collection',
      title: 'Museum of Science and Industry',
      figure: '/assets/img/home/collections/msi.jpg',
      description: 'The story of Manchester’s scientific and industrial past, present and future',
      columns: 'small-6 medium-3',
      link: '/search/museum/museum-of-science-and-industry'
    }, {
      type: 'collection',
      title: 'National Science and Media Museum',
      figure: '/assets/img/home/collections/nmm.jpg',
      description: 'The science and culture of light and sound technologies, and their impact on our lives',
      columns: 'small-6 medium-3',
      link: '/search/museum/national-media-museum'
    }, {
      type: 'collection',
      title: 'National Railway Museum',
      figure: '/assets/img/home/collections/nrm.jpg',
      description: 'Home to the largest collection of railway objects in the world',
      columns: 'small-6 medium-3',
      link: '/search/museum/national-railway-museum'
    }]
  }, {
    title: 'Themes from our collection',
    collection: [{
      type: 'collection',
      title: 'Information Age',
      description: 'Search across a snapshot of objects representing the development of the internet',
      figure: '/assets/img/home/collections/informationage.jpg',
      columns: 'small-6 medium-3',
      link: '/search/gallery/information-age-gallery%3A-web'
    }, {
      type: 'collection',
      title: 'Cinematography',
      figure: '/assets/img/home/collections/cinematography.jpg',
      description: 'Objects representing the history of cinema: from magic lanterns to digital technologies',
      columns: 'small-6 medium-3',
      link: '/search/categories/cinematography/images'
    }, {
      type: 'collection',
      title: 'The Babbage Papers',
      figure: '/assets/img/home/collections/babbage.jpg',
      description: 'The drawings and papers of mathematician and computer pioneer Charles Babbage',
      columns: 'small-6 medium-3',
      link: '/documents/aa110000003/the-babbage-papers'
    },
    {
      type: 'collection',
      title: 'Railway Photographic Collection',
      figure: '/assets/img/home/collections/railway-photographic-collections.jpg',
      description: 'Explore railway history\u2014from technological to social change\u2014using our photographic collection',
      columns: 'small-6 medium-3',
      link: '/search/categories/photographic-collections-(railway)/images'
    }
    /*
    {
      type: 'collection',
      title: 'Railway Posters',
      description: 'Explore our collection of historic railway posters',
      figure: '/assets/img/home/collections/railway-posters.jpg',
      columns: 'small-6 medium-3',
      link: '/search/images/categories/railway-posters,-notices-&-handbills'
    }
    */
    ]
  }]
};
<|endoftext|>


<commit_before>/**
 * Javascript required to power the user defined forms.
 *
 * Rewritten and refactored from the prototype version FieldEditor.
 */
(function($) {
	$(document).ready(function() {

		/*--------------------- SUBMISSIONS ------------------------ */

		/**
		 * Delete a given Submission from the form, or all submissions
		 * we let the href of the delete link to do all the work for us
		 */

		$("#FormSubmissions .deleteSubmission").click(function() {
			var deletedSubmission = $(this);
			$.post($(this).attr('href'), function(data) {
				deletedSubmission.parents('div.report').fadeOut();
			});
			return false;
		});

		/*-------------------- FIELD EDITOR ----------------------- */

		/**
		 * Create a new instance of a field in the current form
		 * area. the type information should all be on this object
		 */

		$("div.FieldEditor .MenuHolder .action").live('click',function() {

			// if this form is readonly...
			if($("#Fields").hasClass('readonly')) return false;

			// Give the user some feedback
			statusMessage(ss.i18n._t('UserForms.ADDINGNEWFIELD', 'Adding New Field'));

			// variables
			var action = $("#Form_EditForm").attr("action") + '/field/Fields/addfield';
			var length = $(".FieldInfo").length + 1;
			var securityID = ($("#SecurityID").length > 0) ? '&SecurityID='+$("#SecurityID").attr("value") : '';
			var type = $(this).siblings("select").val();

			//send ajax request to the page
			$.ajax({
				type: "GET",
				url: action,
				data: 'NewID='+ length +"&Type="+ type + securityID,

				// create a new field
				success: function(msg){
					$('#Fields_fields').append(msg);
					statusMessage(ss.i18n._t('UserForms.ADDEDNEWFIELD', 'Added New Field'));

					//update the internal lists
					var name = $("#Fields_fields li.EditableFormField:last").attr("id").split(' ');

					$("#Fields_fields select.fieldOption").append("<option value='"+ name[2] +"'>New "+ name[2] + "</option>");
				},

				// error creating new field
				error: function(request, text, error) {
					statusMessage(ss.i18n._t('UserForms.ERRORCREATINGFIELD', 'Error Creating Field'));
				}
			});
			return false;
		});

		/**
		 * Upon renaming a field we should go through and rename all the
		 * fields in the select fields to use this new field title. We can
		 * just worry about the title text - don't mess around with the keys
		 */
		$('.EditableFormField .fieldInfo .text').live('change', function() {
			var value = $(this).val();
			var name = $(this).parents("li").attr("id").split(' ');
			$("#Fields_fields select.fieldOption option").each(function(i, domElement) {
				if($(domElement).val() == name[2]) {
					$(domElement).text(value);
				}
			});
		})
		/**
		 * Show the more options popdown. Or hide it if we
		 * currently have it open
		 */
		$(".EditableFormField .moreOptions").live('click',function() {

			var parentID = $(this).parents(".EditableFormField");
			if(parentID) {
				var extraOptions = parentID.children(".extraOptions");
				if(extraOptions) {
					if(extraOptions.hasClass('hidden')) {
						$(this).html(ss.i18n._t('UserForms.HIDEOPTIONS', 'Hide Options'));
						$(this).addClass("showing");
						extraOptions.removeClass('hidden').show();
					}
					else {
						$(this).html(ss.i18n._t('UserForms.SHOWOPTIONS', 'Show Options'));
						$(this).removeClass("showing");
						extraOptions.addClass('hidden').hide();
					}
				}
			}
			return false;
		});

		/**
		 * Delete a field from the user defined form
		 */
		$(".EditableFormField .delete").live('click', function() {
			// remove all the rules with relate to this field
			var text = $(this).parents("li").find(".fieldInfo .text").val();
			$("#Fields_fields .customRules select.fieldOption option").each(function(i, domElement) {
				if($(domElement).text() == text) {

					// check to see if this is selected. If it is then just remove the whole rule
					if($(domElement).parent('select.customRuleField').val() == $(domElement).val()) {
						$(domElement).parents('li.customRule').remove();
					}
					// otherwise remove the option
					else {
						$(domElement).remove();
					}
				}
			});
			$(this).parents(".EditableFormField").remove();
			return false;
		});

		/**
		 * Add a suboption to a radio field or to a dropdown box
		 * for example
		 */
		$(".EditableFormField .addableOption").live('click', function() {

			// Give the user some feedback
			statusMessage(ss.i18n._t('UserForms.ADDINGNEWFIELD', 'Adding New Option'));

			// variables
			var options = $(this).parent("li");
			var action = $("#Form_EditForm").attr("action") + '/field/Fields/addoptionfield';
			var parent = $(this).attr("rel");

			//send ajax request to the page
			$.ajax({
				type: "GET",
				url: action,
				data: 'Parent='+ parent,

				// create a new field
				success: function(msg){
					options.before(msg);
					statusMessage(ss.i18n._t('UserForms.ADDEDNEWFIELD', 'Added New Field'));
				},

				// error creating new field
				error: function(request, text, error) {
					statusMessage(ss.i18n._t('UserForms.ERRORCREATINGFIELD', 'Error Creating Field'));
				}
			});
			return false;
		});

		/**
		 * Delete a suboption such as an dropdown option or a
		 * checkbox field
		 */
		$(".EditableFormField .deleteOption").live('click', function() {
			// pass the deleted status onto the element
			$(this).parent("li").children("[type=text]").attr("value", "field-node-deleted");
			$(this).parent("li").hide();

			// Give the user some feedback
			statusMessage(ss.i18n._t('UserForms.REMOVINGOPTION', 'Removed Option'));
			return false;
		});

		/**
		 * Sort Fields in the Field List
		 */
		$("#Fields_fields").sortable({
  	 		handle : '.fieldHandler',
			cursor: 'pointer',
			items: 'li.EditableFormField',
			placeholder: 'removed-form-field',
			opacity: 0.6,
			revert: true,
			change : function (event, ui) {
				$("#Fields_fields").sortable('refreshPositions');
			},
	    	update : function (event, ui) {
	      		// get all the fields
				var sort = 1;
				$("li.EditableFormField").each(function() {
					$(this).find(".sortHidden").val(sort++);
				});
	    	}
		});

		/**
		 * Sort Options in a Field List - Such as options in a
		 * dropdown field.
		 */
		$(".editableOptions").live(function() {
			$(this).sortable({
				handle : '.handle',
				cursor: 'pointer',
				items: 'li',
				placeholder: 'removed-form-field',
				opacity: 0.6,
				revert: true,
				change : function (event, ui) {
					$(this).sortable('refreshPositions');
				},
		    	update : function (event, ui) {
		      		// get all the fields
					var sort = 1;
					$(".editableOptions li").each(function() {
						$(this).find(".sortOptionHidden").val(sort++);
					});
		    	}
			});
		});

		/**
		 * Custom Rules Interface
		 */
		$(".customRules .conditionOption").live('change', function(){
			var valueInput = $(this).siblings(".ruleValue");
			if($(this).val() != "" && $(this).val() != "IsBlank" && $(this).val() != "IsNotBlank") {
				valueInput.removeClass("hidden");
			}
			else {
				valueInput.addClass("hidden");
			}
		});

		/**
		 * Delete a custom rule
		 */
		$(".customRules .deleteCondition").live('click', function() {
			$(this).parent("li").fadeOut().remove();

			return false;
		});

		/**
		 * Adding a custom rule to a given form
		 */
		$(".customRules .addCondition").live('click', function() {

			// Give the user some feedback
			statusMessage(ss.i18n._t('UserForms.ADDINGNEWRULE', 'Adding New Rule'));

			// get the fields li which to duplicate
			var currentRules = $(this).parent("li").parent("ul");
			var defaultRule = currentRules.children("li.hidden:first");
			var newRule = defaultRule.clone();

			newRule.children(".customRuleField").each(function(i, domElement) {
				var currentName = domElement.name.split("][");
				currentName[3] = currentName[2];
				currentName[2] = currentRules.children().size() + 1;
				domElement.name = currentName.join("][");
			});

			// remove hidden tag
			newRule.removeClass("hidden");

			// update the fields dropdown
			newRule.children("select.fieldOption").empty();

			$("#Fields_fields li.EditableFormField").each(function (i, domElement) {
				var name = $(domElement).attr("id").split(' ');
				newRule.children("select.fieldOption").append("<option value='"+ name[2] + "'>"+ $(domElement).find(".text").val() + "</option>");
			});

			// append to the list
			currentRules.append(newRule);

			return false;
		});
	});
})
(jQuery);<commit_msg>BUGFIX: refresh sortable lists after adding elements<commit_after>/**
 * Javascript required to power the user defined forms.
 *
 * Rewritten and refactored from the prototype version FieldEditor.
 */
(function($) {
	$(document).ready(function() {

		/*--------------------- SUBMISSIONS ------------------------ */

		/**
		 * Delete a given Submission from the form, or all submissions
		 * we let the href of the delete link to do all the work for us
		 */

		$("#FormSubmissions .deleteSubmission").click(function() {
			var deletedSubmission = $(this);
			$.post($(this).attr('href'), function(data) {
				deletedSubmission.parents('div.report').fadeOut();
			});
			return false;
		});

		/*-------------------- FIELD EDITOR ----------------------- */

		/**
		 * Create a new instance of a field in the current form
		 * area. the type information should all be on this object
		 */

		$("div.FieldEditor .MenuHolder .action").live('click',function() {

			// if this form is readonly...
			if($("#Fields").hasClass('readonly')) return false;

			// Give the user some feedback
			statusMessage(ss.i18n._t('UserForms.ADDINGNEWFIELD', 'Adding New Field'));

			// variables
			var action = $("#Form_EditForm").attr("action") + '/field/Fields/addfield';
			var length = $(".FieldInfo").length + 1;
			var securityID = ($("#SecurityID").length > 0) ? '&SecurityID='+$("#SecurityID").attr("value") : '';
			var type = $(this).siblings("select").val();

			//send ajax request to the page
			$.ajax({
				type: "GET",
				url: action,
				data: 'NewID='+ length +"&Type="+ type + securityID,

				// create a new field
				success: function(msg){
					$('#Fields_fields').append(msg);
					statusMessage(ss.i18n._t('UserForms.ADDEDNEWFIELD', 'Added New Field'));

					//update the internal lists
					var name = $("#Fields_fields li.EditableFormField:last").attr("id").split(' ');

					$("#Fields_fields select.fieldOption").append("<option value='"+ name[2] +"'>New "+ name[2] + "</option>");
				},

				// error creating new field
				error: function(request, text, error) {
					statusMessage(ss.i18n._t('UserForms.ERRORCREATINGFIELD', 'Error Creating Field'));
				}
			});

			$("#Fields_fields").sortable("refresh");

			return false;
		});

		/**
		 * Upon renaming a field we should go through and rename all the
		 * fields in the select fields to use this new field title. We can
		 * just worry about the title text - don't mess around with the keys
		 */
		$('.EditableFormField .fieldInfo .text').live('change', function() {
			var value = $(this).val();
			var name = $(this).parents("li").attr("id").split(' ');
			$("#Fields_fields select.fieldOption option").each(function(i, domElement) {
				if($(domElement).val() == name[2]) {
					$(domElement).text(value);
				}
			});
		})
		/**
		 * Show the more options popdown. Or hide it if we
		 * currently have it open
		 */
		$(".EditableFormField .moreOptions").live('click',function() {
			var parentID = $(this).parents(".EditableFormField");
			if(parentID) {
				var extraOptions = parentID.children(".extraOptions");
				if(extraOptions) {
					if(extraOptions.hasClass('hidden')) {
						$(this).html(ss.i18n._t('UserForms.HIDEOPTIONS', 'Hide Options'));
						$(this).addClass("showing");
						extraOptions.removeClass('hidden').show();
					}
					else {
						$(this).html(ss.i18n._t('UserForms.SHOWOPTIONS', 'Show Options'));
						$(this).removeClass("showing");
						extraOptions.addClass('hidden').hide();
					}
				}
			}
			return false;
		});

		/**
		 * Delete a field from the user defined form
		 */
		$(".EditableFormField .delete").live('click', function() {
			// remove all the rules with relate to this field
			var text = $(this).parents("li").find(".fieldInfo .text").val();
			$("#Fields_fields .customRules select.fieldOption option").each(function(i, domElement) {
				if($(domElement).text() == text) {

					// check to see if this is selected. If it is then just remove the whole rule
					if($(domElement).parent('select.customRuleField').val() == $(domElement).val()) {
						$(domElement).parents('li.customRule').remove();
					}
					// otherwise remove the option
					else {
						$(domElement).remove();
					}
				}
			});

			$(this).parents(".EditableFormField").remove();

			return false;
		});

		/**
		 * Add a suboption to a radio field or to a dropdown box
		 * for example
		 */
		$(".EditableFormField .addableOption").live('click', function() {

			// Give the user some feedback
			statusMessage(ss.i18n._t('UserForms.ADDINGNEWFIELD', 'Adding New Option'));

			// variables
			var options = $(this).parent("li");
			var action = $("#Form_EditForm").attr("action") + '/field/Fields/addoptionfield';
			var parent = $(this).attr("rel");

			//send ajax request to the page
			$.ajax({
				type: "GET",
				url: action,
				data: 'Parent='+ parent,

				// create a new field
				success: function(msg){
					options.before(msg);
					statusMessage(ss.i18n._t('UserForms.ADDEDNEWFIELD', 'Added New Field'));
				},

				// error creating new field
				error: function(request, text, error) {
					statusMessage(ss.i18n._t('UserForms.ERRORCREATINGFIELD', 'Error Creating Field'));
				}
			});
			return false;
		});

		/**
		 * Delete a suboption such as an dropdown option or a
		 * checkbox field
		 */
		$(".EditableFormField .deleteOption").live('click', function() {
			// pass the deleted status onto the element
			$(this).parent("li").children("[type=text]").attr("value", "field-node-deleted");
			$(this).parent("li").hide();

			// Give the user some feedback
			statusMessage(ss.i18n._t('UserForms.REMOVINGOPTION', 'Removed Option'));
			return false;
		});

		/**
		 * Sort Fields in the Field List
		 */
		$("#Fields_fields").sortable({
  	 		handle : '.fieldHandler',
			cursor: 'pointer',
			items: 'li.EditableFormField',
			placeholder: 'removed-form-field',
			opacity: 0.6,
			revert: true,
			change : function (event, ui) {
				$("#Fields_fields").sortable('refreshPositions');
			},
	    	update : function (event, ui) {
	      		// get all the fields
				var sort = 1;
				$("li.EditableFormField").each(function() {
					$(this).find(".sortHidden").val(sort++);
				});
	    	}
		});

		/**
		 * Sort Options in a Field List - Such as options in a
		 * dropdown field.
		 */
		$(".editableOptions").sortable({
			handle : '.handle',
			cursor: 'pointer',
			items: 'li',
			placeholder: 'removed-form-field',
			opacity: 0.6,
			revert: true,
			change : function (event, ui) {
				$(this).sortable('refreshPositions');
			},
	    	update : function (event, ui) {
	      		// get all the fields
				var sort = 1;
				$(".editableOptions li").each(function() {
					$(this).find(".sortOptionHidden").val(sort++);
				});
	    	}
		});

		/**
		 * Custom Rules Interface
		 */
		$(".customRules .conditionOption").live('change', function(){
			var valueInput = $(this).siblings(".ruleValue");
			if($(this).val() != "" && $(this).val() != "IsBlank" && $(this).val() != "IsNotBlank") {
				valueInput.removeClass("hidden");
			}
			else {
				valueInput.addClass("hidden");
			}
		});

		/**
		 * Delete a custom rule
		 */
		$(".customRules .deleteCondition").live('click', function() {
			$(this).parent("li").fadeOut().remove();

			return false;
		});

		/**
		 * Adding a custom rule to a given form
		 */
		$(".customRules .addCondition").click(function() {
			// Give the user some feedback
			statusMessage(ss.i18n._t('UserForms.ADDINGNEWRULE', 'Adding New Rule'));

			// get the fields li which to duplicate
			var currentRules = $(this).parent("li").parent("ul");
			var defaultRule = currentRules.children("li.hidden:first");
			var newRule = defaultRule.clone();

			newRule.children(".customRuleField").each(function(i, domElement) {
				var currentName = domElement.name.split("][");
				currentName[3] = currentName[2];
				currentName[2] = currentRules.children().size() + 1;
				domElement.name = currentName.join("][");
			});

			// remove hidden tag
			newRule.removeClass("hidden");

			// update the fields dropdown
			newRule.children("select.fieldOption").empty();

			$("#Fields_fields li.EditableFormField").each(function (i, domElement) {
				var name = $(domElement).attr("id").split(' ');
				newRule.children("select.fieldOption").append("<option value='"+ name[2] + "'>"+ $(domElement).find(".text").val() + "</option>");
			});

			// append to the list
			currentRules.append(newRule);

			$(".editableOptions").sortable("refresh");

			return false;
		});
	});
})
(jQuery);<|endoftext|>