<commit_before>angular.module('app.controllers', [])
    // Controller for Tag View
    .controller('TagCtrl', function ($scope, $rootScope, $cordovaBluetoothLE, Log, settings) {
        $scope.devices = {};
        $scope.scanDevice = true;
        $scope.noDevice = true;
        $scope.dev1Connected = false;
        $scope.dev2Connected = false;
        $scope.currentDevice1 = null;
        $scope.currentDevice2 = null;
        $scope.firstScan = true;
        $scope.barometer = { temperatureDev1: "FREEZING HELL", pressureDev1: "Inside of Jupiter",
                             temperatureDev2: "FREEZING HELL", pressureDev2: "Inside of Jupiter" };
        var barometer = {
            service: "F000AA40-0451-4000-B000-000000000000",
            data: "F000AA41-0451-4000-B000-000000000000",
            notification: "F0002902-0451-4000-B000-000000000000",
            configuration: "F000AA42-0451-4000-B000-000000000000",
            period: "F000AA43-0451-4000-B000-000000000000"
        };

        function getLastCon() {
            return localStorage.getItem("lastCon");
        }

        function setLastCon(deviceId) {
            localStorage.setItem("lastCon", deviceId);
        }

        $scope.startScan = function () {
            var params = {
                services: [],
                allowDuplicates: false,
                scanTimeout: settings.settings.scanDuration * 1000
            };

            if (window.cordova) {
                params.scanMode = bluetoothle.SCAN_MODE_LOW_POWER;
                params.matchMode = bluetoothle.MATCH_MODE_STICKY;
                params.matchNum = bluetoothle.MATCH_NUM_ONE_ADVERTISEMENT;
                //params.callbackType = bluetoothle.CALLBACK_TYPE_FIRST_MATCH;
            }

            Log.add("Start Scan : " + JSON.stringify(params));

            $cordovaBluetoothLE.startScan(params).then(function (obj) {
                Log.add("Start Scan Auto Stop : " + JSON.stringify(obj));
                $scope.firstScan = false;
                $scope.scanDevice = false;
            }, function (obj) {
                Log.add("Start Scan Error : " + JSON.stringify(obj));
            }, function (device) {
                Log.add("Start Scan Success : " + JSON.stringify(device));

                if (device.status == "scanStarted") return;

                $scope.noDevice = false;
                $scope.devices[device.address] = device;
                $scope.devices[device.address].services = {};
                console.log(JSON.stringify($scope.devices));

                if (device.address == getLastCon() && $scope.firstScan) {
                    $scope.connect(device);
                    $scope.firstScan = false;
                }
            });
        };

        $scope.connect = function (device) {

            var onConnect = function (obj) {

                Log.add("Connect Success : " + JSON.stringify(obj));
                // Save deviceId as last connected one
                setLastCon(device.address);

                if($scope.dev1Connected == false){
                    $scope.dev1Connected = true;
                    $scope.currentDevice1 = device;
                    $scope.barometer.temperatureDev1 = "FREEZING HELL";
                    $scope.barometer.pressureDev1 = "Inside of Jupiter";
                }else{
                    $scope.dev2Connected = true;
                    $scope.currentDevice2 = device;
                    $scope.barometer.temperatureDev2 = "FREEZING HELL";
                    $scope.barometer.pressureDev2 = "Inside of Jupiter";
                }

                //Subscribe to barometer service
                var params = {
                    address: device.address,
                    service: barometer.service,
                    characteristic: barometer.data,
                    timeout: 5000
                };

                Log.add("Subscribe : " + JSON.stringify(params));

                $cordovaBluetoothLE.subscribe(params).then(function (obj) {
                    Log.add("Subscribe Auto Unsubscribe : " + JSON.stringify(obj));
                }, function (obj) {
                    Log.add("Subscribe Error : " + JSON.stringify(obj));
                }, function (obj) {
                    //Log.add("Subscribe Success : " + JSON.stringify(obj));

                    if (obj.status == "subscribedResult") {
                        //Log.add("Subscribed Result");
                        onBarometerData(obj,device);
                        var bytes = $cordovaBluetoothLE.encodedStringToBytes(obj.value);
                        Log.add("Subscribe Success ASCII (" + bytes.length + "): " + $cordovaBluetoothLE.bytesToString(bytes));
                        Log.add("HEX (" + bytes.length + "): " + $cordovaBluetoothLE.bytesToHex(bytes));
                    } else if (obj.status == "subscribed") {
                        Log.add("Subscribed");
                        //Turn on barometer
                        var barometerConfig = new Uint8Array(1);
                        barometerConfig[0] = 0x01;
                        var params = {
                            address: device.address,
                            service: barometer.service,
                            characteristic: barometer.configuration,
                            value: $cordovaBluetoothLE.bytesToEncodedString(barometerConfig),
                            timeout: 5000
                        };

                        Log.add("Write : " + JSON.stringify(params));

                        $cordovaBluetoothLE.write(params).then(function (obj) {
                            Log.add("Write Success : " + JSON.stringify(obj));
                        }, function (obj) {
                            Log.add("Write Error : " + JSON.stringify(obj));
                        });
                    } else {
                        Log.add("Unexpected Subscribe Status");
                    }
                });

            };
            var params = { address: device.address, timeout: 10000 };

            Log.add("Connect : " + JSON.stringify(params));

            $cordovaBluetoothLE.connect(params).then(null, function (obj) {
                Log.add("Connect Error : " + JSON.stringify(obj));
                $scope.close(params.address); //Best practice is to close on connection error
            }, function () {
                $scope.discover(device.address, onConnect);
            });

        };

        $rootScope.$on("bleEnabledEvent", function () {
            $scope.startScan();
        })

        $scope.refreshSensortags = function () {
            $scope.devices = {};
            $scope.startScan();
            $scope.noDevice = true;
            $scope.scanDevice = true;
        }

        $scope.stopScan = function () {
            $scope.scanDevice = false;
            $scope.firstScan = false;
            $cordovaBluetoothLE.stopScan().then(function (obj) {
                Log.add("Stop Scan Success : " + JSON.stringify(obj));
            }, function (obj) {
                Log.add("Stop Scan Error : " + JSON.stringify(obj));
            });
        }

        $scope.close = function (address) {
            var params = { address: address };

            Log.add("Close : " + JSON.stringify(params));

            $cordovaBluetoothLE.close(params).then(function (obj) {
                Log.add("Close Success : " + JSON.stringify(obj));
            }, function (obj) {
                Log.add("Close Error : " + JSON.stringify(obj));
            });

            var device = $scope.devices[address];
            device.services = {};
        };

        function onBarometerData(obj, device) {
            var a = $cordovaBluetoothLE.encodedStringToBytes(obj.value);

            function sensorBarometerConvert(data) {
                return (data / 100);
            }

            if($scope.currentDevice1.address == device.address){
                $scope.barometer.temperatureDev1 = sensorBarometerConvert(a[0] | (a[1] << 8) | (a[2] << 16)) + "°C";
                $scope.barometer.pressureDev1 = sensorBarometerConvert(a[3] | (a[4] << 8) | (a[5] << 16)) + "hPa";
            }else if ($scope.currentDevice2.address == device.address){
                $scope.barometer.temperatureDev2 = sensorBarometerConvert(a[0] | (a[1] << 8) | (a[2] << 16)) + "°C";
                $scope.barometer.pressureDev2 = sensorBarometerConvert(a[3] | (a[4] << 8) | (a[5] << 16)) + "hPa";
            }else{
                Log.add("onBarometerData: no matching device" + JSON.stringify(device.address));
            }
        }

        $scope.disconnect = function (device) {
            if ($scope.currentDevice1.address == device.address){
                $scope.dev1Connected = false;
                $scope.close($scope.currentDevice1.address);
            } else if ($scope.dev2Connected && $scope.currentDevice2.address == device.address){
                $scope.dev2Connected = false;
                $scope.close($scope.currentDevice2.address);
            }
        }

        $scope.isConnected = function (device) {
            if ($scope.dev1Connected && $scope.currentDevice1.address == device.address){
                return true;
            } else if ($scope.dev2Connected && $scope.currentDevice2.address == device.address){
                return true;
            } else {
                return false;
            }
        }

        $scope.discover = function (address, afterFunction) {
            var params = {
                address: address,
                timeout: 10000
            };

            Log.add("Discover : " + JSON.stringify(params));

            $cordovaBluetoothLE.discover(params).then(function (obj) {
                Log.add("Discover Success : " + JSON.stringify(obj));

                var device = $scope.devices[obj.address];

                var services = obj.services;

                for (var i = 0; i < services.length; i++) {
                    var service = services[i];

                    addService(service, device);

                    var serviceNew = device.services[service.uuid];

                    var characteristics = service.characteristics;

                    for (var j = 0; j < characteristics.length; j++) {
                        var characteristic = characteristics[j];

                        addCharacteristic(characteristic, serviceNew);

                        var characteristicNew = serviceNew.characteristics[characteristic.uuid];

                        var descriptors = characteristic.descriptors;

                        for (var k = 0; k < descriptors.length; k++) {
                            var descriptor = descriptors[k];

                            addDescriptor(descriptor, characteristicNew);
                        }

                    }
                }
                if (afterFunction != undefined) {
                    afterFunction();
                }
            }, function (obj) {
                Log.add("Discover Error : " + JSON.stringify(obj));
            });
        };
        function addService(service, device) {
            if (device.services[service.uuid] !== undefined) {
                return;
            }
            device.services[service.uuid] = { uuid: service.uuid, characteristics: {} };
        }

        function addCharacteristic(characteristic, service) {
            if (service.characteristics[characteristic.uuid] !== undefined) {
                return;
            }
            service.characteristics[characteristic.uuid] = { uuid: characteristic.uuid, descriptors: {}, properties: characteristic.properties };
        }

        function addDescriptor(descriptor, characteristic) {
            if (characteristic.descriptors[descriptor.uuid] !== undefined) {
                return;
            }
            characteristic.descriptors[descriptor.uuid] = { uuid: descriptor.uuid };
        }

        $scope.firstScan = false;
        if (settings.settings.startReconnect == "true" || settings.settings.startReconnect === true) {
            $scope.firstScan = true;
        }

        $rootScope.$on("bleEnabledEvent", function () {
            $scope.startScan();
        });

    })

    // Controller for Settings
    .controller('SettingsCtrl', function ($scope, settings) {

        // Link the scope settings to the settings service
        $scope.settings = settings.settings;

        // Scope update function is the settings service persist function
        $scope.update = settings.persistSettings;
        //
        $scope.$on('volumeupbutton', function () {
            $scope.$apply(function () {									// angular doesn't fire $apply on the events so if $broadcast is called outside angular's context, you are going to need to $apply by hand.

                // Update Volume + checks for valid values (0 to 100)
                var vol = settings.settings.volume;
                var up = 10;

                // Catch if volume 91 to 100, update to max 100
                if (vol > 90)
                    up = 100 - vol;
                vol = vol + up;

                // Save setting
                settings.setSetting("volume", vol);

                // Initialise GUI with saved setting values
                settings.settings.volume = vol;

            });
        });

        $scope.$on('volumedownbutton', function () {
            $scope.$apply(function () {									// angular doesn't fire $apply on the events so if $broadcast is called outside angular's context, you are going to need to $apply by hand.

                // Update Volume + checks for valid values (0 to 100)
                var vol = settings.settings.volume;
                var down = 10;

                // Catch if volume 9 to 0, update to min 0
                if (vol < 10)
                    down = vol;
                vol = vol - down;

                // Save setting
                settings.setSetting("volume", vol);

                // Initialise GUI with saved setting values
                settings.settings.volume = vol;

            });
        });
    });
<commit_msg>limited max number of connections to two<commit_after>angular.module('app.controllers', [])
    // Controller for Tag View
    .controller('TagCtrl', function ($scope, $rootScope, $cordovaBluetoothLE, Log, settings) {
        $scope.devices = {};
        $scope.scanDevice = true;
        $scope.noDevice = true;
        $scope.dev1Connected = false;
        $scope.dev2Connected = false;
        $scope.currentDevice1 = null;
        $scope.currentDevice2 = null;
        $scope.firstScan = true;
        $scope.barometer = { temperatureDev1: "FREEZING HELL", pressureDev1: "Inside of Jupiter",
                             temperatureDev2: "FREEZING HELL", pressureDev2: "Inside of Jupiter" };
        var barometer = {
            service: "F000AA40-0451-4000-B000-000000000000",
            data: "F000AA41-0451-4000-B000-000000000000",
            notification: "F0002902-0451-4000-B000-000000000000",
            configuration: "F000AA42-0451-4000-B000-000000000000",
            period: "F000AA43-0451-4000-B000-000000000000"
        };

        function getLastCon() {
            return localStorage.getItem("lastCon");
        }

        function setLastCon(deviceId) {
            localStorage.setItem("lastCon", deviceId);
        }

        $scope.startScan = function () {
            var params = {
                services: [],
                allowDuplicates: false,
                scanTimeout: settings.settings.scanDuration * 1000
            };

            if (window.cordova) {
                params.scanMode = bluetoothle.SCAN_MODE_LOW_POWER;
                params.matchMode = bluetoothle.MATCH_MODE_STICKY;
                params.matchNum = bluetoothle.MATCH_NUM_ONE_ADVERTISEMENT;
                //params.callbackType = bluetoothle.CALLBACK_TYPE_FIRST_MATCH;
            }

            Log.add("Start Scan : " + JSON.stringify(params));

            $cordovaBluetoothLE.startScan(params).then(function (obj) {
                Log.add("Start Scan Auto Stop : " + JSON.stringify(obj));
                $scope.firstScan = false;
                $scope.scanDevice = false;
            }, function (obj) {
                Log.add("Start Scan Error : " + JSON.stringify(obj));
            }, function (device) {
                Log.add("Start Scan Success : " + JSON.stringify(device));

                if (device.status == "scanStarted") return;

                $scope.noDevice = false;
                $scope.devices[device.address] = device;
                $scope.devices[device.address].services = {};
                console.log(JSON.stringify($scope.devices));

                if (device.address == getLastCon() && $scope.firstScan) {
                    $scope.connect(device);
                    $scope.firstScan = false;
                }
            });
        };

        $scope.connect = function (device) {

            var onConnect = function (obj) {

                if($scope.dev1Connected && $scope.dev2Connected){
                    navigator.notification.alert("Sorry. You cannot connect to more than two devices!", function () {});
                    return;
                }

                Log.add("Connect Success : " + JSON.stringify(obj));
                // Save deviceId as last connected one
                setLastCon(device.address);

                if($scope.dev1Connected == false){
                    $scope.dev1Connected = true;
                    $scope.currentDevice1 = device;
                    $scope.barometer.temperatureDev1 = "FREEZING HELL";
                    $scope.barometer.pressureDev1 = "Inside of Jupiter";
                }else{
                    $scope.dev2Connected = true;
                    $scope.currentDevice2 = device;
                    $scope.barometer.temperatureDev2 = "FREEZING HELL";
                    $scope.barometer.pressureDev2 = "Inside of Jupiter";
                }

                //Subscribe to barometer service
                var params = {
                    address: device.address,
                    service: barometer.service,
                    characteristic: barometer.data,
                    timeout: 5000
                };

                Log.add("Subscribe : " + JSON.stringify(params));

                $cordovaBluetoothLE.subscribe(params).then(function (obj) {
                    Log.add("Subscribe Auto Unsubscribe : " + JSON.stringify(obj));
                }, function (obj) {
                    Log.add("Subscribe Error : " + JSON.stringify(obj));
                }, function (obj) {
                    //Log.add("Subscribe Success : " + JSON.stringify(obj));

                    if (obj.status == "subscribedResult") {
                        //Log.add("Subscribed Result");
                        onBarometerData(obj,device);
                        var bytes = $cordovaBluetoothLE.encodedStringToBytes(obj.value);
                        Log.add("Subscribe Success ASCII (" + bytes.length + "): " + $cordovaBluetoothLE.bytesToString(bytes));
                        Log.add("HEX (" + bytes.length + "): " + $cordovaBluetoothLE.bytesToHex(bytes));
                    } else if (obj.status == "subscribed") {
                        Log.add("Subscribed");
                        //Turn on barometer
                        var barometerConfig = new Uint8Array(1);
                        barometerConfig[0] = 0x01;
                        var params = {
                            address: device.address,
                            service: barometer.service,
                            characteristic: barometer.configuration,
                            value: $cordovaBluetoothLE.bytesToEncodedString(barometerConfig),
                            timeout: 5000
                        };

                        Log.add("Write : " + JSON.stringify(params));

                        $cordovaBluetoothLE.write(params).then(function (obj) {
                            Log.add("Write Success : " + JSON.stringify(obj));
                        }, function (obj) {
                            Log.add("Write Error : " + JSON.stringify(obj));
                        });
                    } else {
                        Log.add("Unexpected Subscribe Status");
                    }
                });

            };
            var params = { address: device.address, timeout: 10000 };

            Log.add("Connect : " + JSON.stringify(params));

            $cordovaBluetoothLE.connect(params).then(null, function (obj) {
                Log.add("Connect Error : " + JSON.stringify(obj));
                $scope.close(params.address); //Best practice is to close on connection error
            }, function () {
                $scope.discover(device.address, onConnect);
            });

        };

        $rootScope.$on("bleEnabledEvent", function () {
            $scope.startScan();
        })

        $scope.refreshSensortags = function () {
            $scope.devices = {};
            $scope.startScan();
            $scope.noDevice = true;
            $scope.scanDevice = true;
        }

        $scope.stopScan = function () {
            $scope.scanDevice = false;
            $scope.firstScan = false;
            $cordovaBluetoothLE.stopScan().then(function (obj) {
                Log.add("Stop Scan Success : " + JSON.stringify(obj));
            }, function (obj) {
                Log.add("Stop Scan Error : " + JSON.stringify(obj));
            });
        }

        $scope.close = function (address) {
            var params = { address: address };

            Log.add("Close : " + JSON.stringify(params));

            $cordovaBluetoothLE.close(params).then(function (obj) {
                Log.add("Close Success : " + JSON.stringify(obj));
            }, function (obj) {
                Log.add("Close Error : " + JSON.stringify(obj));
            });

            var device = $scope.devices[address];
            device.services = {};
        };

        function onBarometerData(obj, device) {
            var a = $cordovaBluetoothLE.encodedStringToBytes(obj.value);

            function sensorBarometerConvert(data) {
                return (data / 100);
            }

            if($scope.currentDevice1.address == device.address){
                $scope.barometer.temperatureDev1 = sensorBarometerConvert(a[0] | (a[1] << 8) | (a[2] << 16)) + "°C";
                $scope.barometer.pressureDev1 = sensorBarometerConvert(a[3] | (a[4] << 8) | (a[5] << 16)) + "hPa";
            }else if ($scope.currentDevice2.address == device.address){
                $scope.barometer.temperatureDev2 = sensorBarometerConvert(a[0] | (a[1] << 8) | (a[2] << 16)) + "°C";
                $scope.barometer.pressureDev2 = sensorBarometerConvert(a[3] | (a[4] << 8) | (a[5] << 16)) + "hPa";
            }else{
                Log.add("onBarometerData: no matching device" + JSON.stringify(device.address));
            }
        }

        $scope.disconnect = function (device) {
            if ($scope.dev1Connected && $scope.currentDevice1.address == device.address){
                $scope.dev1Connected = false;
                $scope.close($scope.currentDevice1.address);
            } else if ($scope.dev2Connected && $scope.currentDevice2.address == device.address){
                $scope.dev2Connected = false;
                $scope.close($scope.currentDevice2.address);
            }
        }

        $scope.isConnected = function (device) {
            if ($scope.dev1Connected && $scope.currentDevice1.address == device.address){
                return true;
            } else if ($scope.dev2Connected && $scope.currentDevice2.address == device.address){
                return true;
            } else {
                return false;
            }
        }

        $scope.discover = function (address, afterFunction) {
            var params = {
                address: address,
                timeout: 10000
            };

            Log.add("Discover : " + JSON.stringify(params));

            $cordovaBluetoothLE.discover(params).then(function (obj) {
                Log.add("Discover Success : " + JSON.stringify(obj));

                var device = $scope.devices[obj.address];

                var services = obj.services;

                for (var i = 0; i < services.length; i++) {
                    var service = services[i];

                    addService(service, device);

                    var serviceNew = device.services[service.uuid];

                    var characteristics = service.characteristics;

                    for (var j = 0; j < characteristics.length; j++) {
                        var characteristic = characteristics[j];

                        addCharacteristic(characteristic, serviceNew);

                        var characteristicNew = serviceNew.characteristics[characteristic.uuid];

                        var descriptors = characteristic.descriptors;

                        for (var k = 0; k < descriptors.length; k++) {
                            var descriptor = descriptors[k];

                            addDescriptor(descriptor, characteristicNew);
                        }

                    }
                }
                if (afterFunction != undefined) {
                    afterFunction();
                }
            }, function (obj) {
                Log.add("Discover Error : " + JSON.stringify(obj));
            });
        };
        function addService(service, device) {
            if (device.services[service.uuid] !== undefined) {
                return;
            }
            device.services[service.uuid] = { uuid: service.uuid, characteristics: {} };
        }

        function addCharacteristic(characteristic, service) {
            if (service.characteristics[characteristic.uuid] !== undefined) {
                return;
            }
            service.characteristics[characteristic.uuid] = { uuid: characteristic.uuid, descriptors: {}, properties: characteristic.properties };
        }

        function addDescriptor(descriptor, characteristic) {
            if (characteristic.descriptors[descriptor.uuid] !== undefined) {
                return;
            }
            characteristic.descriptors[descriptor.uuid] = { uuid: descriptor.uuid };
        }

        $scope.firstScan = false;
        if (settings.settings.startReconnect == "true" || settings.settings.startReconnect === true) {
            $scope.firstScan = true;
        }

        $rootScope.$on("bleEnabledEvent", function () {
            $scope.startScan();
        });

    })

    // Controller for Settings
    .controller('SettingsCtrl', function ($scope, settings) {

        // Link the scope settings to the settings service
        $scope.settings = settings.settings;

        // Scope update function is the settings service persist function
        $scope.update = settings.persistSettings;
        //
        $scope.$on('volumeupbutton', function () {
            $scope.$apply(function () {									// angular doesn't fire $apply on the events so if $broadcast is called outside angular's context, you are going to need to $apply by hand.

                // Update Volume + checks for valid values (0 to 100)
                var vol = settings.settings.volume;
                var up = 10;

                // Catch if volume 91 to 100, update to max 100
                if (vol > 90)
                    up = 100 - vol;
                vol = vol + up;

                // Save setting
                settings.setSetting("volume", vol);

                // Initialise GUI with saved setting values
                settings.settings.volume = vol;

            });
        });

        $scope.$on('volumedownbutton', function () {
            $scope.$apply(function () {									// angular doesn't fire $apply on the events so if $broadcast is called outside angular's context, you are going to need to $apply by hand.

                // Update Volume + checks for valid values (0 to 100)
                var vol = settings.settings.volume;
                var down = 10;

                // Catch if volume 9 to 0, update to min 0
                if (vol < 10)
                    down = vol;
                vol = vol - down;

                // Save setting
                settings.setSetting("volume", vol);

                // Initialise GUI with saved setting values
                settings.settings.volume = vol;

            });
        });
    });
<|endoftext|>


<commit_before>jest.dontMock("../gamestate")

let GameState = require("../gamestate")

describe("GameState", function() {
  it("can be created", function() {
    let state = new GameState({name: "bob"})
  })

  it("can be serialized", function() {
    let state = new GameState({name: "bob"})
    let data = JSON.stringify(state)
    expect(data).toEqual('{"name":"bob","_started":false,"players":[{"name":"bob","hand":[],"board":[],"trash":[],"life":30,"mana":0,"maxMana":0},{"name":"waiting...","hand":[],"board":[],"trash":[],"life":30,"mana":0,"maxMana":0}],"winner":null,"godMode":false,"history":[]}')
  })

  it("can be deserialized", function() {
    let state = new GameState({name: "bob"})
    let data = JSON.stringify(state)
    let state2 = new GameState(JSON.parse(data))

    expect(state2.name).toEqual(state.name)
    expect(state2._started).toEqual(state._started)
    expect(state2.winner).toEqual(state.winner)
  })

  it("has accessible players via playerForName", function() {
    let state = new GameState({name: "bob"})
    expect(state.playerForName("bob").name).toEqual("bob")
  })

  it("retains card function across reserialization", function() {
    let state = new GameState({name: "bob"})
    state.startGame(["bob", "eve"], 123)
    state.makeMove({op: "refreshPlayers"})
    let data = JSON.stringify(state)
    let state2 = new GameState(JSON.parse(data))

    expect(state2.name).toEqual(state.name)
    expect(state2._started).toEqual(state._started)
    expect(state2.winner).toEqual(state.winner)
  })

  it("can be consistently serialized with displayString", function() {
    let state = new GameState({name: "yorp"})
    state.startGame(["yorp", "blorp"], 1212)
    let data = JSON.stringify(state)
    let state2 = new GameState(JSON.parse(data))
    expect(state.displayString()).toEqual(state2.displayString())
  })

  it("kill moves creature from board to trash", function() {
    let state = new GameState({name: "bob"})

    state.startGame(["bob", "eve"], 123)

    // draw the simplest permanent
    state.draw({"name":"bob"}, {"cost":0, "permanent": true})
    expect(state.localPlayer().hand.length).toEqual(1)

    // play last card drawn
    state.selectCard(0, "hand", "bob")
    state.selectCard(0, "hand", "bob")
    expect(state.localPlayer().board.length).toEqual(1)

    // go to next turn
    state.makeMove({op: "refreshPlayers"})

    // get a card that implements kill
    state.draw({"name":"eve"}, {"kill":true, "cost":0})
    // play last card drawn
    state.selectCard(0, "hand", "eve")
    state.selectCard(0, "hand", "eve")

    // permanent leaves board and goes to trash
    expect(state.remotePlayer().board.length).toEqual(0)
    expect(state.remotePlayer().trash.length).toEqual(1)

  })

  it("direct damage kills appropriate sized creature", function() {
    let state = new GameState({name: "bob"})

    state.startGame(["bob", "eve"], 123)

    // draw the simplest 2/2
    state.draw({"name":"bob"},
               {"cost":0, "permanent": true, "attack": 2, "defense": 2})

    // play last card drawn
    state.selectCard(0, "hand", "bob")
    state.selectCard(0, "hand", "bob")

    // go to next turn
    state.makeMove({op: "refreshPlayers"})

    // get a card that implements direct damage
    state.draw({"name":"eve"}, {"damage":2, "cost":0})

    // play last card drawn
    state.selectCard(0, "hand", "eve")
    state.selectCard(0, "opponentBoard", "eve")

    // permanent leaves board and goes to trash
    // bob
    expect(state.localPlayer().board.length).toEqual(0)
    expect(state.localPlayer().trash.length).toEqual(1)
    // eve
    expect(state.remotePlayer().board.length).toEqual(0)
    expect(state.remotePlayer().trash.length).toEqual(1)
  })

  it("two creatures die when colliding", function() {
    let state = new GameState({name: "bob"})

    state.startGame(["bob", "eve"], 123)

    // draw the simplest 2/2
    state.draw({"name":"bob"},
               {"cost":0, "permanent": true, "attack": 2, "defense": 2})

    // play last card drawn
    state.selectCard(0, "hand", "bob")
    state.selectCard(0, "hand", "bob")

    // draw the simplest 2/2
    state.draw({"name":"eve"},
               {"cost":0, "permanent": true, "attack": 2, "defense": 2})

    // play last card drawn
    state.selectCard(0, "hand", "eve")
    state.selectCard(0, "hand", "eve")

    // go to next turn
    state.makeMove({op: "refreshPlayers"})

    state.selectCard(0, "board", "eve")
    state.selectCard(0, "opponentBoard", "eve")

    // both permanents leave board and go to trash
    expect(state.localPlayer().board.length).toEqual(0)
    expect(state.localPlayer().trash.length).toEqual(1)
    expect(state.remotePlayer().board.length).toEqual(0)
    expect(state.remotePlayer().trash.length).toEqual(1)

  })

})
<commit_msg>fix test<commit_after>jest.dontMock("../gamestate")

let GameState = require("../gamestate")

describe("GameState", function() {
  it("can be created", function() {
    let state = new GameState({name: "bob"})
  })

  it("can be serialized", function() {
    let state = new GameState({name: "bob"})
    let data = JSON.stringify(state)
    expect(data).toEqual('{"name":"bob","_started":false,"players":[{"name":"bob","hand":[],"board":[],"trash":[],"life":30,"mana":0,"maxMana":0},{"name":"waiting...","hand":[],"board":[],"trash":[],"life":30,"mana":0,"maxMana":0}],"winner":null,"declaredWinner":false,"godMode":false,"history":[],"damageDuration":900}')
  })

  it("can be deserialized", function() {
    let state = new GameState({name: "bob"})
    let data = JSON.stringify(state)
    let state2 = new GameState(JSON.parse(data))

    expect(state2.name).toEqual(state.name)
    expect(state2._started).toEqual(state._started)
    expect(state2.winner).toEqual(state.winner)
  })

  it("has accessible players via playerForName", function() {
    let state = new GameState({name: "bob"})
    expect(state.playerForName("bob").name).toEqual("bob")
  })

  it("retains card function across reserialization", function() {
    let state = new GameState({name: "bob"})
    state.startGame(["bob", "eve"], 123)
    state.makeMove({op: "refreshPlayers"})
    let data = JSON.stringify(state)
    let state2 = new GameState(JSON.parse(data))

    expect(state2.name).toEqual(state.name)
    expect(state2._started).toEqual(state._started)
    expect(state2.winner).toEqual(state.winner)
  })

  it("can be consistently serialized with displayString", function() {
    let state = new GameState({name: "yorp"})
    state.startGame(["yorp", "blorp"], 1212)
    let data = JSON.stringify(state)
    let state2 = new GameState(JSON.parse(data))
    expect(state.displayString()).toEqual(state2.displayString())
  })

  it("kill moves creature from board to trash", function() {
    let state = new GameState({name: "bob"})

    state.startGame(["bob", "eve"], 123)

    // draw the simplest permanent
    state.draw({"name":"bob"}, {"cost":0, "permanent": true})
    expect(state.localPlayer().hand.length).toEqual(1)

    // play last card drawn
    state.selectCard(0, "hand", "bob")
    state.selectCard(0, "hand", "bob")
    expect(state.localPlayer().board.length).toEqual(1)

    // go to next turn
    state.makeMove({op: "refreshPlayers"})

    // get a card that implements kill
    state.draw({"name":"eve"}, {"kill":true, "cost":0})
    // play last card drawn
    state.selectCard(0, "hand", "eve")
    state.selectCard(0, "hand", "eve")

    // permanent leaves board and goes to trash
    expect(state.remotePlayer().board.length).toEqual(0)
    expect(state.remotePlayer().trash.length).toEqual(1)

  })

  it("direct damage kills appropriate sized creature", function() {
    let state = new GameState({name: "bob"})

    state.startGame(["bob", "eve"], 123)

    // draw the simplest 2/2
    state.draw({"name":"bob"},
               {"cost":0, "permanent": true, "attack": 2, "defense": 2})

    // play last card drawn
    state.selectCard(0, "hand", "bob")
    state.selectCard(0, "hand", "bob")

    // go to next turn
    state.makeMove({op: "refreshPlayers"})

    // get a card that implements direct damage
    state.draw({"name":"eve"}, {"damage":2, "cost":0})

    // play last card drawn
    state.selectCard(0, "hand", "eve")
    state.selectCard(0, "opponentBoard", "eve")

    // permanent leaves board and goes to trash
    // bob
    expect(state.localPlayer().board.length).toEqual(0)
    expect(state.localPlayer().trash.length).toEqual(1)
    // eve
    expect(state.remotePlayer().board.length).toEqual(0)
    expect(state.remotePlayer().trash.length).toEqual(1)
  })

  it("two creatures die when colliding", function() {
    let state = new GameState({name: "bob"})

    state.startGame(["bob", "eve"], 123)

    // draw the simplest 2/2
    state.draw({"name":"bob"},
               {"cost":0, "permanent": true, "attack": 2, "defense": 2})

    // play last card drawn
    state.selectCard(0, "hand", "bob")
    state.selectCard(0, "hand", "bob")

    // draw the simplest 2/2
    state.draw({"name":"eve"},
               {"cost":0, "permanent": true, "attack": 2, "defense": 2})

    // play last card drawn
    state.selectCard(0, "hand", "eve")
    state.selectCard(0, "hand", "eve")

    // go to next turn
    state.makeMove({op: "refreshPlayers"})

    state.selectCard(0, "board", "eve")
    state.selectCard(0, "opponentBoard", "eve")

    // both permanents leave board and go to trash
    expect(state.localPlayer().board.length).toEqual(0)
    expect(state.localPlayer().trash.length).toEqual(1)
    expect(state.remotePlayer().board.length).toEqual(0)
    expect(state.remotePlayer().trash.length).toEqual(1)

  })

})
<|endoftext|>


<commit_before>import _ from 'underscore';
import { Collection } from 'backbone';
import app from '../app';
import Mod from '../models/VerifiedMod';

export default class extends Collection {
  constructor(...args) {
    super(...args);
    this._data = {};
  }

  model(attrs, options) {
    return new Mod(attrs, options);
  }

  modelId(attrs) {
    return attrs.peerID;
  }

  url() {
    const usingTor = app.serverConfig.tor && getCurrentConnection().server.get('useTor');
    return app.localSettings.get(`verifiedModsProvider${usingTor ? 'Tor' : ''}`);
  }

  get data() {
    return this._data;
  }

  get localBadge() {
    return {
      large: '../imgs/verifiedModeratorBadge-large.png',
      medium: '../imgs/verifiedModeratorBadge-medium.png',
      small: '../imgs/verifiedModeratorBadge-small.png',
      tiny: '../imgs/verifiedModeratorBadge-tiny.png',
    };
  }

  /**
   * Return a list of verified moderators that match the ids passed in
   * @param IDs {array} - a list of IDs
   */
  matched(IDs = []) {
    return this.filter(mod => IDs.includes(mod.get('peerID')));
  }

  /**
   * Return a badge object to use to represent the verified moderators available on a listing.
   * If none of the moderators have a badge value, return the local badge files.
   * @param IDs {array} - a list of IDs
   */
  defaultBadge(IDs) {
    const modelWithBadge = _.find(this.matched(IDs), mod => mod.get('type').badge);
    return {
      ...this.localBadge,
      ...(modelWithBadge && modelWithBadge.get('type').badge || {}),
    };
  }

  parse(response) {
    /* The data is expected to be delivered in the following format. There must be at least one
       type with a badge, or the grey loading badge will be shown instead.
    {
      data: {
        name: 'name of provider (required)',
        description: 'description of the provider (optional)',
        link: 'url to the provider (required)'
      },
      types: [
        {
          name: 'standard (required)',
          description: 'description of this type of moderator (optional)',
          badge: {
            large: "url",
            medium: "url",
            small: "url",
            tiny: "url"
          }
        }
      ],
      moderators: [
        {
          peerID: 'QmVFNEj1rv2d3ZqSwhQZW2KT4zsext4cAMsTZRt5dAQqFJ',
          type: 'standard'
        }
      ]
    }
     */
    this._data = response.data || {};
    this._data.url = this.url(); // backup for templates if the link is missing
    const parsedMods = response.moderators ? response.moderators : [];
    /*
       Embed the type in each moderator so it's easier to use elsewhere. It should look like:
       peerID: string,
       type: {
         name: string,
         description: string,
         badge: url string,
         }
     */
    const parsedResponse = [];
    parsedMods.forEach((mod) => {
      if (response.types && response.types.length && mod.type) {
        mod.type = _.findWhere(response.types, { name: mod.type });
        if (mod.type) parsedResponse.push(mod);
      }
    });
    return parsedResponse;
  }
}
<commit_msg>fix linting<commit_after>import _ from 'underscore';
import { Collection } from 'backbone';
import app from '../app';
import Mod from '../models/VerifiedMod';
import { getCurrentConnection } from '../utils/serverConnect';

export default class extends Collection {
  constructor(...args) {
    super(...args);
    this._data = {};
  }

  model(attrs, options) {
    return new Mod(attrs, options);
  }

  modelId(attrs) {
    return attrs.peerID;
  }

  url() {
    const usingTor = app.serverConfig.tor && getCurrentConnection().server.get('useTor');
    return app.localSettings.get(`verifiedModsProvider${usingTor ? 'Tor' : ''}`);
  }

  get data() {
    return this._data;
  }

  get localBadge() {
    return {
      large: '../imgs/verifiedModeratorBadge-large.png',
      medium: '../imgs/verifiedModeratorBadge-medium.png',
      small: '../imgs/verifiedModeratorBadge-small.png',
      tiny: '../imgs/verifiedModeratorBadge-tiny.png',
    };
  }

  /**
   * Return a list of verified moderators that match the ids passed in
   * @param IDs {array} - a list of IDs
   */
  matched(IDs = []) {
    return this.filter(mod => IDs.includes(mod.get('peerID')));
  }

  /**
   * Return a badge object to use to represent the verified moderators available on a listing.
   * If none of the moderators have a badge value, return the local badge files.
   * @param IDs {array} - a list of IDs
   */
  defaultBadge(IDs) {
    const modelWithBadge = _.find(this.matched(IDs), mod => mod.get('type').badge);
    return {
      ...this.localBadge,
      ...(modelWithBadge && modelWithBadge.get('type').badge || {}),
    };
  }

  parse(response) {
    /* The data is expected to be delivered in the following format. There must be at least one
       type with a badge, or the grey loading badge will be shown instead.
    {
      data: {
        name: 'name of provider (required)',
        description: 'description of the provider (optional)',
        link: 'url to the provider (required)'
      },
      types: [
        {
          name: 'standard (required)',
          description: 'description of this type of moderator (optional)',
          badge: {
            large: "url",
            medium: "url",
            small: "url",
            tiny: "url"
          }
        }
      ],
      moderators: [
        {
          peerID: 'QmVFNEj1rv2d3ZqSwhQZW2KT4zsext4cAMsTZRt5dAQqFJ',
          type: 'standard'
        }
      ]
    }
     */
    this._data = response.data || {};
    this._data.url = this.url(); // backup for templates if the link is missing
    const parsedMods = response.moderators ? response.moderators : [];
    /*
       Embed the type in each moderator so it's easier to use elsewhere. It should look like:
       peerID: string,
       type: {
         name: string,
         description: string,
         badge: url string,
         }
     */
    const parsedResponse = [];
    parsedMods.forEach((mod) => {
      if (response.types && response.types.length && mod.type) {
        mod.type = _.findWhere(response.types, { name: mod.type });
        if (mod.type) parsedResponse.push(mod);
      }
    });
    return parsedResponse;
  }
}
<|endoftext|>


<commit_before>import React from 'react'
import Dropdown from 'fyndiq-component-dropdown'

import styles from '../styles.less'

const Tooltip = ({ text, children, position }) => (
  <Dropdown
    button={{children}}
    hoverMode
    noWrapperStyle
    position={position}
  >

      {text}

  </Dropdown>
)

Tooltip.propTypes = {
  text: React.PropTypes.string,
  children: React.PropTypes.string,
  position: Dropdown.propTypes.position,
}

Tooltip.defaultProps = {
  text: '',
  children: '',
  position: 'bc',
}

export default Tooltip
<commit_msg>Change use of PropTypes<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import Dropdown from 'fyndiq-component-dropdown'

import styles from '../styles.less'

const Tooltip = ({ text, children, position }) => (
  <Dropdown
    button={{children}}
    hoverMode
    noWrapperStyle
    position={position}
  >

      {text}

  </Dropdown>
)

Tooltip.propTypes = {
  text: PropTypes.string,
  children: PropTypes.string,
  position: Dropdown.propTypes.position,
}

Tooltip.defaultProps = {
  text: '',
  children: '',
  position: 'bc',
}

export default Tooltip
<|endoftext|>


<commit_before>var DB = {};

DB.init = function() {
  this.db = null;
  this.idx = -1;
  this.keys = null;    // keys of all questions that fit the current filter
  this.filter = null;  // current query filter
};

DB.open = function() {
  var d = this.$q.defer();

  var db = new Dexie(this.dbname);
  db.version(1).stores({
    questions: '++id,status,company,*tags'
  });

  db.open()
    .then(function(db) {
      DB.db = db;
      d.resolve(db);
    })
    .catch(function(e) {
      console.log('db open failed:', e.stack);
      d.reject(e);
    });

  return d.promise;
};

DB.countQuestions = function(tag) {
  var questions = this.db.questions;

  if (tag && tag !== '') {
    // count by given tag
    questions = questions.where('tags').anyOf([tag]);
  } else {
    // count all
    questions = questions.toCollection();
  }

  return questions.count();
};

DB.updateQuestions = function(questions, keys) {
  var d = this.$q.defer();
  if (keys) questions = questions.map(function(x) {
    return _.pick(x, keys);
  });

  this.db.questions
    .bulkPut(questions)
    .then(function(key) {
      d.resolve();
    })
    .catch(Dexie.BulkError, function (e) {
      // ignore put error of duplicate questions
      d.resolve(e);
    });

  return d.promise;
};

DB.filterQuestions = function(filter) {
  var d = this.$q.defer();

  // use cached if filter not changed
  if (_.isEqual(filter, this.filter)) {
    d.resolve();
    return d.promise;
  }

  // otherwise we have to invalidate cache and re-collect
  // all questions that meet this new filter
  var questions = this.db.questions
    .where('status')
    .belowOrEqual(+filter.status);

  if (filter.tag !== '') {
    questions.and(function(q) {
      return q.tags.indexOf(filter.tag) >= 0;
    });
  }

  if (filter.company !== '') {
    questions.and(function(q) {
      return q.company === filter.company;
    });
  }

  if (filter.level !== '') {
    questions.and(function(q) {
      return q.level === +filter.level;
    });
  }

  // FIXME: will this be slow??
  questions.sortBy('name')
    .then(function(sortedQuestions) {
      var keys = _.map(sortedQuestions, function(question) {
        return question.id;
      });
      // console.log(JSON.stringify(keys));
      DB.keys = keys;
      DB.filter = _.clone(filter);

      d.resolve();
  });

  return d.promise;
};

DB.selectQuestion = function(filter) {
  var d = this.$q.defer();

  this.filterQuestions(_.omit(filter, 'algo', 'reversed'))
    .then(function() {
      var n = DB.keys.length;
      if (n === 0) return d.resolve(null);

      var i = DB.idx;
      if (filter.algo === 'Random') {
        i = _.random(n - 1);
      } else {
        var step = filter.reversed ? -1 : 1;
        i = (i + step + n) % n;
      }
      var id = DB.keys[i];

      console.debug('selected question id=' + id + ',' + i + '-th of ' + n);
      DB.db.questions
        .get(id)
        .then(function(question) {
          DB.idx = i;
          d.resolve(question);
        });
    });

  return d.promise;
};

function match(f, q) {
  return f &&
    +f.status >= q.status &&
    (f.company === '' || f.company === q.company) &&
    (f.tag === '' || q.tags.indexOf(f.tag) >= 0);
}

DB.updateQuestion = function(question, keys) {
  var d = this.$q.defer();
  var toUpdate = keys ? _.pick(question, keys) : question;

  // if given, only specific keys will be updated
  this.db.questions
    .update(question.id, toUpdate)
    .then(function(updated) {
      // remove this question from cache if not fit filter any more
      if (!match(DB.filter, question)) {
        var i = DB.keys.indexOf(question.id);
        if (i >= 0) DB.keys.splice(i, 1);
      }

      d.resolve();
    })
    .catch(function(e) {
      d.resolve(e);
    });

  return d.promise;
};

DB.getQuestions = function() {
  return this.db.questions.toCollection().toArray();
};

DB.setQuestions = function(questions) {
  var d = this.$q.defer();

  questions = questions.map(function(q) {
    return _.omit(q, 'rand', 'link');
  });

  this.db.questions
    .clear()
    .then(function() {
      DB.db.questions
        .bulkPut(questions)
        .then(function(key) {
          d.resolve();
        })
        .catch(Dexie.BulkError, function (e) {
          d.resolve(e);
        });
    });

  return d.promise;
};

angular.module('Services')
.service('DB', [ '$q', 'Config' ,function($q, Config) {
  DB.$q = $q;
  DB.dbname = Config.name + '.db';

  DB.init();
  return DB;
}]);
<commit_msg>Auto upgrade to db schema v2.<commit_after>var DB = {};

DB.init = function() {
  this.db = null;
  this.idx = -1;
  this.keys = null;    // keys of all questions that fit the current filter
  this.filter = null;  // current query filter
};

DB.open = function() {
  var d = this.$q.defer();

  var db = new Dexie(this.dbname);
  db.version(1).stores({
    questions: '++id,&name,status,company,*tags'
  });
  db.version(2).stores({
    questions: '++id,status,company,*tags'
  }).upgrade(function() {
    console.log('found new schema v2!');
    DB.toV2 = true;
  });

  db.open()
    .then(function(db) {
      DB.db = db;
      DB.fixup(function() {
        d.resolve(db);
      });
    })
    .catch(function(e) {
      console.log('db open failed:', e.stack);
      d.reject(e);
    });

  return d.promise;
};

DB.fixup = function(cb) {
  if (!DB.toV2) return cb();
  console.log('upgrading to v2 ...');
  DB.getQuestions()
    .then(function(questions) {
      questions.forEach(function(q) {
        q.id = q.name;
        delete q.name;
      });
      return DB.setQuestions(questions);
    })
    .then(function(e) {
      if (e) {
        console.log('failed to upgrade v2: ' + e.message);
      } else {
        console.log('upgraded to v2!');
        DB.toV2 = false;
      }
      return cb();
    });
};

DB.countQuestions = function(tag) {
  var questions = this.db.questions;

  if (tag && tag !== '') {
    // count by given tag
    questions = questions.where('tags').anyOf([tag]);
  } else {
    // count all
    questions = questions.toCollection();
  }

  return questions.count();
};

DB.updateQuestions = function(questions, keys) {
  var d = this.$q.defer();
  if (keys) questions = questions.map(function(x) {
    return _.pick(x, keys);
  });

  this.db.questions
    .bulkAdd(questions)
    .then(function(key) {
      d.resolve();
    })
    .catch(Dexie.BulkError, function (e) {
      // ignore put error of duplicate questions
      d.resolve(e);
    });

  return d.promise;
};

DB.filterQuestions = function(filter) {
  var d = this.$q.defer();

  // use cached if filter not changed
  if (_.isEqual(filter, this.filter)) {
    d.resolve();
    return d.promise;
  }

  // otherwise we have to invalidate cache and re-collect
  // all questions that meet this new filter
  var questions = this.db.questions
    .where('status')
    .belowOrEqual(+filter.status);

  if (filter.tag !== '') {
    questions.and(function(q) {
      return q.tags.indexOf(filter.tag) >= 0;
    });
  }

  if (filter.company !== '') {
    questions.and(function(q) {
      return q.company === filter.company;
    });
  }

  if (filter.level !== '') {
    questions.and(function(q) {
      return q.level === +filter.level;
    });
  }

  // FIXME: will this be slow??
  questions.sortBy('name')
    .then(function(sortedQuestions) {
      var keys = _.map(sortedQuestions, function(question) {
        return question.id;
      });
      // console.log(JSON.stringify(keys));
      DB.keys = keys;
      DB.filter = _.clone(filter);

      d.resolve();
  });

  return d.promise;
};

DB.selectQuestion = function(filter) {
  var d = this.$q.defer();

  this.filterQuestions(_.omit(filter, 'algo', 'reversed'))
    .then(function() {
      var n = DB.keys.length;
      if (n === 0) return d.resolve(null);

      var i = DB.idx;
      if (filter.algo === 'Random') {
        i = _.random(n - 1);
      } else {
        var step = filter.reversed ? -1 : 1;
        i = (i + step + n) % n;
      }
      var id = DB.keys[i];

      console.debug('selected question id=' + id + ',' + i + '-th of ' + n);
      DB.db.questions
        .get(id)
        .then(function(question) {
          DB.idx = i;
          d.resolve(question);
        });
    });

  return d.promise;
};

function match(f, q) {
  return f &&
    +f.status >= q.status &&
    (f.company === '' || f.company === q.company) &&
    (f.tag === '' || q.tags.indexOf(f.tag) >= 0);
}

DB.updateQuestion = function(question, keys) {
  var d = this.$q.defer();
  var toUpdate = keys ? _.pick(question, keys) : question;

  // if given, only specific keys will be updated
  this.db.questions
    .update(question.id, toUpdate)
    .then(function(updated) {
      // remove this question from cache if not fit filter any more
      if (!match(DB.filter, question)) {
        var i = DB.keys.indexOf(question.id);
        if (i >= 0) DB.keys.splice(i, 1);
      }

      d.resolve();
    })
    .catch(function(e) {
      d.resolve(e);
    });

  return d.promise;
};

DB.getQuestions = function() {
  return this.db.questions.toCollection().toArray();
};

DB.setQuestions = function(questions) {
  var d = this.$q.defer();

  questions = questions.map(function(q) {
    return _.omit(q, 'rand', 'link');
  });

  this.db.questions
    .clear()
    .then(function() {
      DB.db.questions
        .bulkPut(questions)
        .then(function(key) {
          d.resolve();
        })
        .catch(Dexie.BulkError, function (e) {
          d.resolve(e);
        });
    });

  return d.promise;
};

angular.module('Services')
.service('DB', [ '$q', 'Config' ,function($q, Config) {
  DB.$q = $q;
  DB.dbname = Config.name + '.db';

  DB.init();
  return DB;
}]);
<|endoftext|>


<commit_before>// Copyright 2016-2017, University of Colorado Boulder

/**
 * Triangle indicators used in the paint gradient and apple graph.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const inherit = require( 'PHET_CORE/inherit' );
  const Matrix3 = require( 'DOT/Matrix3' );
  const Path = require( 'SCENERY/nodes/Path' );
  const proportionPlayground = require( 'PROPORTION_PLAYGROUND/proportionPlayground' );
  const Shape = require( 'KITE/Shape' );
  const Side = require( 'PROPORTION_PLAYGROUND/common/model/Side' );

  // constants
  var TRIANGLE_LENGTH = 17;
  var TRIANGLE_ALTITUDE = 10;
  var LEFT_TRIANGLE_SHAPE = new Shape().moveTo( 0, 0 )
    .lineTo( TRIANGLE_ALTITUDE, TRIANGLE_LENGTH / 2 )
    .lineTo( 0, TRIANGLE_LENGTH )
    .lineTo( 0, 0 );
  var RIGHT_TRIANGLE_SHAPE = LEFT_TRIANGLE_SHAPE.transformed( Matrix3.scaling( -1, 1 ) );

  /**
   * @constructor
   * @extends {Path}
   *
   * @param {Side} side - Side.LEFT or Side.RIGHT
   * @param {Object} [options]
   */
  function TriangleNode( side, options ) {
    assert && assert( Side.isSide( side ), 'Side should be Side.LEFT or Side.RIGHT' );

    // defaults
    options = _.extend( {
      stroke: 'black',
      lineWidth: 1
    }, options );

    Path.call( this, side === Side.LEFT ? LEFT_TRIANGLE_SHAPE : RIGHT_TRIANGLE_SHAPE, options );
  }

  proportionPlayground.register( 'TriangleNode', TriangleNode );

  return inherit( Path, TriangleNode );
} );
<commit_msg>var -> const using eslint auto fix, https://github.com/phetsims/tasks/issues/1012<commit_after>// Copyright 2016-2017, University of Colorado Boulder

/**
 * Triangle indicators used in the paint gradient and apple graph.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const inherit = require( 'PHET_CORE/inherit' );
  const Matrix3 = require( 'DOT/Matrix3' );
  const Path = require( 'SCENERY/nodes/Path' );
  const proportionPlayground = require( 'PROPORTION_PLAYGROUND/proportionPlayground' );
  const Shape = require( 'KITE/Shape' );
  const Side = require( 'PROPORTION_PLAYGROUND/common/model/Side' );

  // constants
  const TRIANGLE_LENGTH = 17;
  const TRIANGLE_ALTITUDE = 10;
  const LEFT_TRIANGLE_SHAPE = new Shape().moveTo( 0, 0 )
    .lineTo( TRIANGLE_ALTITUDE, TRIANGLE_LENGTH / 2 )
    .lineTo( 0, TRIANGLE_LENGTH )
    .lineTo( 0, 0 );
  const RIGHT_TRIANGLE_SHAPE = LEFT_TRIANGLE_SHAPE.transformed( Matrix3.scaling( -1, 1 ) );

  /**
   * @constructor
   * @extends {Path}
   *
   * @param {Side} side - Side.LEFT or Side.RIGHT
   * @param {Object} [options]
   */
  function TriangleNode( side, options ) {
    assert && assert( Side.isSide( side ), 'Side should be Side.LEFT or Side.RIGHT' );

    // defaults
    options = _.extend( {
      stroke: 'black',
      lineWidth: 1
    }, options );

    Path.call( this, side === Side.LEFT ? LEFT_TRIANGLE_SHAPE : RIGHT_TRIANGLE_SHAPE, options );
  }

  proportionPlayground.register( 'TriangleNode', TriangleNode );

  return inherit( Path, TriangleNode );
} );
<|endoftext|>


<commit_before>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2017 GDL
 *
 * See LICENSE
 */

import * as React from 'react';
import Image from './Image';
import { TABLET_BREAKPOINT } from '../style/theme/misc';

const NO_COVER_PLACEHOLDER_URL = require('../static/placeholder-cover.png');

// To prevent downloading the same covers again again with small differences
// Set the "biggest" size for each viewport here.
const COVER_WIDTHS = [130, 260];

// Desktop size: 115 x 155
// Mobile size: 80 x 108

type Props = {
  ariaHidden: boolean,
  src: ?string,
  w: Array<string | number>,
  h: Array<string | number>
};

const srcUrl = (src, width) =>
  `${src}?storedRatio=0.81&focalX=50&focalY=50&ratio=0.81&width=${width}`;

/** Generate srcset value for two different pixels densities */
function srcSet(url: string, width: number) {
  // Note: 1x descriptior is assumed when left out
  return `${srcUrl(url, width)}, ${srcUrl(url, width * 2)} 2x`;
}

/**
 * Add query parameters to book cover images so they fit our wanted ratio
 * This also means we don't download the full image, just the part we want
 */
const CoverImage = ({ src, ariaHidden, ...props }: Props) => {
  const srcOrPlaceholder =
    (src && srcUrl(src, COVER_WIDTHS[1])) || NO_COVER_PLACEHOLDER_URL;

  if (src == null) {
    return (
      <Image
        ariaHidden={ariaHidden}
        src={NO_COVER_PLACEHOLDER_URL}
        {...props}
      />
    );
  }

  return (
    <Image
      ariaHidden={ariaHidden}
      srcSet={srcSet(src, COVER_WIDTHS[0])}
      src={srcOrPlaceholder}
      {...props}
    >
      <source
        media={`(min-width: ${TABLET_BREAKPOINT}px)`}
        srcSet={srcSet(src, COVER_WIDTHS[1])}
      />
    </Image>
  );
};

export default CoverImage;
<commit_msg>#348: Disable storedRatio lookup GlobalDigitalLibraryio/issues#348<commit_after>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2017 GDL
 *
 * See LICENSE
 */

import * as React from 'react';
import Image from './Image';
import { TABLET_BREAKPOINT } from '../style/theme/misc';

const NO_COVER_PLACEHOLDER_URL = require('../static/placeholder-cover.png');

// To prevent downloading the same covers again again with small differences
// Set the "biggest" size for each viewport here.
const COVER_WIDTHS = [130, 260];

// Desktop size: 115 x 155
// Mobile size: 80 x 108

type Props = {
  ariaHidden: boolean,
  src: ?string,
  w: Array<string | number>,
  h: Array<string | number>
};

const srcUrl = (src, width) =>
  `${src}?focalX=50&focalY=50&ratio=0.81&width=${width}`;

/** Generate srcset value for two different pixels densities */
function srcSet(url: string, width: number) {
  // Note: 1x descriptior is assumed when left out
  return `${srcUrl(url, width)}, ${srcUrl(url, width * 2)} 2x`;
}

/**
 * Add query parameters to book cover images so they fit our wanted ratio
 * This also means we don't download the full image, just the part we want
 */
const CoverImage = ({ src, ariaHidden, ...props }: Props) => {
  const srcOrPlaceholder =
    (src && srcUrl(src, COVER_WIDTHS[1])) || NO_COVER_PLACEHOLDER_URL;

  if (src == null) {
    return (
      <Image
        ariaHidden={ariaHidden}
        src={NO_COVER_PLACEHOLDER_URL}
        {...props}
      />
    );
  }

  return (
    <Image
      ariaHidden={ariaHidden}
      srcSet={srcSet(src, COVER_WIDTHS[0])}
      src={srcOrPlaceholder}
      {...props}
    >
      <source
        media={`(min-width: ${TABLET_BREAKPOINT}px)`}
        srcSet={srcSet(src, COVER_WIDTHS[1])}
      />
    </Image>
  );
};

export default CoverImage;
<|endoftext|>


<commit_before>;(function ( $, window, document, undefined ) {
$.widget( "figgy.filemanager", {
    options: {
        images: [],
        selected: [],
        blank: {
          'id': '',
          'label': '',
          'url': '',
          'pageType': 'single',
          'isThumb': null,
          'isStart': null
        }
    },
    _create: function() {
        this.thumb_pixel_width = 200
        this.caption_pixel_padding = 9
        this.changeList = []
        this.original_order = []
        this._createGallery()
        this._createDetailSidebar()
        this._createForm()
        this._createControls()

        var _this = this;

        // add event handlers
        this._on(this.document, {
  				'click.thumbnail': function(event) {
            _this.handleSelectPage(event)
  				}
  			});

        this._on(this.document, {
          'click#save_btn': function(event) {
            this.save()
  				}
        });

        // md form listeners
        this._on(this.document, {
          'input#label': function(event) {
            this.options.selected[0].label = $( '#label' ).val()
            this.applyChanges()
  				}
        });

        // gallery toolbar listeners
        this._on(this.document, {
          'input[type=range]': function(event) {
            this.thumb_pixel_width = event.target.value
            if( this.thumb_pixel_width < 75 ) {
              this.caption_pixel_padding = 0
              $( '.thumbnail .caption' ).css("padding", "0")
            } else {
              this.caption_pixel_padding = 9
              $( '.thumbnail .caption' ).css("padding", "9px")
            }
            $( '.thumbnail' ).css("max-width", this.thumb_pixel_width + 'px' )
  				}
        });

        this._on(this.document, {
  				'click#select_all_btn': function(event) {
            _this.handleSelectAll()
  				}
  			});

        this._on(this.document, {
          'click#select_none_btn': function(event) {
            _this.handleSelectNone()
          }
        });

        this._on(this.document, {
          'click#select_alternate_btn': function(event) {
            _this.handleSelectAlternate()
          }
        });

        this._on(this.document, {
          'click#select_inverse_btn': function(event) {
            _this.handleSelectInverse()
          }
        });

        // Note: this relies on jQuery UI Sortable widget
        // Since jQuery UI is a dependency, we can lean on it
        // and look into optimizing with HTML5 native DnD if needed
        this.element.find( "#sortable" ).sortable({
          update: function( event, ui ) {
            var sortedIDs = $( "#sortable" ).sortable( "toArray" );
            _this._saveSort(sortedIDs);
          }
        });

        // paint the img_collection here
        this.refresh()
    },
    applyChanges: function() {
      // TODO: Keep a list of all changed ids so we can mark them as changed
      // or perhaps add a property to images
      for (i = 0; i < this.options.selected.length; i++) {
        var index = this.options.images.map(function(img) {
          return img.id;
        }).indexOf(this.options.selected[i].id);
        this.options.images[index] = this.options.selected[i]
        if (this.changeList.indexOf(this.options.selected[i].id) == -1) {
          this.changeList.push(this.options.selected[i].id)
        }
      }

      this.refresh()
    },
    _createGallery: function() {
      var $content = $('

')
      var $gallery_controls = $('
' +
          'Select: <button id="select_all_btn" class="btn btn-default btn-sm"> All</button>' +
          '<button id="select_none_btn" class="btn btn-default btn-sm"> None</button>' +
          '<button id="select_alternate_btn"class="btn btn-default btn-sm"> Alternate</button>' +
          '<button id="select_inverse_btn" class="btn btn-default btn-sm"> Inverse</button>' +
          '
 <input style="display:inline-block;" type="range" min="40" max="400" value="200">
' +
          '
')
      var $gallery = $('

')
      $content.append( $gallery_controls )
      $content.append( $gallery )
      this.element.append( $content )
    },
    _createDetailSidebar: function() {
      var $sidebar = $('

')
      var $detail = $('

')
      var $img_detail = $('')
      $detail.append( $img_detail )
      $sidebar.append( $detail )
      this.element.append( $sidebar )
    },
    _createForm: function() {
      var $form_panel = $('

')
      var $noneSelected = $('
No items selected.
')
      var $multiSelected = $('
Multiple items are selected.
')
      var singleSelected = '<form id="singleSelected" class="formContent form-horizontal">'+
                   '
' +
                      'Label' +
                      '<input type="text" name="label" id="label" value="1" class="form-control">' +
                   '
' +
                   '
' +
                   'Page Type' +
                      '<select id="pageType" class="form-control">' +
                      '<option value="single">Single Page (Default)</option>' +
                      '<option value="non-paged">Non-Paged</option>' +
                      '<option value="facing">Facing Pages</option>' +
                    '</select>' +
                  '
' +
                  '
' +
                    '
' +
                      '' +
                        '<input id="isThumb" type="checkbox" value="">' +
                        'Set as Thumbnail (?)' +
                      '' +
                  '
' +
                    '
' +
                      '' +
                        '<input id="isStart" type="checkbox" value="">' +
                        'Set as Start Page (?)' +
                      '' +
                    '
' +
                  '
' +
                  '<input id="canvas_id" type="hidden" name="canvas_id"></form>'

      $form_panel.append( $noneSelected, $multiSelected, singleSelected )
      this.element.append( $form_panel )
    },
    _createControls: function() {
      var $controls = $('

 Page order has changed.

')
      var $button = $('<button id="save_btn" name="button" type="button" class="btn btn-lg btn-primary">Save</button>')
      $controls.append( $button )
      this.element.append( $controls )
    },
    deselectAll: function() {
      this.options.selected = []
    },
    deselectOne: function() {
      this.options.selected.splice(this.getImageById(event.target.id), 1)
    },
    getImageIndexById: function( id ) {
      return this.options.images.map(function(image) {
        return image.id
      }).indexOf(id)
    },
    getImageById: function( id ) {
      var elementPos = this.getImageIndexById(id)
      return this.options.images[elementPos]
    },
    getSelectedIndexById: function( id ) {
      return this.options.selected.map(function(image) {
        return image.id
      }).indexOf(id)
    },
    handleSelectAll: function() {
      this.options.selected = this.options.images
      this._paintSelected()
    },
    handleSelectAlternate: function() {
      this.options.selected = []
      var img_total = this.options.images.length
      for (i = 0; i < img_total; i=i+2) {
        this.options.selected.push(this.options.images[i])
      }
      this._paintSelected()
    },
    handleSelectInverse: function() {
      this.options.selected = []
      var img_total = this.options.images.length
      for (i = 1; i < img_total; i=i+2) {
        this.options.selected.push(this.options.images[i])
      }
      this._paintSelected()
    },
    handleSelectNone: function() {
      this.deselectAll()
      this._paintSelected()
    },
    handleSelectPage: function( event ) {
      var image_id = $(event.target).find( "img" ).attr('id');
      if (event.metaKey) {
        if(this.isSelected(image_id)){
          this.options.selected.splice(this.getSelectedIndexById(image_id), 1)
          console.log(this.options.selected)
        }else{
          this.options.selected.push(this.getImageById(image_id))
        }
      } else if (event.shiftKey) {
        console.log("Shift + Click")
      } else {
        this.deselectAll()
        this.options.selected.push(this.getImageById(image_id))
      }

      // update form panel
      $( '.formContent' ).hide()
      switch (this.options.selected.length) {
        case 0:
          $( '#noneSelected' ).show()
          break
        case 1:
          var selected = this.options.selected[0]
          $( '#label' ).val(selected.label)
          $( '#pageType option[value="'+ selected.pageType +'"]' ).prop('selected', true)
          $( '#isThumb' ).prop( "checked", selected.isThumb )
          $( '#isStart' ).prop( "checked", selected.isStart )
          $( '#canvas_id' ).val(selected.id)
          $( '#detail_img' ).attr("src",selected.url)
          $( '#singleSelected' ).show()
          break
        default:
          $( '#multiSelected' ).show()
      }
      this._paintSelected()
    },
    hasChanged: function(id) {
      if (this.changeList.indexOf(id) == -1) {
        return false
      } else {
        return true
      }
    },
    isSelected: function(select_id) {
      var index = this.options.selected.map(function(img) {
        return img.id;
      }).indexOf(select_id);
      if( index == -1) {
        return false
      } else {
        return true
      }
    },
    originalOrderChanged: function( sortedIDs ) {
      return JSON.stringify(this.original_order) !== JSON.stringify(sortedIDs);
    },
    _paintPages: function() {
      var totalImages = this.options.images.length
      _this = this
      for (var i = 0; i < totalImages; i++) {
        var item_state = _this.hasChanged(_this.options.images[i].id) ? 'hasChanged' : ''
        var $thumbnail = $( "

" )
        img_markup = "
" + _this.options.images[i].label + "
"
        $thumbnail.append(img_markup)
        $( '#sortable' ).append($thumbnail)
      }
      this._paintSelected()
      // set original order
      if( this.original_order.length === 0 ){
        this.original_order = $( "#sortable" ).sortable( "toArray" );
      }
    },
    _paintSelected: function() {
      // loop through and add class to all the ids
      $('.thumbnail').removeClass('selected')
      for (i = 0; i < this.options.selected.length; i++) {
        var element = document.getElementById(this.options.selected[i].id).parentElement
        element.className += " selected";
      }
    },
    _saveSort: function( sortedIDs ) {
      var new_imgArr = [];
      var arrayLength = sortedIDs.length;
      for (var i = 0; i < arrayLength; i++) {
        new_imgArr[i] = this.options.images[sortedIDs[i]];
      }
      this.options.images = new_imgArr;
      if( this.originalOrderChanged( sortedIDs ) ) {
        $( "#orderChangedIcon" ).show();
      } else {
        $( "#orderChangedIcon" ).hide();
      }
    },
    _setOption: function( key, value ) {
        this._super( key, value );
    },
    _setOptions: function( options ) {
        this._super( options );
    },
    refresh: function() {
      $( "#sortable" ).empty();
      this._paintPages()
    },
    reset: function() {
      this._destroy();
      this._create();
    },
    save: function() {
      this.applyChanges()
      //  This could save it to a server if we want
      //  but for now just emit an event
      this.element.trigger( "objectSaved", [this.options.images] )
      // TODO: investigate why reset seems to prevent us from listening for the 'metaKey'
      // Implementing "softReset()"" now as a workaround.
      // this.reset()
      this.softReset()
      alert('Changes saved!')
    },
    softReset: function() {
      this.deselectAll()
      this.changeList = []
      this.refresh()
    },
    _destroy: function() {
        this.element
            .removeClass( "filemanager" )
            .text( "" );
    }
});
})( jQuery, window, document );
<commit_msg>fixes #17<commit_after>;(function ( $, window, document, undefined ) {
$.widget( "figgy.filemanager", {
    options: {
        images: [],
        selected: [],
        blank: {
          'id': '',
          'label': '',
          'url': '',
          'pageType': 'single',
          'isThumb': null,
          'isStart': null
        }
    },
    _create: function() {
        this.thumb_pixel_width = 200
        this.caption_pixel_padding = 9
        this.sortedIDs = []
        this.changeList = []
        this.original_order = []
        this._createGallery()
        this._createDetailSidebar()
        this._createForm()
        this._createControls()

        var _this = this;

        // add event handlers
        this._on(this.document, {
  				'click.thumbnail': function(event) {
            _this.handleSelectPage(event)
  				}
  			});

        this._on(this.document, {
          'click#save_btn': function(event) {
            this.save()
  				}
        });

        // md form listeners
        this._on(this.document, {
          'input#label': function(event) {
            this.options.selected[0].label = $( '#label' ).val()
            this.applyChanges()
  				}
        });

        // gallery toolbar listeners
        this._on(this.document, {
          'input[type=range]': function(event) {
            this.thumb_pixel_width = event.target.value
            if( this.thumb_pixel_width < 75 ) {
              this.caption_pixel_padding = 0
              $( '.thumbnail .caption' ).css("padding", "0")
            } else {
              this.caption_pixel_padding = 9
              $( '.thumbnail .caption' ).css("padding", "9px")
            }
            $( '.thumbnail' ).css("max-width", this.thumb_pixel_width + 'px' )
  				}
        });

        this._on(this.document, {
  				'click#select_all_btn': function(event) {
            _this.handleSelectAll()
  				}
  			});

        this._on(this.document, {
          'click#select_none_btn': function(event) {
            _this.handleSelectNone()
          }
        });

        this._on(this.document, {
          'click#select_alternate_btn': function(event) {
            _this.handleSelectAlternate()
          }
        });

        this._on(this.document, {
          'click#select_inverse_btn': function(event) {
            _this.handleSelectInverse()
          }
        });

        // Note: this relies on jQuery UI Sortable widget
        // Since jQuery UI is a dependency, we can lean on it
        // and look into optimizing with HTML5 native DnD if needed
        this.element.find( "#sortable" ).sortable({
          update: function( event, ui ) {
            _this._saveSort();
          }
        });

        // paint the img_collection here
        this.refresh()
    },
    applyChanges: function() {
      // TODO: Keep a list of all changed ids so we can mark them as changed
      // or perhaps add a property to images
      for (i = 0; i < this.options.selected.length; i++) {
        var index = this.options.images.map(function(img) {
          return img.id;
        }).indexOf(this.options.selected[i].id);
        this.options.images[index] = this.options.selected[i]
        if (this.changeList.indexOf(this.options.selected[i].id) == -1) {
          this.changeList.push(this.options.selected[i].id)
        }
      }

      this.refresh()
    },
    _createGallery: function() {
      var $content = $('

')
      var $gallery_controls = $('
' +
          'Select: <button id="select_all_btn" class="btn btn-default btn-sm"> All</button>' +
          '<button id="select_none_btn" class="btn btn-default btn-sm"> None</button>' +
          '<button id="select_alternate_btn"class="btn btn-default btn-sm"> Alternate</button>' +
          '<button id="select_inverse_btn" class="btn btn-default btn-sm"> Inverse</button>' +
          '
 <input style="display:inline-block;" type="range" min="40" max="400" value="200">
' +
          '
')
      var $gallery = $('

')
      $content.append( $gallery_controls )
      $content.append( $gallery )
      this.element.append( $content )
    },
    _createDetailSidebar: function() {
      var $sidebar = $('

')
      var $detail = $('

')
      var $img_detail = $('')
      $detail.append( $img_detail )
      $sidebar.append( $detail )
      this.element.append( $sidebar )
    },
    _createForm: function() {
      var $form_panel = $('

')
      var $noneSelected = $('
No items selected.
')
      var $multiSelected = $('
Multiple items are selected.
')
      var singleSelected = '<form id="singleSelected" class="formContent form-horizontal">'+
                   '
' +
                      'Label' +
                      '<input type="text" name="label" id="label" value="1" class="form-control">' +
                   '
' +
                   '
' +
                   'Page Type' +
                      '<select id="pageType" class="form-control">' +
                      '<option value="single">Single Page (Default)</option>' +
                      '<option value="non-paged">Non-Paged</option>' +
                      '<option value="facing">Facing Pages</option>' +
                    '</select>' +
                  '
' +
                  '
' +
                    '
' +
                      '' +
                        '<input id="isThumb" type="checkbox" value="">' +
                        'Set as Thumbnail (?)' +
                      '' +
                  '
' +
                    '
' +
                      '' +
                        '<input id="isStart" type="checkbox" value="">' +
                        'Set as Start Page (?)' +
                      '' +
                    '
' +
                  '
' +
                  '<input id="canvas_id" type="hidden" name="canvas_id"></form>'

      $form_panel.append( $noneSelected, $multiSelected, singleSelected )
      this.element.append( $form_panel )
    },
    _createControls: function() {
      var $controls = $('

 Page order has changed.

')
      var $button = $('<button id="save_btn" type="button" class="btn btn-lg btn-primary disabled">Save</button>')

      $controls.append( $button )
      this.element.append( $controls )
    },
    deselectAll: function() {
      this.options.selected = []
    },
    deselectOne: function() {
      this.options.selected.splice(this.getImageById(event.target.id), 1)
    },
    getImageIndexById: function( id ) {
      return this.options.images.map(function(image) {
        return image.id
      }).indexOf(id)
    },
    getImageById: function( id ) {
      var elementPos = this.getImageIndexById(id)
      return this.options.images[elementPos]
    },
    getSelectedIndexById: function( id ) {
      return this.options.selected.map(function(image) {
        return image.id
      }).indexOf(id)
    },
    handleSelectAll: function() {
      this.options.selected = this.options.images
      this._paintSelected()
    },
    handleSelectAlternate: function() {
      this.options.selected = []
      var img_total = this.options.images.length
      for (i = 0; i < img_total; i=i+2) {
        this.options.selected.push(this.options.images[i])
      }
      this._paintSelected()
    },
    handleSelectInverse: function() {
      this.options.selected = []
      var img_total = this.options.images.length
      for (i = 1; i < img_total; i=i+2) {
        this.options.selected.push(this.options.images[i])
      }
      this._paintSelected()
    },
    handleSelectNone: function() {
      this.deselectAll()
      this._paintSelected()
    },
    handleSelectPage: function( event ) {
      var image_id = $(event.target).find( "img" ).attr('id');
      if (event.metaKey) {
        if(this.isSelected(image_id)){
          this.options.selected.splice(this.getSelectedIndexById(image_id), 1)
          console.log(this.options.selected)
        }else{
          this.options.selected.push(this.getImageById(image_id))
        }
      } else if (event.shiftKey) {
        console.log("Shift + Click")
      } else {
        this.deselectAll()
        this.options.selected.push(this.getImageById(image_id))
      }

      // update form panel
      $( '.formContent' ).hide()
      switch (this.options.selected.length) {
        case 0:
          $( '#noneSelected' ).show()
          break
        case 1:
          var selected = this.options.selected[0]
          $( '#label' ).val(selected.label)
          $( '#pageType option[value="'+ selected.pageType +'"]' ).prop('selected', true)
          $( '#isThumb' ).prop( "checked", selected.isThumb )
          $( '#isStart' ).prop( "checked", selected.isStart )
          $( '#canvas_id' ).val(selected.id)
          $( '#detail_img' ).attr("src",selected.url)
          $( '#singleSelected' ).show()
          break
        default:
          $( '#multiSelected' ).show()
      }
      this._paintSelected()
    },
    hasChanged: function(id) {
      if (this.changeList.indexOf(id) == -1) {
        return false
      } else {
        return true
      }
    },
    isSelected: function(select_id) {
      var index = this.options.selected.map(function(img) {
        return img.id;
      }).indexOf(select_id);
      if( index == -1) {
        return false
      } else {
        return true
      }
    },
    originalOrderChanged: function() {
      if( this.sortedIDs.length == 0 ) { // if it has been sorted at least once
        return false
      } else {
        return JSON.stringify(this.original_order) !== JSON.stringify(this.sortedIDs)
      }
    },
    _paintControls: function() {
      // disable/enable save button if anything has changed
      if( this._saveWorthy() ){
        $( "#save_btn" ).removeClass('disabled')
      } else {
        $( "#save_btn" ).addClass('disabled')
      }
      if( this.originalOrderChanged() ) {
        $( "#orderChangedIcon" ).show();
      } else {
        $( "#orderChangedIcon" ).hide();
      }
    },
    _paintPages: function() {
      var totalImages = this.options.images.length
      _this = this
      for (var i = 0; i < totalImages; i++) {
        var item_state = _this.hasChanged(_this.options.images[i].id) ? 'hasChanged' : ''
        var $thumbnail = $( "

" )
        img_markup = "
" + _this.options.images[i].label + "
"
        $thumbnail.append(img_markup)
        $( '#sortable' ).append($thumbnail)
      }
      this._paintSelected()
      // set original order
      if( this.original_order.length === 0 ){
        this.original_order = $( "#sortable" ).sortable( "toArray" );
      }
    },
    _paintSelected: function() {
      // loop through and add class to all the ids
      $('.thumbnail').removeClass('selected')
      for (i = 0; i < this.options.selected.length; i++) {
        var element = document.getElementById(this.options.selected[i].id).parentElement
        element.className += " selected";
      }
    },
    _saveSort: function() {
      this.sortedIDs = $( "#sortable" ).sortable( "toArray" );
      var new_imgArr = []
      var arrayLength = this.sortedIDs.length
      for (var i = 0; i < arrayLength; i++) {
        new_imgArr[i] = this.options.images[this.sortedIDs[i]]
      }
      this.options.images = new_imgArr
      this._paintControls()
    },
    _saveWorthy: function( ) {
        if( this.changeList.length || this.originalOrderChanged() ){
          return true
        } else {
          return false
        }
    },
    _setOption: function( key, value ) {
        this._super( key, value )
    },
    _setOptions: function( options ) {
        this._super( options )
    },
    refresh: function() {
      $( "#sortable" ).empty();
      this._paintPages()
      this._paintControls()
    },
    reset: function() {
      this._destroy();
      this._create();
    },
    save: function() {
      this.applyChanges()
      //  This could save it to a server if we want
      //  but for now just emit an event
      this.element.trigger( "objectSaved", [this.options.images] )
      // TODO: investigate why reset seems to prevent us from listening for the 'metaKey'
      // Implementing "softReset()"" now as a workaround.
      // this.reset()
      this.softReset()
      alert('Changes saved!')
    },
    softReset: function() {
      this.deselectAll()
      this.changeList = []
      this.refresh()
    },
    _destroy: function() {
        this.element
            .removeClass( "filemanager" )
            .text( "" );
    }
});
})( jQuery, window, document );
<|endoftext|>


<commit_before>"use strict"

class TypeHandler{
  constructor(folderView){
    this.folderView = folderView
    this.types = entity.types

    this.typesSelectValues = ""
    this.typesSelectValuesNoFolder = ""
    for(let t in this.types){
      if(t != "folder")
        this.typesSelectValuesNoFolder += `<option value="${t}">${this.types[t].title}</option>\n`
      this.typesSelectValues += `<option value="${t}">${this.types[t].title}</option>\n`
    }
  }

  addNewTypeChanged(e){

  }

  async addNewExecute(e){
    let typeInput = e.parent().find("select[name=type]")
    let titleInput = e.parent().find("input[name=title]")
    await mscp.add(this.folderView.path, typeInput.val(), titleInput.val())
    this.folderView.showFolder(this.folderView.path)
    e.parents(".toolbarbutton").removeClass("open")
    typeInput.val("folder")
    titleInput.val("")
  }

  async addExistingExecute(e){
    let typeInput = e.parent().find("select[name=type]")
    let titleInput = e.parent().find("input[name=title]")
    let identifierInput = e.parent().find("input[name=identifier]")
    await mscp.add(this.folderView.path, typeInput.val(), titleInput.val(), identifierInput.val())
    this.folderView.showFolder(this.folderView.path)
    e.parents(".toolbarbutton").removeClass("open")
    typeInput.val("folder")
    titleInput.val("")
    identifierInput.val("")
  }

  async openItem(item, e){
    e = e || {}
    let type = this.types[item.properties.type]
    switch(type.open.type){
      case "url":
        let url = await this.getShareableLink(item, true)

        if(type.open.dest === "window"){
          let width = type.open.windowWidth || 520
          let height = type.open.windowHeight || 570
          let left = Math.max(e.screenX - parseInt(width/2), 0)
          let top = Math.max(e.screenY - parseInt(height/2), 0)
          window.open(url, '_blank', `height=${height},width=${width},scrollbars=yes,status=no,left=${left},top=${top}`);
        } else
          window.open(url, '_blank');
        break;
    }
  }

  async getShareableLink(item, writeAccess){
    let type = this.types[item.properties.type]
    switch(type.open.type){
      case "url":

        let accessToken = ""
        if(type.open.includeAccessToken)
          accessToken = await mscp.getEntityAccessToken(item.id, writeAccess)

        let url = type.open.url
                    .replace("$id$", item.id)
                    .replace("$identifier$", item.properties.identifier || item.id)
                    .replace("$accesstoken$", accessToken)
                    .replace("$title$", item.properties.title)

        return url;
    }

    return ""
  }
}
<commit_msg>Misc fixes<commit_after>"use strict"

class TypeHandler{
  constructor(folderView){
    this.folderView = folderView
    this.types = entity.types

    this.typesSelectValues = ""
    this.typesSelectValuesNoFolder = ""
    for(let t in this.types){
      if(t != "folder")
        this.typesSelectValuesNoFolder += `<option value="${t}">${this.types[t].title}</option>\n`
      this.typesSelectValues += `<option value="${t}">${this.types[t].title}</option>\n`
    }
  }

  addNewTypeChanged(e){

  }

  async addNewExecute(e){
    let typeInput = e.parent().find("select[name=type]")
    let titleInput = e.parent().find("input[name=title]")
    await mscp.add(this.folderView.path, typeInput.val(), titleInput.val())
    this.folderView.showFolder(this.folderView.path)
    e.parents(".toolbarbutton").removeClass("open")
    typeInput.val("folder")
    titleInput.val("")
  }

  async addExistingExecute(e){
    let typeInput = e.parent().find("select[name=type]")
    let titleInput = e.parent().find("input[name=title]")
    let identifierInput = e.parent().find("input[name=identifier]")
    await mscp.add(this.folderView.path, typeInput.val(), titleInput.val(), identifierInput.val())
    this.folderView.showFolder(this.folderView.path)
    e.parents(".toolbarbutton").removeClass("open")
    typeInput.val("folder")
    titleInput.val("")
    identifierInput.val("")
  }

  async openItem(item, e){
    e = e || {}
    let type = this.types[item.properties.type]
    switch(type.open.type){
      case "url":
        let url = await this.getShareableLink(item, true)

        if(type.open.dest === "window"){
          let width = type.open.windowWidth || 520
          let height = type.open.windowHeight || 570
          let left = Math.max(e.screenX - parseInt(width/2), 0)
          let top = Math.max(e.screenY - parseInt(height/2), 0)
          window.open(url, '_blank', `height=${height},width=${width},scrollbars=yes,status=no,left=${left},top=${top}`);
        } else
          window.open(url, '_blank');
        break;
    }
  }

  async getShareableLink(item, writeAccess, permanentAccess){
    let type = this.types[item.properties.type]
    switch(type.open.type){
      case "url":

        let accessToken = ""
        if(type.open.includeAccessToken)
          accessToken = await mscp.getEntityAccessToken(item.id, writeAccess, permanentAccess)

        let url = type.open.url
                    .replace("$id$", item.id)
                    .replace("$identifier$", item.properties.identifier || item.id)
                    .replace("$accesstoken$", accessToken)
                    .replace("$title$", item.properties.title)

        return url;
    }

    return ""
  }
}
<|endoftext|>