<commit_before>define(function (require){

    var BaseObject = require('BaseObject'),
        THREE = require('three'),
        fileManager = require('fileManager'),
        utils = require('utils');

    //  WORKER SHARED MESSAGES

    var WORKER = {
        INPUT:0,
        START:1,
        ADD_OBJECT:2,
        REMOVE_OBJECT:3,
        CREATE_CAR: 4,
        SIMULATION_DATA: 5
    };

    // SCALE
    var SCALE = 10;
    var PERSPECTIVE_CAMERA = false;

    return BaseObject.extend({

        // variables

        physicsWorker: null,
        bodies: null,
        scene: null,
        camera: null,
        spawners: null,
        vehicles: null,
        wheels: null,

        // functions

        init: function () {
            this.__init();

            this.bodies = [];
            this.spawners = [];
            this.vehicles = [];
            this.wheels = [];

            var scope = this;

            window.onkeydown = function (event) {

                var code = utils.getKeyCode(event.keyCode);

                scope.physicsWorker.postMessage({
                    type: WORKER.INPUT,
                    data: {
                        id: 0,
                        status: true,
                        code: code
                    }
                });
            };
            window.onkeyup = function (event) {

                var code = utils.getKeyCode(event.keyCode);

                scope.physicsWorker.postMessage({
                    type: WORKER.INPUT,
                    data: {
                        id: 0,
                        status: false,
                        code: code
                    }
                });
            };
        },
        generateFromSceneData: function (data) {
            // setup the physical world
            this.setupWorker();

            // create a new scene info from data
            var loader = new THREE.ObjectLoader();
            this.scene = loader.parse(data);

            // load camera
            this.loadCameraFromSceneData(this.scene);

            // load light
            this.loadLightFromSceneData(this.scene);

            // load static objects
            this.loadStaticObjectsFromSceneData(this.scene);

            // load spawn areas
            this.loadSpawnersFromSceneData(this.scene);

            // TODO THIS: vehicles have to be created at the start and
            // upon connection of new controllers

                // create a sample vehicle
                var playerData = {
                    id: 3,
                    vehicleID: 1
                };
                this.addPlayer(playerData);
            //
        },
        addPlayer: function (playerData) {

            var spawner = this.getFreeSpawner();
            var loader = new THREE.ObjectLoader();
            var chasis = loader.parse(fileManager.CHASIS[0]);

            chasis.position.copy(spawner.position);
            chasis.rotation.copy(spawner.rotation);

            var chasisData = this.serializeMesh(chasis);

            this.scene.add(chasis);
            this.bodies.push(chasis);

            chasis.castShadow = true;
            chasis.receiveShadow = true;

            // wheels

            for (var i = 0; i < 4; i++) {
                var wheel = loader.parse(fileManager.WHEELS[0]);
                this.scene.add(wheel);
                this.wheels.push(wheel);
            };

            var dx = 0.9;
            var dy = 0.4;
            var dzBack = 2;
            var dzFront = 2;

            var radius = 0.5;

            var data = {

                "vehicleID" : playerData.vehicleID,

                "chasisData": chasisData,

                "wheels" : [

                    {
                        "isFrontWheel"  : true,
                        "mirrored"      : false,

                        "wheelRadius"           : radius,
                        "suspensionRestLength"  : 0.25,

                        "connectionPoint"   : [ -dx, -dy, dzFront ],
                        "wheelDirection"    : [ 0, -1, 0 ],
                        "wheelAxle"         : [ -1, 0, 0 ],

                        "tuning" : {
                            "rollInfluence" : 0.1,
                        }
                    },

                    {
                        "isFrontWheel"  : true,
                        "mirrored"      : true,

                        "wheelRadius"           : radius,
                        "suspensionRestLength"  : 0.25,

                        "connectionPoint"   : [ dx, -dy, dzFront ],
                        "wheelDirection"    : [ 0, -1, 0 ],
                        "wheelAxle"         : [ -1, 0, 0 ],

                        "tuning" : {
                            "rollInfluence" : 0.1,
                        }

                    },

                    {
                        "isFrontWheel"  : false,
                        "mirrored"      : false,

                        "wheelRadius"           : radius,
                        "suspensionRestLength"  : 0.25,

                        "connectionPoint"   : [ -dx, -dy, -dzBack ],
                        "wheelDirection"    : [ 0, -1, 0 ],
                        "wheelAxle"         : [ -1, 0, 0 ]

                    },

                    {
                        "mirrored"      : true,
                        "isFrontWheel"  : false,

                        "wheelRadius"           : radius,
                        "suspensionRestLength"  : 0.25,

                        "connectionPoint"   : [ dx, -dy, -dzBack ],
                        "wheelDirection"    : [ 0, -1, 0 ],
                        "wheelAxle"         : [ -1, 0, 0 ]
                    }

                ],

                "maxSuspensionTravelCm" : 750.0,
                "maxSuspensionForce"    : 6000.0,
                "suspensionStiffness"   : 10.0,
                "suspensionCompression" : 20.83,
                "suspensionDamping"     : 0.88,
                "rollInfluence"         : 0.1,
                "frictionSlip"          : 1000

            }

            this.physicsWorker.postMessage({
                type: WORKER.CREATE_CAR,
                data: data
            });

            this.vehicles.push(playerData);
        },
        loadCameraFromSceneData: function (sceneData) {

            var w = window.innerWidth;
            var h = window.innerHeight;

            if (PERSPECTIVE_CAMERA) {
                this.camera = new THREE.PerspectiveCamera( 70, w / h, 1, 1500 );
            } else {
                this.camera = new THREE.OrthographicCamera( -w/2, w/2, h/2, -h/2, 1, 1500 );
            }

            var cameraData = sceneData.getObjectByName("CAMERA");
            var pos = cameraData.getObjectByName("POSITION");
            var target = cameraData.getObjectByName("TARGET");

            this.camera.position.copy(pos.position);
            this.camera.lookAt(target.position);

        },
        loadLightFromSceneData: function (sceneData) {

            var w = window.innerWidth;
            var h = window.innerHeight;

            // light

            var light = sceneData.getObjectByName("LIGHT", true);
            light.castShadow = true;

            light.shadowCameraNear = 100;
            light.shadowCameraFar = 1500;
            light.shadowCameraFov = 10;

            //light.shadowCameraVisible = true;

            light.shadowBias = 0.0001;
            light.shadowDarkness = 0.2;

            light.shadowMapWidth = w;
            light.shadowMapHeight = h;
        },
        loadStaticObjectsFromSceneData: function (sceneData) {
            // load objects from LEVEL layer
            var levelObject = sceneData.getObjectByName("LEVEL");
            var len = levelObject.children.length;

            for (var i = 0; i < len; i++) {
                var object = levelObject.children[i];

                object.castShadow = true;
                object.receiveShadow = true;

                this.bodies.push(object);    // logicaly
                this.createPhysicalObject(object);  // physicaly
            };
        },
        loadSpawnersFromSceneData: function (sceneData) {
            // load objects from SPAWNERS layer
            var spawnersObject = sceneData.getObjectByName("SPAWNERS");
            this.spawners = spawnersObject.children;
        },
        createPhysicalObject: function (mesh) {
            var serializedMesh = this.serializeMesh(mesh);

            this.physicsWorker.postMessage({
                type: WORKER.ADD_OBJECT,
                data: serializedMesh
            });

            return serializedMesh;
        },
        getFreeSpawner: function() {
            return utils.getRandom(this.spawners);
        },
        serializeMesh: function (mesh) {

            // serialize the mesh params to pass them to the
            // physics worker.

            var offset = 0;
            var object = [];

            offset = this.serializePosition(mesh, object, offset);
            offset = this.serializeRotation(mesh, object, offset);
            offset = this.serializeBoxSize(mesh, object, offset);

            object[offset] = mesh.userData.mass !== undefined ? mesh.userData.mass : 1;

            return object;
        },
        serializeVehicle: function (mesh, spawner) {

            // serialize the mesh params to pass them to the
            // physics worker.

            var offset = 0;
            var object = [];

            offset = this.serializePosition(spawner, object, offset);
            offset = this.serializeRotation(spawner, object, offset);
            offset = this.serializeBoxSize(mesh, object, offset);

            object[offset] = mesh.userData.mass !== undefined ? mesh.userData.mass : 1;

            return object;
        },
        serializeObject3D: function (mesh) {

            // serialize an Object 3d

            var offset = 0;
            var object = [];

            offset = this.serializePosition(mesh, object, offset);
            offset = this.serializeRotation(mesh, object, offset);

            return object;
        },
        serializePosition: function(obj, array, offset) {
            var origin = obj.position;
            array[offset] = origin.x / SCALE;
            array[offset+1] = origin.y / SCALE;
            array[offset+2] = origin.z / SCALE;

            return offset + 3;
        },
        serializeRotation: function(obj, array, offset) {
            var rotation = obj.quaternion;
            array[offset] = rotation.x;
            array[offset+1] = rotation.y;
            array[offset+2] = rotation.z;
            array[offset+3] = rotation.w;

            return offset + 4;
        },
        serializeBoxSize: function(obj, array, offset) {
            var size = obj.geometry.parameters;
            array[offset] = size.width / SCALE;
            array[offset+1] = size.height / SCALE;
            array[offset+2] = size.depth / SCALE;

            return offset + 3;
        },
        setupWorker: function () {
            // Worker
            this.physicsWorker = new Worker('./js/src/physics/worker.js');
            this.physicsWorker.onmessage = this.onmessage;
            this.physicsWorker.postMessage({
                type: WORKER.START
            });
        },
        onmessage: function (event) {
            var message = event.data;

            switch(message.type) {
                case WORKER.SIMULATION_DATA:
                    this.updateWorldWithSimulationData(message.data);
                    break;
                default:
            }
        },
        updateWorldWithSimulationData: function (data) {
            var offset = 0;

            offset = this.updateObjectFromData(data, offset, this.bodies);
            offset = this.updateObjectFromData(data, offset, this.wheels);
        },
        updateObjectFromData: function (data, offset, array) {
            //bodies
            for (var i = 0; i < array.length; i++) {
                var body = array[i];
                body.position.x = data[offset] * SCALE;
                body.position.y = data[offset+1] * SCALE;
                body.position.z = data[offset+2] * SCALE;

                body.quaternion.x = data[offset+3];
                body.quaternion.y = data[offset+4];
                body.quaternion.z = data[offset+5];
                body.quaternion.w = data[offset+6];

                offset += 7;
            }

            return offset;
        },
        dispose: function () {
            if (physicsWorker) physicsWorker.terminate();

            physicsWorker = null;
            this.vehicles = null;
            bodies = null;
            scene = null;
            camera = null;
            spawners = null;
            vehicles = null;
            wheels = null;

            this.__dispose();
        }
    });
});<commit_msg>fixed SCALE to vehicles<commit_after>define(function (require){

    var BaseObject = require('BaseObject'),
        THREE = require('three'),
        fileManager = require('fileManager'),
        utils = require('utils');

    //  WORKER SHARED MESSAGES

    var WORKER = {
        INPUT:0,
        START:1,
        ADD_OBJECT:2,
        REMOVE_OBJECT:3,
        CREATE_CAR: 4,
        SIMULATION_DATA: 5
    };

    // SCALE
    var SCALE = 25; // MORE SCALE -> MORE SPEED
    var PERSPECTIVE_CAMERA = false;

    return BaseObject.extend({

        // variables

        physicsWorker: null,
        bodies: null,
        scene: null,
        camera: null,
        spawners: null,
        vehicles: null,
        wheels: null,

        // functions

        init: function () {
            this.__init();

            this.bodies = [];
            this.spawners = [];
            this.vehicles = [];
            this.wheels = [];

            var scope = this;

            window.onkeydown = function (event) {

                var code = utils.getKeyCode(event.keyCode);

                scope.physicsWorker.postMessage({
                    type: WORKER.INPUT,
                    data: {
                        id: 0,
                        status: true,
                        code: code
                    }
                });
            };
            window.onkeyup = function (event) {

                var code = utils.getKeyCode(event.keyCode);

                scope.physicsWorker.postMessage({
                    type: WORKER.INPUT,
                    data: {
                        id: 0,
                        status: false,
                        code: code
                    }
                });
            };
        },
        generateFromSceneData: function (data) {
            // setup the physical world
            this.setupWorker();

            // create a new scene info from data
            var loader = new THREE.ObjectLoader();
            this.scene = loader.parse(data);

            // load camera
            this.loadCameraFromSceneData(this.scene);

            // load light
            this.loadLightFromSceneData(this.scene);

            // load static objects
            this.loadStaticObjectsFromSceneData(this.scene);

            // load spawn areas
            this.loadSpawnersFromSceneData(this.scene);

            // TODO THIS: vehicles have to be created at the start and
            // upon connection of new controllers

                // create a sample vehicle
                var playerData = {
                    id: 3,
                    vehicleID: 1
                };
                this.addPlayer(playerData);
            //
        },
        addPlayer: function (playerData) {

            var spawner = this.getFreeSpawner();
            var loader = new THREE.ObjectLoader();
            var chasis = loader.parse(fileManager.CHASIS[0]);

            chasis.position.copy(spawner.position);
            chasis.rotation.copy(spawner.rotation);

            var chasisData = this.serializeMesh(chasis);

            this.scene.add(chasis);
            this.bodies.push(chasis);

            chasis.castShadow = true;
            chasis.receiveShadow = true;

            // wheels

            for (var i = 0; i < 4; i++) {
                var wheel = loader.parse(fileManager.WHEELS[0]);
                this.scene.add(wheel);
                this.wheels.push(wheel);
            };

            var ratio = SCALE / 10;
            var dx = 0.9 / ratio;
            var dy = 0.4 / ratio;
            var dzBack = 2 / ratio;
            var dzFront = 2 / ratio;

            var radius = 0.5 / ratio;

            var data = {

                "vehicleID" : playerData.vehicleID,

                "chasisData": chasisData,

                "wheels" : [

                    {
                        "isFrontWheel"  : true,
                        "mirrored"      : false,

                        "wheelRadius"           : radius,
                        "suspensionRestLength"  : 0.25 / (ratio),

                        "connectionPoint"   : [ -dx, -dy, dzFront ],
                        "wheelDirection"    : [ 0, -1, 0 ],
                        "wheelAxle"         : [ -1, 0, 0 ],

                        "tuning" : {
                            "rollInfluence" : 0.1,
                        }
                    },

                    {
                        "isFrontWheel"  : true,
                        "mirrored"      : true,

                        "wheelRadius"           : radius,
                        "suspensionRestLength"  : 0.25 / (ratio),

                        "connectionPoint"   : [ dx, -dy, dzFront ],
                        "wheelDirection"    : [ 0, -1, 0 ],
                        "wheelAxle"         : [ -1, 0, 0 ],

                        "tuning" : {
                            "rollInfluence" : 0.1,
                        }

                    },

                    {
                        "isFrontWheel"  : false,
                        "mirrored"      : false,

                        "wheelRadius"           : radius,
                        "suspensionRestLength"  : 0.5 / (ratio),

                        "connectionPoint"   : [ -dx, -dy, -dzBack ],
                        "wheelDirection"    : [ 0, -1, 0 ],
                        "wheelAxle"         : [ -1, 0, 0 ]

                    },

                    {
                        "mirrored"      : true,
                        "isFrontWheel"  : false,

                        "wheelRadius"           : radius,
                        "suspensionRestLength"  : 0.5 / (ratio),

                        "connectionPoint"   : [ dx, -dy, -dzBack ],
                        "wheelDirection"    : [ 0, -1, 0 ],
                        "wheelAxle"         : [ -1, 0, 0 ]
                    }

                ],

                "maxSuspensionTravelCm" : 750.0,
                "maxSuspensionForce"    : 6000.0,
                "suspensionStiffness"   : 10.0 * ratio,
                "suspensionCompression" : 20.83,
                "suspensionDamping"     : 0.88,
                "rollInfluence"         : 0.1,
                "frictionSlip"          : 1000

            }

            this.physicsWorker.postMessage({
                type: WORKER.CREATE_CAR,
                data: data
            });

            this.vehicles.push(playerData);
        },
        loadCameraFromSceneData: function (sceneData) {

            var w = window.innerWidth;
            var h = window.innerHeight;

            if (PERSPECTIVE_CAMERA) {
                this.camera = new THREE.PerspectiveCamera( 70, w / h, 1, 1500 );
            } else {
                this.camera = new THREE.OrthographicCamera( -w/2, w/2, h/2, -h/2, 1, 1500 );
            }

            var cameraData = sceneData.getObjectByName("CAMERA");
            var pos = cameraData.getObjectByName("POSITION");
            var target = cameraData.getObjectByName("TARGET");

            this.camera.position.copy(pos.position);
            this.camera.lookAt(target.position);

        },
        loadLightFromSceneData: function (sceneData) {

            var w = window.innerWidth;
            var h = window.innerHeight;

            // light

            var light = sceneData.getObjectByName("LIGHT", true);
            light.castShadow = true;

            light.shadowCameraNear = 100;
            light.shadowCameraFar = 1500;
            light.shadowCameraFov = 10;

            //light.shadowCameraVisible = true;

            light.shadowBias = 0.0001;
            light.shadowDarkness = 0.2;

            light.shadowMapWidth = w;
            light.shadowMapHeight = h;
        },
        loadStaticObjectsFromSceneData: function (sceneData) {
            // load objects from LEVEL layer
            var levelObject = sceneData.getObjectByName("LEVEL");
            var len = levelObject.children.length;

            for (var i = 0; i < len; i++) {
                var object = levelObject.children[i];

                object.castShadow = true;
                object.receiveShadow = true;

                this.bodies.push(object);    // logicaly
                this.createPhysicalObject(object);  // physicaly
            };
        },
        loadSpawnersFromSceneData: function (sceneData) {
            // load objects from SPAWNERS layer
            var spawnersObject = sceneData.getObjectByName("SPAWNERS");
            this.spawners = spawnersObject.children;
        },
        createPhysicalObject: function (mesh) {
            var serializedMesh = this.serializeMesh(mesh);

            this.physicsWorker.postMessage({
                type: WORKER.ADD_OBJECT,
                data: serializedMesh
            });

            return serializedMesh;
        },
        getFreeSpawner: function() {
            return utils.getRandom(this.spawners);
        },
        serializeMesh: function (mesh) {

            // serialize the mesh params to pass them to the
            // physics worker.

            var offset = 0;
            var object = [];

            offset = this.serializePosition(mesh, object, offset);
            offset = this.serializeRotation(mesh, object, offset);
            offset = this.serializeBoxSize(mesh, object, offset);

            object[offset] = mesh.userData.mass !== undefined ? mesh.userData.mass : 1;

            return object;
        },
        serializeVehicle: function (mesh, spawner) {

            // serialize the mesh params to pass them to the
            // physics worker.

            var offset = 0;
            var object = [];

            offset = this.serializePosition(spawner, object, offset);
            offset = this.serializeRotation(spawner, object, offset);
            offset = this.serializeBoxSize(mesh, object, offset);

            object[offset] = mesh.userData.mass !== undefined ? mesh.userData.mass : 1;

            return object;
        },
        serializeObject3D: function (mesh) {

            // serialize an Object 3d

            var offset = 0;
            var object = [];

            offset = this.serializePosition(mesh, object, offset);
            offset = this.serializeRotation(mesh, object, offset);

            return object;
        },
        serializePosition: function(obj, array, offset) {
            var origin = obj.position;
            array[offset] = origin.x / SCALE;
            array[offset+1] = origin.y / SCALE;
            array[offset+2] = origin.z / SCALE;

            return offset + 3;
        },
        serializeRotation: function(obj, array, offset) {
            var rotation = obj.quaternion;
            array[offset] = rotation.x;
            array[offset+1] = rotation.y;
            array[offset+2] = rotation.z;
            array[offset+3] = rotation.w;

            return offset + 4;
        },
        serializeBoxSize: function(obj, array, offset) {
            var size = obj.geometry.parameters;
            array[offset] = size.width / SCALE;
            array[offset+1] = size.height / SCALE;
            array[offset+2] = size.depth / SCALE;

            return offset + 3;
        },
        setupWorker: function () {
            // Worker
            this.physicsWorker = new Worker('./js/src/physics/worker.js');
            this.physicsWorker.onmessage = this.onmessage;
            this.physicsWorker.postMessage({
                type: WORKER.START
            });
        },
        onmessage: function (event) {
            var message = event.data;

            switch(message.type) {
                case WORKER.SIMULATION_DATA:
                    this.updateWorldWithSimulationData(message.data);
                    break;
                default:
            }
        },
        updateWorldWithSimulationData: function (data) {
            var offset = 0;

            offset = this.updateObjectFromData(data, offset, this.bodies);
            offset = this.updateObjectFromData(data, offset, this.wheels);
        },
        updateObjectFromData: function (data, offset, array) {
            //bodies
            for (var i = 0; i < array.length; i++) {
                var body = array[i];
                body.position.x = data[offset] * SCALE;
                body.position.y = data[offset+1] * SCALE;
                body.position.z = data[offset+2] * SCALE;

                body.quaternion.x = data[offset+3];
                body.quaternion.y = data[offset+4];
                body.quaternion.z = data[offset+5];
                body.quaternion.w = data[offset+6];

                offset += 7;
            }

            return offset;
        },
        dispose: function () {
            if (physicsWorker) physicsWorker.terminate();

            physicsWorker = null;
            this.vehicles = null;
            bodies = null;
            scene = null;
            camera = null;
            spawners = null;
            vehicles = null;
            wheels = null;

            this.__dispose();
        }
    });
});<|endoftext|>


<commit_before>/**
 * Represents a 2D Point.
 */
export default class Point {
	/**
	 * Create a Point.
	 * @param {number} x - the x of point.
	 * @param {number} y - the y of point.
	 */
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}

	static create (object) {
		object = object || {};
		return new Point(
			object.x,
			object.y,
		);
	}

	/**
	 * Get x value.
	 * @return {number} The x value.
	 */
	get x () {
		return Number(this._x) || 0;
	}

	/**
	 * Set x value.
	 * @param {number} The x value.
	 */
	set x (value) {
		if (!isNaN(value)) {
			this._x = Number(value);
		}
	}

	/**
	 * Get y value.
	 * @return {number} The y value.
	 */
	get y () {
		return Number(this._y) || 0;
	}

	/**
	 * Set y value.
	 * @param {number} The y value.
	 */
	set y (value) {
		if (!isNaN(value)) {
			this._y = Number(value);
		}
	}

	/**
	 * Get distance to a point.
	 * @param {Point} Object of Point class.
	 * @return {number} The distance.
	 */
	distance (point) {
		point = point || {};
		return Math.sqrt(Math.pow(point2.x - this.x, 2) + Math.pow(point2.y - this.y, 2));
	}
}
<commit_msg>in Point class method isEqualTo was added<commit_after>/**
 * Represents a 2D Point.
 */
export default class Point {
	/**
	 * Create a Point.
	 * @param {number} x - the x of point.
	 * @param {number} y - the y of point.
	 */
	constructor(x, y) {
		this.x = x;
		this.y = y;
	}

	static create (object) {
		object = object || {};
		return new Point(
			object.x,
			object.y,
		);
	}

	/**
	 * Get x value.
	 * @return {number} The x value.
	 */
	get x () {
		return Number(this._x) || 0;
	}

	/**
	 * Set x value.
	 * @param {number} The x value.
	 */
	set x (value) {
		if (!isNaN(value)) {
			this._x = Number(value);
		}
	}

	/**
	 * Get y value.
	 * @return {number} The y value.
	 */
	get y () {
		return Number(this._y) || 0;
	}

	/**
	 * Set y value.
	 * @param {number} The y value.
	 */
	set y (value) {
		if (!isNaN(value)) {
			this._y = Number(value);
		}
	}

	/**
	 * Get distance to a point.
	 * @param {Point} Object of Point class.
	 * @return {number} The distance.
	 */
	distance (point) {
		point = point || {};
		return Math.sqrt(Math.pow(point2.x - this.x, 2) + Math.pow(point2.y - this.y, 2));
	}

	isEqualTo (point) {
		let result = false;
		if (point instanceof Point) {
			result = point.x === this.x && point.y === this.y;
		}
		return result;
	}
}
<|endoftext|>


<commit_before>// @flow
import React, { isValidElement, cloneElement } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import type { AnyReactElement, ReactChildren } from 'react-flow-types';

import icBEM from './utils/icBEM';
import prefixClass from './utils/prefixClass';

import Icon from './Icon';
import Overlay from './Overlay';
import escapable from './mixins/escapable';
import renderToLayer from './mixins/renderToLayer';

import './styles/Popup.scss';

export const COMPONENT_NAME = prefixClass('popup');
const ROOT_BEM = icBEM(COMPONENT_NAME);
export const BEM = {
    root: ROOT_BEM,
    container: ROOT_BEM.element('container'),
    body: ROOT_BEM.element('body'),
    message: ROOT_BEM.element('message'),
    button: ROOT_BEM.element('button'),
    buttonsGroup: ROOT_BEM.element('buttons-group')
};

export type Props = {
    icon?: string | ReactChildren,
    message?: ReactChildren,
    buttons?: AnyReactElement[],

    /* eslint-disable react/require-default-props */
    className?: string,
    children?: ReactChildren,
    /* eslint-enable react/require-default-props */
};

/**
 * Render popup's icon
 *
 * @param  {String|Element} icon
 * @return {Element}
 */
function renderPopupIcon(icon) {
    if (typeof icon === 'string') {
        return (
            <Icon
                large
                color="blue"
                type={icon} />
        );
    }

    return icon;
}

/**
 * Render popup's message
 *
 * @param  {Element|String} message
 * @return {Element}
 */
function renderPopupMessage(message) {
    if (!message) {
        return null;
    }

    return (

            {message}

    );
}

/**
 * Render popup's buttons
 *
 * @param  {Array} buttons
 * @return {Array}
 */
function renderPopupButtons(buttons) {
    if (!buttons || buttons.length === 0) {
        return null;
    }

    const popupButtons = buttons.map((button) => {
        // Render as expanded button
        if (isValidElement(button)) {
            const buttonBemClass = BEM.button.toString();

            return cloneElement(button, {
                className: buttonBemClass,
                align: 'center',
                minified: false
            });
        }

        return button;
    });

    return (

            {popupButtons}

    );
}

function Popup({
    icon,
    message,
    buttons,
    // React props
    className,
    children,
    ...popupProps
}: Props) {
    const rootClassName = classNames(BEM.root.toString(), className);

    return (

            <Overlay />

                    {renderPopupIcon(icon)}
                    {renderPopupMessage(message)}

                {renderPopupButtons(buttons)}
                {children}

    );
}

Popup.propTypes = {
    icon: PropTypes.node,
    message: PropTypes.node,
    buttons: PropTypes.arrayOf(PropTypes.element)
};

Popup.defaultProps = {
    icon: null,
    message: null,
    buttons: []
};

// export for tests
export { Popup as PurePopup };
export const EscapablePopup = escapable(Popup);

export default renderToLayer(EscapablePopup);
<commit_msg>Refine Flow type check for 'buttons' array on <Popup><commit_after>// @flow
import React, { isValidElement, cloneElement } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import type { ReactChildren } from 'react-flow-types';

import icBEM from './utils/icBEM';
import prefixClass from './utils/prefixClass';

import Icon from './Icon';
import Overlay from './Overlay';
import escapable from './mixins/escapable';
import renderToLayer from './mixins/renderToLayer';

import './styles/Popup.scss';

export const COMPONENT_NAME = prefixClass('popup');
const ROOT_BEM = icBEM(COMPONENT_NAME);
export const BEM = {
    root: ROOT_BEM,
    container: ROOT_BEM.element('container'),
    body: ROOT_BEM.element('body'),
    message: ROOT_BEM.element('message'),
    button: ROOT_BEM.element('button'),
    buttonsGroup: ROOT_BEM.element('buttons-group')
};

export type Props = {
    icon?: string | ReactChildren,
    message?: ReactChildren,
    buttons?: React$Element<*>[],

    /* eslint-disable react/require-default-props */
    className?: string,
    children?: ReactChildren,
    /* eslint-enable react/require-default-props */
};

/**
 * Render popup's icon
 *
 * @param  {String|Element} icon
 * @return {Element}
 */
function renderPopupIcon(icon) {
    if (typeof icon === 'string') {
        return (
            <Icon
                large
                color="blue"
                type={icon} />
        );
    }

    return icon;
}

/**
 * Render popup's message
 *
 * @param  {Element|String} message
 * @return {Element}
 */
function renderPopupMessage(message) {
    if (!message) {
        return null;
    }

    return (

            {message}

    );
}

/**
 * Render popup's buttons
 *
 * @param  {Array} buttons
 * @return {Array}
 */
function renderPopupButtons(buttons) {
    if (!buttons || buttons.length === 0) {
        return null;
    }

    const popupButtons = buttons.map((button) => {
        // Render as expanded button
        if (isValidElement(button)) {
            const buttonBemClass = BEM.button.toString();

            return cloneElement(button, {
                className: buttonBemClass,
                align: 'center',
                minified: false
            });
        }

        return button;
    });

    return (

            {popupButtons}

    );
}

function Popup({
    icon,
    message,
    buttons,
    // React props
    className,
    children,
    ...popupProps
}: Props) {
    const rootClassName = classNames(BEM.root.toString(), className);

    return (

            <Overlay />

                    {renderPopupIcon(icon)}
                    {renderPopupMessage(message)}

                {renderPopupButtons(buttons)}
                {children}

    );
}

Popup.propTypes = {
    icon: PropTypes.node,
    message: PropTypes.node,
    buttons: PropTypes.arrayOf(PropTypes.element)
};

Popup.defaultProps = {
    icon: null,
    message: null,
    buttons: []
};

// export for tests
export { Popup as PurePopup };
export const EscapablePopup = escapable(Popup);

export default renderToLayer(EscapablePopup);
<|endoftext|>


<commit_before>import { useQuery } from '@apollo/react-hooks'
import Breadcrumbs from '@material-ui/core/Breadcrumbs'
import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles'
import NavigateNextIcon from '@material-ui/icons/NavigateNext'
import classnames from 'classnames'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React, { useState, useEffect } from 'react'
import { Link, useLocation } from 'react-router-dom'

import { TL1, TL2, Label3 } from 'src/components/typography'

import Cassettes from './MachineComponents/Cassettes'
import Commissions from './MachineComponents/Commissions'
import Details from './MachineComponents/Details'
import Overview from './MachineComponents/Overview'
import Transactions from './MachineComponents/Transactions'
import styles from './Machines.styles'
const useStyles = makeStyles(styles)

const getMachineInfo = R.compose(R.find, R.propEq('name'))

const GET_INFO = gql`
  query getInfo {
    machines {
      name
      deviceId
      paired
      lastPing
      pairedAt
      version
      model
      cashbox
      cassette1
      cassette2
      statuses {
        label
        type
      }
      downloadSpeed
      responseTime
      packetLoss
    }
    config
  }
`

const getMachines = R.path(['machines'])

const Machines = () => {
  const { data, refetch, loading } = useQuery(GET_INFO)
  const location = useLocation()
  const [selectedMachine, setSelectedMachine] = useState('')
  const classes = useStyles()

  const machines = getMachines(data) ?? []
  const machineInfo = getMachineInfo(selectedMachine)(machines) ?? {}
  const timezone = R.path(['config', 'locale_timezone'], data) ?? {}

  // pre-selects first machine from the list, if there is a machine configured.
  useEffect(() => {
    if (!loading && data && data.machines) {
      if (location.state && location.state.selectedMachine) {
        setSelectedMachine(location.state.selectedMachine)
      } else {
        setSelectedMachine(R.path(['machines', 0, 'name'])(data) ?? '')
      }
    }
  }, [loading, data, location.state])

  return (
    <Grid container className={classes.grid}>
      <Grid item xs={3}>
        <Grid item xs={12}>

            <Breadcrumbs separator={<NavigateNextIcon fontSize="small" />}>
              <Link to="/dashboard" className={classes.breadcrumbLink}>
                <Label3 noMargin className={classes.subtitle}>
                  Dashboard
                </Label3>
              </Link>
              <TL2 noMargin className={classes.subtitle}>
                {selectedMachine}
              </TL2>
            </Breadcrumbs>
            <Overview data={machineInfo} onActionSuccess={refetch} />

        </Grid>
        <Grid item xs={12}>
          {/* on hold for now <Sidebar
            isSelected={R.equals(selectedMachine)}
            selectItem={setSelectedMachine}
            data={machines}
            getText={R.prop('name')}
            getKey={R.prop('deviceId')}
          /> */}
        </Grid>
      </Grid>
      <Grid item xs={9}>

            <TL1 className={classes.subtitle}>{'Details'}</TL1>

            <TL1 className={classes.subtitle}>{'Cash cassettes'}</TL1>
            <Cassettes
              refetchData={refetch}
              machine={machineInfo}
              config={data?.config ?? false}
            />

            <TL1 className={classes.subtitle}>{'Latest transactions'}</TL1>
            <Transactions id={machineInfo?.deviceId ?? null} />

            <TL1 className={classes.subtitle}>{'Commissions'}</TL1>
            <Commissions
              name={'commissions'}
              id={machineInfo?.deviceId ?? null}
            />

      </Grid>
    </Grid>
  )
}

export default Machines
<commit_msg>Chore: remove unneeded useEffect and useState hooks<commit_after>import { useQuery } from '@apollo/react-hooks'
import Breadcrumbs from '@material-ui/core/Breadcrumbs'
import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles'
import NavigateNextIcon from '@material-ui/icons/NavigateNext'
import classnames from 'classnames'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React from 'react'
import { Link, useLocation } from 'react-router-dom'

import { TL1, TL2, Label3 } from 'src/components/typography'

import Cassettes from './MachineComponents/Cassettes'
import Commissions from './MachineComponents/Commissions'
import Details from './MachineComponents/Details'
import Overview from './MachineComponents/Overview'
import Transactions from './MachineComponents/Transactions'
import styles from './Machines.styles'
const useStyles = makeStyles(styles)

const getMachineInfo = R.compose(R.find, R.propEq('name'))

const GET_INFO = gql`
  query getInfo {
    machines {
      name
      deviceId
      paired
      lastPing
      pairedAt
      version
      model
      cashbox
      cassette1
      cassette2
      statuses {
        label
        type
      }
      downloadSpeed
      responseTime
      packetLoss
    }
    config
  }
`

const getMachines = R.path(['machines'])

const Machines = () => {
  const { data, refetch } = useQuery(GET_INFO)
  const location = useLocation()
  const classes = useStyles()

  const selectedMachine =
    R.path(['state', 'selectedMachine'])(location) ??
    R.path(['machines', 0, 'name'])(data) ??
    ''
  const machines = getMachines(data) ?? []
  const machineInfo = getMachineInfo(selectedMachine)(machines) ?? {}
  const timezone = R.path(['config', 'locale_timezone'], data) ?? {}

  return (
    <Grid container className={classes.grid}>
      <Grid item xs={3}>
        <Grid item xs={12}>

            <Breadcrumbs separator={<NavigateNextIcon fontSize="small" />}>
              <Link to="/dashboard" className={classes.breadcrumbLink}>
                <Label3 noMargin className={classes.subtitle}>
                  Dashboard
                </Label3>
              </Link>
              <TL2 noMargin className={classes.subtitle}>
                {selectedMachine}
              </TL2>
            </Breadcrumbs>
            <Overview data={machineInfo} onActionSuccess={refetch} />

        </Grid>
        <Grid item xs={12}>
          {/* on hold for now <Sidebar
            isSelected={R.equals(selectedMachine)}
            selectItem={setSelectedMachine}
            data={machines}
            getText={R.prop('name')}
            getKey={R.prop('deviceId')}
          /> */}
        </Grid>
      </Grid>
      <Grid item xs={9}>

            <TL1 className={classes.subtitle}>{'Details'}</TL1>

            <TL1 className={classes.subtitle}>{'Cash cassettes'}</TL1>
            <Cassettes
              refetchData={refetch}
              machine={machineInfo}
              config={data?.config ?? false}
            />

            <TL1 className={classes.subtitle}>{'Latest transactions'}</TL1>
            <Transactions id={machineInfo?.deviceId ?? null} />

            <TL1 className={classes.subtitle}>{'Commissions'}</TL1>
            <Commissions
              name={'commissions'}
              id={machineInfo?.deviceId ?? null}
            />

      </Grid>
    </Grid>
  )
}

export default Machines
<|endoftext|>


<commit_before>import {activationStrategy} from './navigation-plan';
import {isNavigationCommand} from './navigation-commands';

export class CanDeactivatePreviousStep {
  run(navigationInstruction: NavigationInstruction, next: Function) {
    return processDeactivatable(navigationInstruction.plan, 'canDeactivate', next);
  }
}

export class CanActivateNextStep {
  run(navigationInstruction: NavigationInstruction, next: Function) {
    return processActivatable(navigationInstruction, 'canActivate', next);
  }
}

export class DeactivatePreviousStep {
  run(navigationInstruction: NavigationInstruction, next: Function) {
    return processDeactivatable(navigationInstruction.plan, 'deactivate', next, true);
  }
}

export class ActivateNextStep {
  run(navigationInstruction: NavigationInstruction, next: Function) {
    return processActivatable(navigationInstruction, 'activate', next, true);
  }
}

function processDeactivatable(plan, callbackName, next, ignoreResult) {
  let infos = findDeactivatable(plan, callbackName);
  let i = infos.length; //query from inside out

  function inspect(val) {
    if (ignoreResult || shouldContinue(val)) {
      return iterate();
    }

    return next.cancel(val);
  }

  function iterate() {
    if (i--) {
      try {
        let viewModel = infos[i];
        let result = viewModel[callbackName]();
        return processPotential(result, inspect, next.cancel);
      } catch (error) {
        return next.cancel(error);
      }
    }

    return next();
  }

  return iterate();
}

function findDeactivatable(plan, callbackName, list: Array<Object> = []): Array<Object> {
  for (let viewPortName in plan) {
    let viewPortPlan = plan[viewPortName];
    let prevComponent = viewPortPlan.prevComponent;

    if ((viewPortPlan.strategy === activationStrategy.invokeLifecycle ||
        viewPortPlan.strategy === activationStrategy.replace) &&
        prevComponent) {
      let viewModel = prevComponent.viewModel;

      if (callbackName in viewModel) {
        list.push(viewModel);
      }
    }

    if (viewPortPlan.childNavigationInstruction) {
      findDeactivatable(viewPortPlan.childNavigationInstruction.plan, callbackName, list);
    } else if (prevComponent) {
      addPreviousDeactivatable(prevComponent, callbackName, list);
    }
  }

  return list;
}

function addPreviousDeactivatable(component, callbackName, list): void {
  let childRouter = component.childRouter;

  if (childRouter && childRouter.currentInstruction) {
    let viewPortInstructions = childRouter.currentInstruction.viewPortInstructions;

    for (let viewPortName in viewPortInstructions) {
      let viewPortInstruction = viewPortInstructions[viewPortName];
      let prevComponent = viewPortInstruction.component;
      let prevViewModel = prevComponent.viewModel;

      if (callbackName in prevViewModel) {
        list.push(prevViewModel);
      }

      addPreviousDeactivatable(prevComponent, callbackName, list);
    }
  }
}

function processActivatable(navigationInstruction: NavigationInstruction, callbackName: any, next: Function, ignoreResult: boolean) {
  let infos = findActivatable(navigationInstruction, callbackName);
  let length = infos.length;
  let i = -1; //query from top down

  function inspect(val, router) {
    if (ignoreResult || shouldContinue(val, router)) {
      return iterate();
    }

    return next.cancel(val);
  }

  function iterate() {
    i++;

    if (i < length) {
      try {
        let current = infos[i];
        let result = current.viewModel[callbackName](...current.lifecycleArgs);
        return processPotential(result, val => inspect(val, current.router), next.cancel);
      } catch (error) {
        return next.cancel(error);
      }
    }

    return next();
  }

  return iterate();
}

function findActivatable(navigationInstruction: NavigationInstruction, callbackName: string, list: Array<Object> = [], router: Router): Array<Object> {
  let plan = navigationInstruction.plan;

  Object.keys(plan).filter((viewPortName) => {
    let viewPortPlan = plan[viewPortName];
    let viewPortInstruction = navigationInstruction.viewPortInstructions[viewPortName];
    let viewModel = viewPortInstruction.component.viewModel;

    if ((viewPortPlan.strategy === activationStrategy.invokeLifecycle || viewPortPlan.strategy === activationStrategy.replace) && callbackName in viewModel) {
      list.push({
        viewModel,
        lifecycleArgs: viewPortInstruction.lifecycleArgs,
        router
      });
    }

    if (viewPortPlan.childNavigationInstruction) {
      findActivatable(
        viewPortPlan.childNavigationInstruction,
        callbackName,
        list,
        viewPortInstruction.component.childRouter || router
      );
    }
  });

  return list;
}

function shouldContinue(output, router: Router) {
  if (output instanceof Error) {
    return false;
  }

  if (isNavigationCommand(output)) {
    if (typeof output.setRouter === 'function') {
      output.setRouter(router);
    }

    return !!output.shouldContinueProcessing;
  }

  if (output === undefined) {
    return true;
  }

  return output;
}

class SafeSubscription {
  //if this were TypeScript, subscriptionFunc would be of type
  //(sub: SafeSubscription) => Subscription
  constructor(subscriptionFunc) {
    this._subscribed = true;
    this._subscription = subscriptionFunc(this);

    if(!this._subscribed) this.unsubscribe()
  }

  unsubscribe() {
    if(this._subscription) this._subscription.unsubscribe();

    this._subscribed = false;
  }
}

function processPotential(obj, resolve, reject) {
  if (obj && typeof obj.then === 'function') {
    return Promise.resolve(obj).then(resolve).catch(reject);
  }

  if(obj && typeof obj.subscribe === 'function') {
    //an object with a subscribe function should be assumed to be an observable
    new SafeSubscription(sub => obj.subscribe({
      next() {
        sub.unsubscribe();
        resolve(obj);
      },
      error(err) {
        reject(err);
      }
    }));
  }

  try {
    return resolve(obj);
  } catch (error) {
    return reject(error);
  }
}
<commit_msg>feat(router): observables in lifecycle<commit_after>import {activationStrategy} from './navigation-plan';
import {isNavigationCommand} from './navigation-commands';

export class CanDeactivatePreviousStep {
  run(navigationInstruction: NavigationInstruction, next: Function) {
    return processDeactivatable(navigationInstruction.plan, 'canDeactivate', next);
  }
}

export class CanActivateNextStep {
  run(navigationInstruction: NavigationInstruction, next: Function) {
    return processActivatable(navigationInstruction, 'canActivate', next);
  }
}

export class DeactivatePreviousStep {
  run(navigationInstruction: NavigationInstruction, next: Function) {
    return processDeactivatable(navigationInstruction.plan, 'deactivate', next, true);
  }
}

export class ActivateNextStep {
  run(navigationInstruction: NavigationInstruction, next: Function) {
    return processActivatable(navigationInstruction, 'activate', next, true);
  }
}

function processDeactivatable(plan, callbackName, next, ignoreResult) {
  let infos = findDeactivatable(plan, callbackName);
  let i = infos.length; //query from inside out

  function inspect(val) {
    if (ignoreResult || shouldContinue(val)) {
      return iterate();
    }

    return next.cancel(val);
  }

  function iterate() {
    if (i--) {
      try {
        let viewModel = infos[i];
        let result = viewModel[callbackName]();
        return processPotential(result, inspect, next.cancel);
      } catch (error) {
        return next.cancel(error);
      }
    }

    return next();
  }

  return iterate();
}

function findDeactivatable(plan, callbackName, list: Array<Object> = []): Array<Object> {
  for (let viewPortName in plan) {
    let viewPortPlan = plan[viewPortName];
    let prevComponent = viewPortPlan.prevComponent;

    if ((viewPortPlan.strategy === activationStrategy.invokeLifecycle ||
        viewPortPlan.strategy === activationStrategy.replace) &&
        prevComponent) {
      let viewModel = prevComponent.viewModel;

      if (callbackName in viewModel) {
        list.push(viewModel);
      }
    }

    if (viewPortPlan.childNavigationInstruction) {
      findDeactivatable(viewPortPlan.childNavigationInstruction.plan, callbackName, list);
    } else if (prevComponent) {
      addPreviousDeactivatable(prevComponent, callbackName, list);
    }
  }

  return list;
}

function addPreviousDeactivatable(component, callbackName, list): void {
  let childRouter = component.childRouter;

  if (childRouter && childRouter.currentInstruction) {
    let viewPortInstructions = childRouter.currentInstruction.viewPortInstructions;

    for (let viewPortName in viewPortInstructions) {
      let viewPortInstruction = viewPortInstructions[viewPortName];
      let prevComponent = viewPortInstruction.component;
      let prevViewModel = prevComponent.viewModel;

      if (callbackName in prevViewModel) {
        list.push(prevViewModel);
      }

      addPreviousDeactivatable(prevComponent, callbackName, list);
    }
  }
}

function processActivatable(navigationInstruction: NavigationInstruction, callbackName: any, next: Function, ignoreResult: boolean) {
  let infos = findActivatable(navigationInstruction, callbackName);
  let length = infos.length;
  let i = -1; //query from top down

  function inspect(val, router) {
    if (ignoreResult || shouldContinue(val, router)) {
      return iterate();
    }

    return next.cancel(val);
  }

  function iterate() {
    i++;

    if (i < length) {
      try {
        let current = infos[i];
        let result = current.viewModel[callbackName](...current.lifecycleArgs);
        return processPotential(result, val => inspect(val, current.router), next.cancel);
      } catch (error) {
        return next.cancel(error);
      }
    }

    return next();
  }

  return iterate();
}

function findActivatable(navigationInstruction: NavigationInstruction, callbackName: string, list: Array<Object> = [], router: Router): Array<Object> {
  let plan = navigationInstruction.plan;

  Object.keys(plan).filter((viewPortName) => {
    let viewPortPlan = plan[viewPortName];
    let viewPortInstruction = navigationInstruction.viewPortInstructions[viewPortName];
    let viewModel = viewPortInstruction.component.viewModel;

    if ((viewPortPlan.strategy === activationStrategy.invokeLifecycle || viewPortPlan.strategy === activationStrategy.replace) && callbackName in viewModel) {
      list.push({
        viewModel,
        lifecycleArgs: viewPortInstruction.lifecycleArgs,
        router
      });
    }

    if (viewPortPlan.childNavigationInstruction) {
      findActivatable(
        viewPortPlan.childNavigationInstruction,
        callbackName,
        list,
        viewPortInstruction.component.childRouter || router
      );
    }
  });

  return list;
}

function shouldContinue(output, router: Router) {
  if (output instanceof Error) {
    return false;
  }

  if (isNavigationCommand(output)) {
    if (typeof output.setRouter === 'function') {
      output.setRouter(router);
    }

    return !!output.shouldContinueProcessing;
  }

  if (output === undefined) {
    return true;
  }

  return output;
}

class SafeSubscription {
  //if this were TypeScript, subscriptionFunc would be of type
  //(sub: SafeSubscription) => Subscription
  constructor(subscriptionFunc) {
    this._subscribed = true;
    this._subscription = subscriptionFunc(this);

    if(!this._subscribed) this.unsubscribe()
  }

  unsubscribe() {
    if(this._subscribed && this._subscription) this._subscription.unsubscribe();

    this._subscribed = false;
  }
}

function processPotential(obj, resolve, reject) {
  if (obj && typeof obj.then === 'function') {
    return Promise.resolve(obj).then(resolve).catch(reject);
  }

  if(obj && typeof obj.subscribe === 'function') {
    //an object with a subscribe function should be assumed to be an observable
    return new SafeSubscription(sub => obj.subscribe({
      next() {
        sub.unsubscribe();
        resolve(obj);
      },
      error(error) {
        reject(error);
      }
    }));
  }

  try {
    return resolve(obj);
  } catch (error) {
    return reject(error);
  }
}
<|endoftext|>


<commit_before>/**
 * @package assets
 */

(function($) {

	/**
	 * This plugin creates a Symphony duplicator.
	 *
	 * @name $.symphonyDuplicator
	 * @class
	 *
	 * @param {Object} options An object specifying containing the attributes specified below
	 * @param {String} [options.instances='> li:not(.template)'] Selector to find children to use as instances
	 * @param {String} [options.templates='> li.template'] Selector to find children to use as templates
	 * @param {String} [options.headers='> :first-child'] Selector to find the header part of each instance
	 * @param {String} [options.perselect=false] Default option for the selector
	 * @param {Boolean} [options.orderable=false] Can instances be ordered
	 * @param {Boolean} [options.collapsible=false] Can instances be collapsed
	 * @param {Boolean} [options.constructable=true] Allow construction of new instances
	 * @param {Boolean} [options.destructable=true] Allow destruction of instances
	 * @param {Integer} [optionss.minimum=0] Do not allow instances to be removed below this limit
	 * @param {Integer} [options.maximum=1000] Do not allow instances to be added above this limit
	 * @param {String} [options.speed='fast'] Animation speed
	 *
	 *	@example

			$('.duplicator').symphonyDuplicator({
				orderable: true,
				collapsible: true
			});
	 */
	$.fn.symphonyDuplicator = function(options) {
		var objects = this,
			settings = {
				instances:			'> li:not(.template)',
				templates:			'> li.template',
				headers:			'> :first-child',
				preselect:			false,
				orderable:			false,
				collapsible:		false,
				constructable:		true,
				destructable:		true,
				minimum:			0,
				maximum:			1000,
				speed:				'fast'
			};

		$.extend(settings, options);

	/*-----------------------------------------------------------------------*/

		// Language strings
		Symphony.Language.add({
			'Add item': false,
			'Remove item': false
		});

	/*-----------------------------------------------------------------------*/

		objects.each(function() {
			var object = $(this),
				instances = object.find(settings.instances).addClass('instance'),
				templates = object.find(settings.templates).addClass('template'),
				items = instances.add(templates),
				headers = items.find(settings.headers).addClass('header'),
				duplicator = $('
'),
				controls = $('
'),
				selector = $('<select />'),
				constructor = $('' + Symphony.Language.get('Add item') + '');

		/*-------------------------------------------------------------------*/

			// Construct instances
			controls.on('click.duplicator', 'a.constructor:not(.disabled)', function(event) {
				var instance = templates.filter('[data-type="' + selector.val() + '"]').clone();

				instance.trigger('constructstart.duplicator');
				instance.trigger('construct.duplicator'); /* deprecated */
				instance.hide().appendTo(object);
				instance.trigger('constructshow.duplicator');
				instance.slideDown(settings.speed, function() {

					// Focus first input
					instance.find('input[type!="hidden"]:first').focus();
					instance.trigger('constructstop.duplicator');
				});
			});

			// Destruct instances
			duplicator.on('click.duplicator', 'a.destructor:not(.disabled)', function(event) {
				var instance = $(this).parents('.instance:first');

				instance.trigger('destructstart.duplicator');
				instance.trigger('destruct.duplicator'); /* deprecated */
				instance.slideUp(settings.speed, function() {
					$(this).remove();
					instance.trigger('destructstop.duplicator');
				});
			});

			// Lock constructor
			duplicator.on('constructstop.duplicator', '.instance', function() {
				if(duplicator.find('.instance').size() >= settings.maximum) {
					constructor.addClass('disabled');
				}
			});

			// Unlock constructor
			duplicator.on('destructstart.duplicator', '.instance', function() {
				if(duplicator.find('.instance').size() <= settings.maximum) {
					constructor.removeClass('disabled');
				}
			});

			// Lock destructor
			duplicator.on('destructstart.duplicator', '.instance', function() {
				if(duplicator.find('.instance').size() - 1 == settings.minimum) {
					duplicator.find('a.destructor').addClass('disabled');
				}
			});

			// Unlock destructor
			duplicator.on('constructstop.duplicator', '.instance', function() {
				if(duplicator.find('.instance').size() > settings.minimum) {
					duplicator.find('a.destructor').removeClass('disabled');
				}
			});

			// Lock unique instances
			duplicator.on('constructstop.duplicator', '.instance', function(event) {
				var instance = $(this);

				if(instance.is('.unique')) {
					selector.find('option[value="' + instance.attr('data-type') + '"]').attr('disabled', true);

					// Preselect first available instance
					selector.find('option').attr('selected', false).filter(':not(:disabled):first').attr('selected', true);

					// All selected
					if(selector.find('option:not(:disabled)').size() == 0) {
						selector.attr('disabled', 'disabled');
					}
				}
			});

			// Unlock unique instances
			duplicator.on('destructstart.duplicator', '.instance', function(event) {
				var instance = $(this),
					option;

				if(instance.is('.unique')) {
					option = selector.attr('disabled', false).find('option[value="' + instance.attr('data-type') + '"]').attr('disabled', false);

					// Preselect instance if it's the only active one
					if(selector.find('option:not(:disabled)').size() == 1) {
						option.attr('selected', true);
					}
				}
			});

			// Update title descriptions in header
			duplicator.on('keyup.duplicator', '.instance input[name*="[label]"]', function(event) {
				var input = $(this),
					instance = input.parents('.instance:first'),
					title = instance.find(settings.headers).find('span:first'),
					description = title.find('i');

				// Create description
				if(description.size() == 0) {
					description = $('').appendTo(title);
				}

				// Update description
				description.text($.trim(input.val()));
			});

			// Build field indexes
			duplicator.on('constructstop.duplicator refresh.duplicator', '.instance', function(event) {
				var instance = $(this),
					position = duplicator.find('.instance').index(instance);

				// Loop over named fields
				instance.find('*[name]').each(function() {
					var field = $(this),
						exp = /\[\-?[0-9]+\]/,
						name = field.attr('name');

					// Set index
					if(exp.test(name)) {
						field.attr('name', name.replace(exp, '[' + position + ']'));
					}
				});
			});

			// Refresh field indexes
			duplicator.on('orderstop.orderable', function(event) {
				duplicator.find('.instance').trigger('refresh.duplicator');
			});

			// Activate controls
			duplicator.on('constructshow.duplicator', '.instance', function(event) {
				if(duplicator.find('.instance').size() == 1) {
					duplicator.trigger('activate.collapsible');
				}
			});

			// Deactivate controls
			duplicator.on('destructstart.duplicator', '.instance', function(event) {
				if(duplicator.find('.instance').size() == 1) {
					duplicator.trigger('deactivate.collapsible');
				}
			});

		/*-------------------------------------------------------------------*/

			// Build interface
			duplicator.insertBefore(object).prepend(object);
			headers.wrapInner('').each(function() {
				$(this).nextAll().wrapAll('
');
			});

			// Constructable interface
			if(settings.constructable === true) {
				duplicator.addClass('constructable');
				controls.append(selector).append(constructor).appendTo(duplicator);

				// Populate selector
				templates.each(function() {
					var template = $(this);
					template.trigger('constructstart.duplicator');

					// Append options
					$('<option />', {
						text: template.find(settings.headers).text(),
						value: template.attr('data-type')
					}).appendTo(selector);

					// Check uniqueness
					template.trigger('constructstop.duplicator');
				}).removeClass('template').addClass('instance').remove();
			}

			// Select default
			if(settings.preselect != false) {
				selector.find('option[value="' + settings.preselect + '"]').attr('selected', true);
			}

			// Destructable interface
			if(settings.destructable === true) {
				duplicator.addClass('destructable');
				headers.append('' + Symphony.Language.get('Remove item') + '')
			}

			// Collapsible interface
			if(settings.collapsible) {
				duplicator.symphonyCollapsible({
					items: '.instance',
					handles: '.header span'
				});
			}

			// Orderable interface
			if(settings.orderable) {
				duplicator.symphonyOrderable({
					items: '.instance',
					handles: '.header',
					ignore: 'span'
				});
			}

			// Initialise existing instances
			instances.trigger('constructstop.duplicator');
			instances.find('input[name*="[label]"]').trigger('keyup.duplicator');
		});

	/*-----------------------------------------------------------------------*/

		return objects;
	};

})(jQuery.noConflict());
<commit_msg>Enhanced Duplicator Support<commit_after>/**
 * @package assets
 */

(function($) {

	/**
	 * This plugin creates a Symphony duplicator.
	 *
	 * @name $.symphonyDuplicator
	 * @class
	 *
	 * @param {Object} options An object specifying containing the attributes specified below
	 * @param {String} [options.instances='> li:not(.template)'] Selector to find children to use as instances
	 * @param {String} [options.templates='> li.template'] Selector to find children to use as templates
	 * @param {String} [options.headers='> :first-child'] Selector to find the header part of each instance
	 * @param {String} [options.perselect=false] Default option for the selector
	 * @param {Boolean} [options.orderable=false] Can instances be ordered
	 * @param {Boolean} [options.collapsible=false] Can instances be collapsed
	 * @param {Boolean} [options.constructable=true] Allow construction of new instances
	 * @param {Boolean} [options.destructable=true] Allow destruction of instances
	 * @param {Integer} [optionss.minimum=0] Do not allow instances to be removed below this limit
	 * @param {Integer} [options.maximum=1000] Do not allow instances to be added above this limit
	 * @param {String} [options.speed='fast'] Animation speed
	 *
	 *	@example

			$('.duplicator').symphonyDuplicator({
				orderable: true,
				collapsible: true
			});
	 */
	$.fn.symphonyDuplicator = function(options) {
		var objects = this,
			settings = {
				instances:			'> li:not(.template)',
				templates:			'> li.template',
				headers:			'> :first-child',
				preselect:			false,
				orderable:			false,
				collapsible:		false,
				constructable:		true,
				destructable:		true,
				minimum:			0,
				maximum:			1000,
				speed:				'fast'
			};

		$.extend(settings, options);

	/*-----------------------------------------------------------------------*/

		// Language strings
		Symphony.Language.add({
			'Add item': false,
			'Remove item': false
		});

	/*-----------------------------------------------------------------------*/

		objects.each(function() {
			var object = $(this),
				instances = object.find(settings.instances).addClass('instance'),
				templates = object.find(settings.templates).addClass('template'),
				items = instances.add(templates),
				headers = items.find(settings.headers).addClass('header'),
				duplicator = $('
'),
				controls = $('
'),
				selector = $('<select />'),
				constructor = $('' + Symphony.Language.get('Add item') + '');

		/*-------------------------------------------------------------------*/

			// Construct instances
			controls.on('click.duplicator', 'a.constructor:not(.disabled)', function(event, speed) {
				var instance = templates.filter('[data-type="' + selector.val() + '"]').clone();

				instance.trigger('constructstart.duplicator');
				instance.trigger('construct.duplicator'); /* deprecated */
				instance.hide().appendTo(object);

				// Set speed
				if(!speed) {
					speed = settings.speed;
				}

				// Show instance
				instance.trigger('constructshow.duplicator');
				instance.slideDown(speed, function() {

					// Focus first input
					instance.find('input[type!="hidden"]:first').focus();
					instance.trigger('constructstop.duplicator');
				});
			});

			// Destruct instances
			duplicator.on('click.duplicator', 'a.destructor:not(.disabled)', function(event) {
				var instance = $(this).parents('.instance:first');

				instance.trigger('destructstart.duplicator');
				instance.trigger('destruct.duplicator'); /* deprecated */
				instance.slideUp(settings.speed, function() {
					$(this).remove();
					instance.trigger('destructstop.duplicator');
				});
			});

			// Lock constructor
			duplicator.on('constructstop.duplicator', '.instance', function() {
				if(duplicator.find('.instance').size() >= settings.maximum) {
					constructor.addClass('disabled');
				}
			});

			// Unlock constructor
			duplicator.on('destructstart.duplicator', '.instance', function() {
				if(duplicator.find('.instance').size() <= settings.maximum) {
					constructor.removeClass('disabled');
				}
			});

			// Lock destructor
			duplicator.on('destructstart.duplicator', '.instance', function() {
				if(duplicator.find('.instance').size() - 1 == settings.minimum) {
					duplicator.find('a.destructor').addClass('disabled');
				}
			});

			// Unlock destructor
			duplicator.on('constructstop.duplicator', '.instance', function() {
				if(duplicator.find('.instance').size() > settings.minimum) {
					duplicator.find('a.destructor').removeClass('disabled');
				}
			});

			// Lock unique instances
			duplicator.on('constructstop.duplicator', '.instance', function(event) {
				var instance = $(this);

				if(instance.is('.unique')) {
					selector.find('option[value="' + instance.attr('data-type') + '"]').attr('disabled', true);

					// Preselect first available instance
					selector.find('option').attr('selected', false).filter(':not(:disabled):first').attr('selected', true);

					// All selected
					if(selector.find('option:not(:disabled)').size() == 0) {
						selector.attr('disabled', 'disabled');
					}
				}
			});

			// Unlock unique instances
			duplicator.on('destructstart.duplicator', '.instance', function(event) {
				var instance = $(this),
					option;

				if(instance.is('.unique')) {
					option = selector.attr('disabled', false).find('option[value="' + instance.attr('data-type') + '"]').attr('disabled', false);

					// Preselect instance if it's the only active one
					if(selector.find('option:not(:disabled)').size() == 1) {
						option.attr('selected', true);
					}
				}
			});

			// Update title descriptions in header
			duplicator.on('keyup.duplicator', '.instance input[name*="[label]"]', function(event) {
				var input = $(this),
					instance = input.parents('.instance:first'),
					title = instance.find(settings.headers).find('span:first'),
					description = title.find('i');

				// Create description
				if(description.size() == 0) {
					description = $('').appendTo(title);
				}

				// Update description
				description.text($.trim(input.val()));
			});

			// Build field indexes
			duplicator.on('constructstop.duplicator refresh.duplicator', '.instance', function(event) {
				var instance = $(this),
					position = duplicator.find('.instance').index(instance);

				// Loop over named fields
				instance.find('*[name]').each(function() {
					var field = $(this),
						exp = /\[\-?[0-9]+\]/,
						name = field.attr('name');

					// Set index
					if(exp.test(name)) {
						field.attr('name', name.replace(exp, '[' + position + ']'));
					}
				});
			});

			// Refresh field indexes
			duplicator.on('orderstop.orderable', function(event) {
				duplicator.find('.instance').trigger('refresh.duplicator');
			});

			// Activate controls
			duplicator.on('constructshow.duplicator', '.instance', function(event) {
				if(duplicator.find('.instance').size() == 1) {
					duplicator.trigger('activate.collapsible');
				}
			});

			// Deactivate controls
			duplicator.on('destructstart.duplicator', '.instance', function(event) {
				if(duplicator.find('.instance').size() == 1) {
					duplicator.trigger('deactivate.collapsible');
				}
			});

		/*-------------------------------------------------------------------*/

			// Build interface
			duplicator.insertBefore(object).prepend(object);
			headers.wrapInner('').each(function() {
				$(this).nextAll().wrapAll('
');
			});

			// Constructable interface
			if(settings.constructable === true) {
				duplicator.addClass('constructable');
				controls.append(selector).append(constructor).appendTo(duplicator);

				// Populate selector
				templates.each(function() {
					var template = $(this),
						title = template.find(settings.headers).text(),
						value = template.attr('data-type');

					template.trigger('constructstart.duplicator');

					// Check type connection
					if(!value) {
						value = title;
						template.attr('data-type', value);
					}

					// Append options
					$('<option />', {
						text: title,
						value: value
					}).appendTo(selector);

					// Check uniqueness
					template.trigger('constructstop.duplicator');
				}).removeClass('template').addClass('instance').remove();
			}

			// Select default
			if(settings.preselect != false) {
				selector.find('option[value="' + settings.preselect + '"]').attr('selected', true);
			}

			// Single template
			if(templates.size() <= 1) {
				selector.hide();

				// Single unique template
				if(templates.is('.unique')) {
					controls.find('a.constructor').trigger('click.duplicator', [0]);
					controls.hide();
				}
			}

			// Destructable interface
			if(settings.destructable === true) {
				duplicator.addClass('destructable');
				headers.append('' + Symphony.Language.get('Remove item') + '')
			}

			// Collapsible interface
			if(settings.collapsible) {
				duplicator.symphonyCollapsible({
					items: '.instance',
					handles: '.header span'
				});
			}

			// Orderable interface
			if(settings.orderable) {
				duplicator.symphonyOrderable({
					items: '.instance',
					handles: '.header',
					ignore: 'span'
				});
			}

			// Initialise existing instances
			instances.trigger('constructstop.duplicator');
			instances.find('input[name*="[label]"]').trigger('keyup.duplicator');
		});

	/*-----------------------------------------------------------------------*/

		return objects;
	};

})(jQuery.noConflict());
<|endoftext|>


<commit_before>// This file defines a ProseMirror selection subclass that models
// table cell selections. The table plugin needs to be active to wire
// in the user interaction part of table selections (so that you
// actually get such selections when you select across cells).

const {Selection, TextSelection, NodeSelection, SelectionRange} = require("prosemirror-state")
const {Decoration, DecorationSet} = require("prosemirror-view")
const {Fragment, Slice} = require("prosemirror-model")

const {inSameTable, pointsAtCell, setAttr} = require("./util")
const {TableMap} = require("./tablemap")

class CellSelection extends Selection {
  // :: (ResolvedPos, ?ResolvedPos)
  // A table selection is identified by its anchor and head cells. The
  // positions given to this constructor should point _before_ two
  // cells in the same table. They may be the same, to select a single
  // cell.
  constructor($anchorCell, $headCell = $anchorCell) {
    let table = $anchorCell.node(-1), map = TableMap.get(table), start = $anchorCell.start(-1)
    let rect = map.rectBetween($anchorCell.pos - start, $headCell.pos - start)
    let doc = $anchorCell.node(0)
    let cells = map.cellsInRect(rect).filter(p => p != $headCell.pos - start)
    // Make the head cell the first range, so that it counts as the
    // primary part of the selection
    cells.unshift($headCell.pos - start)
    let ranges = cells.map(pos => {
      let cell = table.nodeAt(pos), from = pos + start + 1
      return new SelectionRange(doc.resolve(from), doc.resolve(from + cell.content.size))
    })
    super(ranges[0].$from, ranges[0].$to, ranges)
    this.$anchorCell = $anchorCell
    this.$headCell = $headCell
  }

  map(doc, mapping) {
    let $anchorCell = doc.resolve(mapping.map(this.$anchorCell.pos))
    let $headCell = doc.resolve(mapping.map(this.$headCell.pos))
    if (pointsAtCell($anchorCell) && pointsAtCell($headCell) && inSameTable($anchorCell, $headCell)) {
      let tableChanged = this.$anchorCell.node(-1) != $anchorCell.node(-1)
      if (tableChanged && this.isColSelection())
        return CellSelection.colSelection($anchorCell, $headCell)
      else if (tableChanged && this.isRowSelection())
        return CellSelection.rowSelection($anchorCell, $headCell)
      else
        return new CellSelection($anchorCell, $headCell)
    }
    return TextSelection.between($anchorCell, $headCell)
  }

  // :: () → Slice
  // Returns a rectangular slice of table rows containing the selected
  // cells.
  content() {
    let table = this.$anchorCell.node(-1), map = TableMap.get(table), start = this.$anchorCell.start(-1)
    let rect = map.rectBetween(this.$anchorCell.pos - start, this.$headCell.pos - start)
    let seen = [], rows = []
    for (let row = rect.top; row < rect.bottom; row++) {
      let rowContent = []
      for (let index = row * map.width + rect.left, col = rect.left; col < rect.right; col++, index++) {
        let pos = map.map[index]
        if (seen.indexOf(pos) == -1) {
          seen.push(pos)
          let cellRect = map.findCell(pos), cell = table.nodeAt(pos)
          if (cellRect.left < rect.left || cellRect.right > rect.right) {
            let attrs = setAttr(cell.attrs, "colspan", Math.min(cellRect.right, rect.right) - Math.max(cellRect.left, rect.left))
            if (cellRect.left < rect.left) cell = cell.type.createAndFill(attrs)
            else cell = cell.type.create(attrs, cell.content)
          }
          if (cellRect.top < rect.top || cellRect.bottom > rect.bottom) {
            let attrs = setAttr(cell.attrs, "rowspan", Math.min(cellRect.bottom, rect.bottom) - Math.max(cellRect.top, rect.top))
            if (cellRect.top < rect.top) cell = cell.type.createAndFill(attrs)
            else cell = cell.type.create(attrs, cell.content)
          }
          rowContent.push(cell)
        }
      }
      rows.push(table.child(row).copy(Fragment.from(rowContent)))
    }
    return new Slice(Fragment.from(rows), 1, 1)
  }

  replace(tr, content = Slice.empty) {
    let mapFrom = tr.steps.length, ranges = this.ranges
    for (let i = 0; i < ranges.length; i++) {
      let {$from, $to} = ranges[i], mapping = tr.mapping.slice(mapFrom)
      tr.replace(mapping.map($from.pos), mapping.map($to.pos), i ? Slice.empty : content)
    }
    let sel = Selection.findFrom(tr.doc.resolve(tr.mapping.slice(mapFrom).map(this.to)), -1)
    if (sel) tr.setSelection(sel)
  }

  replaceWith(tr, node) {
    this.replace(tr, new Slice(Fragment.from(node), 0, 0))
  }

  forEachCell(f) {
    let table = this.$anchorCell.node(-1), map = TableMap.get(table), start = this.$anchorCell.start(-1)
    let cells = map.cellsInRect(map.rectBetween(this.$anchorCell.pos - start, this.$headCell.pos - start))
    for (let i = 0; i < cells.length; i++)
      f(table.nodeAt(cells[i]), start + cells[i])
  }

  // :: () → bool
  // True if this selection goes all the way from the left to the
  // right of the table.
  isRowSelection() {
    let anchorTop = this.$anchorCell.index(-1), headTop = this.$headCell.index(-1)
    if (Math.min(anchorTop, headTop) > 0) return false
    let anchorBot = anchorTop + this.$anchorCell.nodeAfter.attrs.rowspan,
        headBot = headTop + this.$headCell.nodeAfter.attrs.rowspan
    return Math.max(anchorBot, headBot) == this.$headCell.node(-1).childCount
  }

  // :: (ResolvedPos, ?ResolvedPos) → CellSelection
  // Returns the smallest row selection that covers the given anchor
  // and head cell.
  static rowSelection($anchorCell, $headCell = $anchorCell) {
    let map = TableMap.get($anchorCell.node(-1)), start = $anchorCell.start(-1)
    let anchorRect = map.findCell($anchorCell.pos - start), headRect = map.findCell($headCell.pos - start)
    let doc = $anchorCell.node(0)
    if (anchorRect.top <= headRect.top) {
      if (anchorRect.top > 0)
        $anchorCell = doc.resolve(start + map.map[anchorRect.left])
      if (headRect.bottom < map.height)
        $headCell = doc.resolve(start + map.map[map.width * (map.height - 1) + headRect.right - 1])
    } else {
      if (headRect.top > 0)
        $headCell = doc.resolve(start + map.map[headRect.left])
      if (anchorRect.bottom < map.height)
        $anchorCell = doc.resolve(start + map.map[map.width * (map.height - 1) + anchorRect.right - 1])
    }
    return new CellSelection($anchorCell, $headCell)
  }

  // :: () → bool
  // True if this selection goes all the way from the top to the
  // bottom of the table.
  isColSelection() {
    let map = TableMap.get(this.$anchorCell.node(-1)), start = this.$anchorCell.start(-1)
    let anchorLeft = map.colCount(this.$anchorCell.pos - start),
        headLeft = map.colCount(this.$headCell.pos - start)
    if (Math.min(anchorLeft, headLeft) > 0) return false
    let anchorRight = anchorLeft + this.$anchorCell.nodeAfter.attrs.colspan,
        headRight = headLeft + this.$headCell.nodeAfter.attrs.colspan
    return Math.max(anchorRight, headRight) == map.width
  }

  eq(other) {
    return other instanceof CellSelection && other.$anchorCell.pos == this.$anchorCell.pos &&
      other.$headCell.pos == this.$headCell.pos
  }

  // :: (ResolvedPos, ?ResolvedPos) → CellSelection
  // Returns the smallest column selection that covers the given anchor
  // and head cell.
  static colSelection($anchorCell, $headCell = $anchorCell) {
    let map = TableMap.get($anchorCell.node(-1)), start = $anchorCell.start(-1)
    let anchorRect = map.findCell($anchorCell.pos - start), headRect = map.findCell($headCell.pos - start)
    let doc = $anchorCell.node(0)
    if (anchorRect.left <= headRect.left) {
      if (anchorRect.left > 0)
        $anchorCell = doc.resolve(start + map.map[anchorRect.top * map.width])
      if (headRect.right < map.width)
        $headCell = doc.resolve(start + map.map[map.width * (headRect.top + 1) - 1])
    } else {
      if (headRect.left > 0)
        $headCell = doc.resolve(start + map.map[headRect.top * map.width])
      if (anchorRect.right < map.width)
        $anchorCell = doc.resolve(start + map.map[map.width * (anchorRect.top + 1) - 1])
    }
    return new CellSelection($anchorCell, $headCell)
  }

  toJSON() {
    return {type: "cell", anchor: this.$anchorCell.pos, head: this.$headCell.pos}
  }

  static fromJSON(doc, json) {
    return new CellSelection(doc.resolve(json.anchor), doc.resolve(json.head))
  }

  // :: (Node, number, ?number) → CellSelection
  static create(doc, anchorCell, headCell = anchorCell) {
    return new CellSelection(doc.resolve(anchorCell), doc.resolve(headCell))
  }

  getBookmark() { return new CellBookmark(this.$anchorCell.pos, this.$headCell.pos) }
}
exports.CellSelection = CellSelection

CellSelection.prototype.visible = false

Selection.jsonID("cell", CellSelection)

class CellBookmark {
  constructor(anchor, head) {
    this.anchor = anchor
    this.head = head
  }
  map(mapping) {
    return new CellBookmark(mapping.map(this.anchor), mapping.map(this.head))
  }
  resolve(doc) {
    let $anchorCell = doc.resolve(this.anchor), $headCell = doc.resolve(this.head)
    if ($anchorCell.parent.type.spec.tableRole == "row" &&
        $headCell.parent.type.spec.tableRole == "row" &&
        $anchorCell.index() < $anchorCell.parent.childCount &&
        $headCell.index() < $headCell.parent.childCount &&
        inSameTable($anchorCell, $headCell))
      return new CellSelection($anchorCell, $headCell)
    else
      return Selection.near($headCell, 1)
  }
}

exports.drawCellSelection = function(state) {
  if (!(state.selection instanceof CellSelection)) return null
  let cells = []
  state.selection.forEachCell((node, pos) => {
    cells.push(Decoration.node(pos, pos + node.nodeSize, {class: "selectedCell"}))
  })
  return DecorationSet.create(state.doc, cells)
}

function isCellBoundarySelection({$from, $to}) {
  if ($from.pos == $to.pos || $from.pos < $from.pos - 6) return false // Cheap elimination
  let afterFrom = $from.pos, beforeTo = $to.pos, depth = $from.depth
  for (; depth >= 0; depth--, afterFrom++)
    if ($from.after(depth + 1) < $from.end(depth)) break
  for (let d = $to.depth; d >= 0; d--, beforeTo--)
    if ($to.before(d + 1) > $to.start(d)) break
  return afterFrom == beforeTo && /row|table/.test($from.node(depth).type.spec.tableRole)
}

exports.normalizeSelection = function(state, tr) {
  let sel = (tr || state).selection, doc = (tr || state).doc, normalize, role
  if (sel instanceof NodeSelection && (role = sel.node.type.spec.tableRole)) {
    if (role == "cell" || role == "header_cell") {
      normalize = CellSelection.create(doc, sel.from)
    } else if (role == "row") {
      let $cell = doc.resolve(sel.from + 1)
      normalize = CellSelection.rowSelection($cell, $cell)
    } else {
      let map = TableMap.get(sel.node), start = sel.from + 1
      let lastCell = start + map.map[map.width * map.height - 1]
      normalize = CellSelection.create(doc, start + 1, lastCell)
    }
  } else if (sel instanceof TextSelection && isCellBoundarySelection(sel)) {
    normalize = TextSelection.create(doc, sel.from)
  }
  if (normalize)
    (tr || (tr = state.tr)).setSelection(normalize)
  return tr
}
<commit_msg>[cellselection] fixed isRowSelection and isColSelection naming<commit_after>// This file defines a ProseMirror selection subclass that models
// table cell selections. The table plugin needs to be active to wire
// in the user interaction part of table selections (so that you
// actually get such selections when you select across cells).

const {Selection, TextSelection, NodeSelection, SelectionRange} = require("prosemirror-state")
const {Decoration, DecorationSet} = require("prosemirror-view")
const {Fragment, Slice} = require("prosemirror-model")

const {inSameTable, pointsAtCell, setAttr} = require("./util")
const {TableMap} = require("./tablemap")

class CellSelection extends Selection {
  // :: (ResolvedPos, ?ResolvedPos)
  // A table selection is identified by its anchor and head cells. The
  // positions given to this constructor should point _before_ two
  // cells in the same table. They may be the same, to select a single
  // cell.
  constructor($anchorCell, $headCell = $anchorCell) {
    let table = $anchorCell.node(-1), map = TableMap.get(table), start = $anchorCell.start(-1)
    let rect = map.rectBetween($anchorCell.pos - start, $headCell.pos - start)
    let doc = $anchorCell.node(0)
    let cells = map.cellsInRect(rect).filter(p => p != $headCell.pos - start)
    // Make the head cell the first range, so that it counts as the
    // primary part of the selection
    cells.unshift($headCell.pos - start)
    let ranges = cells.map(pos => {
      let cell = table.nodeAt(pos), from = pos + start + 1
      return new SelectionRange(doc.resolve(from), doc.resolve(from + cell.content.size))
    })
    super(ranges[0].$from, ranges[0].$to, ranges)
    this.$anchorCell = $anchorCell
    this.$headCell = $headCell
  }

  map(doc, mapping) {
    let $anchorCell = doc.resolve(mapping.map(this.$anchorCell.pos))
    let $headCell = doc.resolve(mapping.map(this.$headCell.pos))
    if (pointsAtCell($anchorCell) && pointsAtCell($headCell) && inSameTable($anchorCell, $headCell)) {
      let tableChanged = this.$anchorCell.node(-1) != $anchorCell.node(-1)
      if (tableChanged && this.isRowSelection())
        return CellSelection.colSelection($anchorCell, $headCell)
      else if (tableChanged && this.isColSelection())
        return CellSelection.rowSelection($anchorCell, $headCell)
      else
        return new CellSelection($anchorCell, $headCell)
    }
    return TextSelection.between($anchorCell, $headCell)
  }

  // :: () → Slice
  // Returns a rectangular slice of table rows containing the selected
  // cells.
  content() {
    let table = this.$anchorCell.node(-1), map = TableMap.get(table), start = this.$anchorCell.start(-1)
    let rect = map.rectBetween(this.$anchorCell.pos - start, this.$headCell.pos - start)
    let seen = [], rows = []
    for (let row = rect.top; row < rect.bottom; row++) {
      let rowContent = []
      for (let index = row * map.width + rect.left, col = rect.left; col < rect.right; col++, index++) {
        let pos = map.map[index]
        if (seen.indexOf(pos) == -1) {
          seen.push(pos)
          let cellRect = map.findCell(pos), cell = table.nodeAt(pos)
          if (cellRect.left < rect.left || cellRect.right > rect.right) {
            let attrs = setAttr(cell.attrs, "colspan", Math.min(cellRect.right, rect.right) - Math.max(cellRect.left, rect.left))
            if (cellRect.left < rect.left) cell = cell.type.createAndFill(attrs)
            else cell = cell.type.create(attrs, cell.content)
          }
          if (cellRect.top < rect.top || cellRect.bottom > rect.bottom) {
            let attrs = setAttr(cell.attrs, "rowspan", Math.min(cellRect.bottom, rect.bottom) - Math.max(cellRect.top, rect.top))
            if (cellRect.top < rect.top) cell = cell.type.createAndFill(attrs)
            else cell = cell.type.create(attrs, cell.content)
          }
          rowContent.push(cell)
        }
      }
      rows.push(table.child(row).copy(Fragment.from(rowContent)))
    }
    return new Slice(Fragment.from(rows), 1, 1)
  }

  replace(tr, content = Slice.empty) {
    let mapFrom = tr.steps.length, ranges = this.ranges
    for (let i = 0; i < ranges.length; i++) {
      let {$from, $to} = ranges[i], mapping = tr.mapping.slice(mapFrom)
      tr.replace(mapping.map($from.pos), mapping.map($to.pos), i ? Slice.empty : content)
    }
    let sel = Selection.findFrom(tr.doc.resolve(tr.mapping.slice(mapFrom).map(this.to)), -1)
    if (sel) tr.setSelection(sel)
  }

  replaceWith(tr, node) {
    this.replace(tr, new Slice(Fragment.from(node), 0, 0))
  }

  forEachCell(f) {
    let table = this.$anchorCell.node(-1), map = TableMap.get(table), start = this.$anchorCell.start(-1)
    let cells = map.cellsInRect(map.rectBetween(this.$anchorCell.pos - start, this.$headCell.pos - start))
    for (let i = 0; i < cells.length; i++)
      f(table.nodeAt(cells[i]), start + cells[i])
  }

  // :: () → bool
  // True if this selection goes all the way from the top to the
  // bottom of the table.
  isColSelection() {
    let anchorTop = this.$anchorCell.index(-1), headTop = this.$headCell.index(-1)
    if (Math.min(anchorTop, headTop) > 0) return false
    let anchorBot = anchorTop + this.$anchorCell.nodeAfter.attrs.rowspan,
        headBot = headTop + this.$headCell.nodeAfter.attrs.rowspan
    return Math.max(anchorBot, headBot) == this.$headCell.node(-1).childCount
  }

  // :: (ResolvedPos, ?ResolvedPos) → CellSelection
  // Returns the smallest row selection that covers the given anchor
  // and head cell.
  static rowSelection($anchorCell, $headCell = $anchorCell) {
    let map = TableMap.get($anchorCell.node(-1)), start = $anchorCell.start(-1)
    let anchorRect = map.findCell($anchorCell.pos - start), headRect = map.findCell($headCell.pos - start)
    let doc = $anchorCell.node(0)
    if (anchorRect.top <= headRect.top) {
      if (anchorRect.top > 0)
        $anchorCell = doc.resolve(start + map.map[anchorRect.left])
      if (headRect.bottom < map.height)
        $headCell = doc.resolve(start + map.map[map.width * (map.height - 1) + headRect.right - 1])
    } else {
      if (headRect.top > 0)
        $headCell = doc.resolve(start + map.map[headRect.left])
      if (anchorRect.bottom < map.height)
        $anchorCell = doc.resolve(start + map.map[map.width * (map.height - 1) + anchorRect.right - 1])
    }
    return new CellSelection($anchorCell, $headCell)
  }

  // :: () → bool
  // True if this selection goes all the way from the left to the
  // right of the table.
  isRowSelection() {
    let map = TableMap.get(this.$anchorCell.node(-1)), start = this.$anchorCell.start(-1)
    let anchorLeft = map.colCount(this.$anchorCell.pos - start),
        headLeft = map.colCount(this.$headCell.pos - start)
    if (Math.min(anchorLeft, headLeft) > 0) return false
    let anchorRight = anchorLeft + this.$anchorCell.nodeAfter.attrs.colspan,
        headRight = headLeft + this.$headCell.nodeAfter.attrs.colspan
    return Math.max(anchorRight, headRight) == map.width
  }

  eq(other) {
    return other instanceof CellSelection && other.$anchorCell.pos == this.$anchorCell.pos &&
      other.$headCell.pos == this.$headCell.pos
  }

  // :: (ResolvedPos, ?ResolvedPos) → CellSelection
  // Returns the smallest column selection that covers the given anchor
  // and head cell.
  static colSelection($anchorCell, $headCell = $anchorCell) {
    let map = TableMap.get($anchorCell.node(-1)), start = $anchorCell.start(-1)
    let anchorRect = map.findCell($anchorCell.pos - start), headRect = map.findCell($headCell.pos - start)
    let doc = $anchorCell.node(0)
    if (anchorRect.left <= headRect.left) {
      if (anchorRect.left > 0)
        $anchorCell = doc.resolve(start + map.map[anchorRect.top * map.width])
      if (headRect.right < map.width)
        $headCell = doc.resolve(start + map.map[map.width * (headRect.top + 1) - 1])
    } else {
      if (headRect.left > 0)
        $headCell = doc.resolve(start + map.map[headRect.top * map.width])
      if (anchorRect.right < map.width)
        $anchorCell = doc.resolve(start + map.map[map.width * (anchorRect.top + 1) - 1])
    }
    return new CellSelection($anchorCell, $headCell)
  }

  toJSON() {
    return {type: "cell", anchor: this.$anchorCell.pos, head: this.$headCell.pos}
  }

  static fromJSON(doc, json) {
    return new CellSelection(doc.resolve(json.anchor), doc.resolve(json.head))
  }

  // :: (Node, number, ?number) → CellSelection
  static create(doc, anchorCell, headCell = anchorCell) {
    return new CellSelection(doc.resolve(anchorCell), doc.resolve(headCell))
  }

  getBookmark() { return new CellBookmark(this.$anchorCell.pos, this.$headCell.pos) }
}
exports.CellSelection = CellSelection

CellSelection.prototype.visible = false

Selection.jsonID("cell", CellSelection)

class CellBookmark {
  constructor(anchor, head) {
    this.anchor = anchor
    this.head = head
  }
  map(mapping) {
    return new CellBookmark(mapping.map(this.anchor), mapping.map(this.head))
  }
  resolve(doc) {
    let $anchorCell = doc.resolve(this.anchor), $headCell = doc.resolve(this.head)
    if ($anchorCell.parent.type.spec.tableRole == "row" &&
        $headCell.parent.type.spec.tableRole == "row" &&
        $anchorCell.index() < $anchorCell.parent.childCount &&
        $headCell.index() < $headCell.parent.childCount &&
        inSameTable($anchorCell, $headCell))
      return new CellSelection($anchorCell, $headCell)
    else
      return Selection.near($headCell, 1)
  }
}

exports.drawCellSelection = function(state) {
  if (!(state.selection instanceof CellSelection)) return null
  let cells = []
  state.selection.forEachCell((node, pos) => {
    cells.push(Decoration.node(pos, pos + node.nodeSize, {class: "selectedCell"}))
  })
  return DecorationSet.create(state.doc, cells)
}

function isCellBoundarySelection({$from, $to}) {
  if ($from.pos == $to.pos || $from.pos < $from.pos - 6) return false // Cheap elimination
  let afterFrom = $from.pos, beforeTo = $to.pos, depth = $from.depth
  for (; depth >= 0; depth--, afterFrom++)
    if ($from.after(depth + 1) < $from.end(depth)) break
  for (let d = $to.depth; d >= 0; d--, beforeTo--)
    if ($to.before(d + 1) > $to.start(d)) break
  return afterFrom == beforeTo && /row|table/.test($from.node(depth).type.spec.tableRole)
}

exports.normalizeSelection = function(state, tr) {
  let sel = (tr || state).selection, doc = (tr || state).doc, normalize, role
  if (sel instanceof NodeSelection && (role = sel.node.type.spec.tableRole)) {
    if (role == "cell" || role == "header_cell") {
      normalize = CellSelection.create(doc, sel.from)
    } else if (role == "row") {
      let $cell = doc.resolve(sel.from + 1)
      normalize = CellSelection.rowSelection($cell, $cell)
    } else {
      let map = TableMap.get(sel.node), start = sel.from + 1
      let lastCell = start + map.map[map.width * map.height - 1]
      normalize = CellSelection.create(doc, start + 1, lastCell)
    }
  } else if (sel instanceof TextSelection && isCellBoundarySelection(sel)) {
    normalize = TextSelection.create(doc, sel.from)
  }
  if (normalize)
    (tr || (tr = state.tr)).setSelection(normalize)
  return tr
}
<|endoftext|>