<commit_before>var chai = require('chai'),
    should = chai.should(),
    flow = require('flow.js').flow,
    expect = chai.expect,
    testRunner = require('..').TestRunner.setup();

var C = {};

describe('js.core.Binding', function () {

    var target,
        model,
        returnValue = "HALLO",
        parameters = ["'abc'", 123, true, false, "null"],
        parStr = "abc",
        parNum = 123,
        extendedModel,
        ExtendedClass,
        bindingCreator;

    before(function (done) {

        flow()
            .seq(function (cb) {
                testRunner.requireClasses({
                    Binding: 'js/core/Binding',
                    Bindable: 'js/core/Bindable',
                    Parser: 'js/lib/parser',
                    BindingCreator: "js/core/BindingCreator"
                }, C, cb);
            })
            .seq(function () {
                ExtendedClass = C.Bindable.inherit('ExtendedClass', {
                    foo: function () {
                        return returnValue;
                    },
                    bar: function () {
                        var args = Array.prototype.slice.call(arguments);
                        if (args.length !== parameters.length) {
                            return false;
                        }
                        var parameter;
                        for (var i = 0; i < args.length; i++) {
                            parameter = parameters[i];
                            parameter = parameter === "null" ? null : parameter;
                            if (parameter && parameter.replace) {
                                parameter = parameter.replace(/'/gi, "");
                            }
                            if (args[i] !== parameter) {
                                return false;
                            }
                        }
                        return true;
                    },
                    foobar: function (a, b) {
                        return a === parStr && b === parNum;
                    },
                    calculatedAttribute: function () {
                        return "test";
                    }.onChange('m1'),

                    // MOCK FUNCTION
                    getScopeForFncName: function (name) {
                        if (this[name]) {
                            return this;
                        }
                        return null;
                    },
                    // MOCK FUNCTION
                    getScopeForKey: function (name) {
                        if (this.$[name]) {
                            return this;
                        }
                        return null;
                    }
                });

                bindingCreator = new C.BindingCreator();
            })
            .exec(done);

    });

    beforeEach(function () {
        target = new C.Bindable({val: null});
        model = new C.Bindable({});

        extendedModel = new ExtendedClass({});
    });

    describe('#one-way-binding', function () {

        it('simple value set should set value on binded target', function () {

            var b1 = new C.Binding({scope: model, path: 'a', target: target, targetKey: "val"});
            model.set("a", "A");

            expect(target.get('val')).to.be.equal('A');

        });

        it('path binding should be work on plain objects', function () {

            var b1 = new C.Binding({scope: model, path: 'a.b', target: target, targetKey: "val"});
            model.set("a", {
                b: 'A'
            });

            expect(target.get('val')).to.be.equal('A');

        });

        it('path binding a.b should return undefined if b is not set', function () {
            var b1 = new C.Binding({scope: model, path: 'a.b', target: target, targetKey: "val"});

            model.set("a", "A");
            should.equal(target.get('val'), undefined);

            var m1 = new C.Bindable({b: "B"});
            model.set("a", m1);
            target.get('val').should.equal('B');

            m1.set("b", "B2");
            target.get("val").should.equal('B2');

            var m2 = new C.Bindable({b: "AWESOME"});
            model.set("a", m2);
            target.get("val").should.equal("AWESOME");

            model.set('a', null);
            should.equal(target.get('val'), undefined);
        });

        it('path binding a.b.c should return undefined if b or c is not set', function () {
            var b1 = new C.Binding({scope: model, path: 'a.b.c', target: target, targetKey: "val"});

            model.set("a", "A");
            should.equal(target.get('val'), undefined);

            var m1 = new C.Bindable({b: "B"});
            model.set("a", m1);
            should.equal(target.get("val"), undefined);

            var m2 = new C.Bindable({c: "WHAT UP"});
            m1.set('b', m2);
            target.get("val").should.equal('WHAT UP');

            var m3 = new C.Bindable({b: "AWESOME"});
            model.set("a", m3);
            should.equal(target.get("val"), undefined);
        });

        it('path binding a.b.c should return c if b is just a json object', function () {
            var b1 = new C.Binding({scope: model, path: 'a.b.c', target: target, targetKey: "val"});

            var testValue = "TEST VALUE";

            should.equal(target.get('val'), null);

            model.set("a", {
                b: {
                    c: testValue
                }
            });

            should.equal(target.get('val'), testValue);
        });

    });

    describe('#BindingCreator', function () {

        it("should have the correct values, even with activated caching", function() {

            var m = new (C.Bindable.inherit({
                defaults: {
                    user: null,
                    repository: null,

                    href: "user/{user}/{repository}",
                    name: "{user}/{repository}"
                }
            }))();

            m.set({
                user: "it-ony",
                repository: "rAppid.js"
            });

            expect(m.$.name).to.eql("it-ony/rAppid.js");
            expect(m.$.href).to.eql("user/it-ony/rAppid.js");

        });

    });

    describe('#two way binding', function () {
        it('should set value on scope', function () {
            new C.Binding({scope: model, path: 'a', target: target, targetKey: "val", twoWay: true});

            model.set("a", "A");
            model.get('a').should.equal("A");
            target.set({val: 'TargetValue'});

            model.get("a").should.equal("TargetValue");
        });

        it('should set value if b in a.b is set', function () {
            var binding = new C.Binding({scope: model, path: 'a.b', target: target, targetKey: "val", twoWay: true});

            target.set({val: 'TargetValue'});
            should.equal(binding.getValue(), null);

            var m1 = new C.Bindable({b: "hello"});
            model.set("a", m1);

            m1.get('b').should.equal("hello");

            target.set('val', 'goodbye');

            m1.get('b').should.equal("goodbye");
        });
    });

    describe('#function binding', function () {

        it('foo() should call fnc and set returned value to target', function () {
            new C.Binding({scope: extendedModel, path: "foo()", target: target, targetKey: 'val'}).triggerBinding();
            target.get('val').should.equal(returnValue);
        });

        it('a.foo() should be triggered if "a" is set and has fnc', function () {
            new C.Binding({scope: model, path: 'a.foo()', target: target, targetKey: 'val'}).triggerBinding();
            should.not.exist(target.get('val'));

            var m1 = new C.Bindable({b: "what"});
            model.set('a', m1);

            should.not.exist(target.get('val'));

            model.set('a', extendedModel);

            target.get('val').should.equal(returnValue);
        });

        var fncBinding = "bar(" + parameters.join(",") + ")";

        it(fncBinding + " should call bar with parameters and return true", function () {
            new C.Binding({scope: extendedModel, path: fncBinding, target: target, targetKey: 'val'}).triggerBinding();
            target.get('val').should.equal(true);
        });

        var fncBinding2 = "foobar(m1.a,m1.b)";

        it(fncBinding2 + ' should be triggered if m1.a or m1.b is changing', function () {
            var extendedTarget = new ExtendedClass();
            // extendedTarget.set('m1', new ExtendedClass({}));
            extendedModel.set('m1', extendedTarget);
            new C.Binding({scope: extendedModel, path: fncBinding2, target: extendedTarget, targetKey: 'val'}).triggerBinding();
            extendedTarget.get('val').should.equal(false);

            extendedTarget.set({'a': parStr, 'b': parNum});
            extendedTarget.get('val').should.equal(true);

            extendedModel.set('m1', new ExtendedClass({
                a: 'a',
                b: 'b'
            }));
            extendedTarget.get('val').should.equal(false);
        });

    });

    describe('#destroy', function () {

        it('should be called on target.destroy', function () {
            var binding = new C.Binding({scope: extendedModel, path: "foo()", target: target, targetKey: 'val'}),
                binding2 = new C.Binding({scope: extendedModel, path: "foobar(m1.a,m1.b)", target: target, targetKey: 'val2'}),
                binding3 = new C.Binding({scope: extendedModel, path: "calculatedAttribute()", target: target, targetKey: 'val3'});

            target.destroy();

            extendedModel.set('m1', new ExtendedClass({
                a: 'a',
                b: 'b'
            }));

            expect(binding.$).to.not.exist;
            expect(binding2.$).to.not.exist;
            expect(binding3.$).to.not.exist;

        });

        it('should be called on scope.destroy', function () {
            var binding = new C.Binding({scope: extendedModel, path: "foo()", target: target, targetKey: 'val'}),
                binding2 = new C.Binding({scope: extendedModel, path: "foobar(m1.a,m1.b)", target: target, targetKey: 'val2'}),
                binding3 = new C.Binding({scope: extendedModel, path: "calculatedAttribute()", target: target, targetKey: 'val3'});

            extendedModel.destroy();

            expect(binding.$).to.not.exist;
            expect(binding2.$).to.not.exist;
            expect(binding3.$).to.not.exist;

        });

    });
});<commit_msg>added test for chained function binding<commit_after>var chai = require('chai'),
    should = chai.should(),
    flow = require('flow.js').flow,
    expect = chai.expect,
    testRunner = require('..').TestRunner.setup();

var C = {};

describe('js.core.Binding', function () {

    var target,
        model,
        returnValue = "HALLO",
        parameters = ["'abc'", 123, true, false, "null"],
        parStr = "abc",
        parNum = 123,
        extendedModel,
        ExtendedClass,
        bindingCreator;

    before(function (done) {

        flow()
            .seq(function (cb) {
                testRunner.requireClasses({
                    Binding: 'js/core/Binding',
                    Bindable: 'js/core/Bindable',
                    Parser: 'js/lib/parser',
                    BindingCreator: "js/core/BindingCreator"
                }, C, cb);
            })
            .seq(function () {
                ExtendedClass = C.Bindable.inherit('ExtendedClass', {
                    foo: function () {
                        return returnValue;
                    },
                    bar: function () {
                        var args = Array.prototype.slice.call(arguments);
                        if (args.length !== parameters.length) {
                            return false;
                        }
                        var parameter;
                        for (var i = 0; i < args.length; i++) {
                            parameter = parameters[i];
                            parameter = parameter === "null" ? null : parameter;
                            if (parameter && parameter.replace) {
                                parameter = parameter.replace(/'/gi, "");
                            }
                            if (args[i] !== parameter) {
                                return false;
                            }
                        }
                        return true;
                    },
                    foobar: function (a, b) {
                        return a === parStr && b === parNum;
                    },

                    calculatedAttribute: function () {
                        return "test";
                    }.onChange('m1'),

                    // MOCK FUNCTION
                    getScopeForFncName: function (name) {
                        if (this[name]) {
                            return this;
                        }
                        return null;
                    },
                    // MOCK FUNCTION
                    getScopeForKey: function (name) {
                        if (this.$[name]) {
                            return this;
                        }
                        return null;
                    }
                });

                bindingCreator = new C.BindingCreator();
            })
            .exec(done);

    });

    beforeEach(function () {
        target = new C.Bindable({val: null});
        model = new C.Bindable({});

        extendedModel = new ExtendedClass({});
    });

    describe('#one-way-binding', function () {

        it('simple value set should set value on binded target', function () {

            var b1 = new C.Binding({scope: model, path: 'a', target: target, targetKey: "val"});
            model.set("a", "A");

            expect(target.get('val')).to.be.equal('A');

        });

        it('path binding should be work on plain objects', function () {

            var b1 = new C.Binding({scope: model, path: 'a.b', target: target, targetKey: "val"});
            model.set("a", {
                b: 'A'
            });

            expect(target.get('val')).to.be.equal('A');

        });

        it('path binding a.b should return undefined if b is not set', function () {
            var b1 = new C.Binding({scope: model, path: 'a.b', target: target, targetKey: "val"});

            model.set("a", "A");
            should.equal(target.get('val'), undefined);

            var m1 = new C.Bindable({b: "B"});
            model.set("a", m1);
            target.get('val').should.equal('B');

            m1.set("b", "B2");
            target.get("val").should.equal('B2');

            var m2 = new C.Bindable({b: "AWESOME"});
            model.set("a", m2);
            target.get("val").should.equal("AWESOME");

            model.set('a', null);
            should.equal(target.get('val'), undefined);
        });

        it('path binding a.b.c should return undefined if b or c is not set', function () {
            var b1 = new C.Binding({scope: model, path: 'a.b.c', target: target, targetKey: "val"});

            model.set("a", "A");
            should.equal(target.get('val'), undefined);

            var m1 = new C.Bindable({b: "B"});
            model.set("a", m1);
            should.equal(target.get("val"), undefined);

            var m2 = new C.Bindable({c: "WHAT UP"});
            m1.set('b', m2);
            target.get("val").should.equal('WHAT UP');

            var m3 = new C.Bindable({b: "AWESOME"});
            model.set("a", m3);
            should.equal(target.get("val"), undefined);
        });

        it('path binding a.b.c should return c if b is just a json object', function () {
            var b1 = new C.Binding({scope: model, path: 'a.b.c', target: target, targetKey: "val"});

            var testValue = "TEST VALUE";

            should.equal(target.get('val'), null);

            model.set("a", {
                b: {
                    c: testValue
                }
            });

            should.equal(target.get('val'), testValue);
        });

    });

    describe('#BindingCreator', function () {

        it("should have the correct values, even with activated caching", function() {

            var m = new (C.Bindable.inherit({
                defaults: {
                    user: null,
                    repository: null,

                    href: "user/{user}/{repository}",
                    name: "{user}/{repository}"
                }
            }))();

            m.set({
                user: "it-ony",
                repository: "rAppid.js"
            });

            expect(m.$.name).to.eql("it-ony/rAppid.js");
            expect(m.$.href).to.eql("user/it-ony/rAppid.js");

        });

    });

    describe('#two way binding', function () {
        it('should set value on scope', function () {
            new C.Binding({scope: model, path: 'a', target: target, targetKey: "val", twoWay: true});

            model.set("a", "A");
            model.get('a').should.equal("A");
            target.set({val: 'TargetValue'});

            model.get("a").should.equal("TargetValue");
        });

        it('should set value if b in a.b is set', function () {
            var binding = new C.Binding({scope: model, path: 'a.b', target: target, targetKey: "val", twoWay: true});

            target.set({val: 'TargetValue'});
            should.equal(binding.getValue(), null);

            var m1 = new C.Bindable({b: "hello"});
            model.set("a", m1);

            m1.get('b').should.equal("hello");

            target.set('val', 'goodbye');

            m1.get('b').should.equal("goodbye");
        });
    });

    describe('#function binding', function () {

        it('foo() should call fnc and set returned value to target', function () {
            new C.Binding({scope: extendedModel, path: "foo()", target: target, targetKey: 'val'}).triggerBinding();
            target.get('val').should.equal(returnValue);
        });

        it('a.foo() should be triggered if "a" is set and has fnc', function () {
            new C.Binding({scope: model, path: 'a.foo()', target: target, targetKey: 'val'}).triggerBinding();
            should.not.exist(target.get('val'));

            var m1 = new C.Bindable({b: "what"});
            model.set('a', m1);

            should.not.exist(target.get('val'));

            model.set('a', extendedModel);

            target.get('val').should.equal(returnValue);
        });

        var fncBinding = "bar(" + parameters.join(",") + ")";

        it(fncBinding + " should call bar with parameters and return true", function () {
            new C.Binding({scope: extendedModel, path: fncBinding, target: target, targetKey: 'val'}).triggerBinding();
            target.get('val').should.equal(true);
        });

        var fncBinding2 = "foobar(m1.a,m1.b)";

        it(fncBinding2 + ' should be triggered if m1.a or m1.b is changing', function () {
            var extendedTarget = new ExtendedClass();
            // extendedTarget.set('m1', new ExtendedClass({}));
            extendedModel.set('m1', extendedTarget);
            new C.Binding({scope: extendedModel, path: fncBinding2, target: extendedTarget, targetKey: 'val'}).triggerBinding();
            extendedTarget.get('val').should.equal(false);

            extendedTarget.set({'a': parStr, 'b': parNum});
            extendedTarget.get('val').should.equal(true);

            extendedModel.set('m1', new ExtendedClass({
                a: 'a',
                b: 'b'
            }));
            extendedTarget.get('val').should.equal(false);
        });

        it("address().toString() should return value of toString()", function(){
            var Person = ExtendedClass.inherit({
                defaults: {
                    address: null
                },
                address: function(){
                    return this.$.address;
                }.onChange('address')
            });

            var Address = ExtendedClass.inherit({
                defaults: {
                    city: "",
                    street: ""
                },
                toString: function(){

                    return this.$.city + " " + this.$.street;
                }
            });

            var person = new Person();
            var address = new Address({
                city: "City1",
                street: "Street1"
            });
            var target = new ExtendedClass();

            new C.Binding({scope: person, path: "address().toString()", target: target, targetKey: 'val'}).triggerBinding();

            should.not.exist(target.get('val'));

            person.set('address',address);

            should.exist(target.get('val'));
            target.get('val').should.equal("City1 Street1");
        });

    });

    describe('#destroy', function () {

        it('should be called on target.destroy', function () {
            var binding = new C.Binding({scope: extendedModel, path: "foo()", target: target, targetKey: 'val'}),
                binding2 = new C.Binding({scope: extendedModel, path: "foobar(m1.a,m1.b)", target: target, targetKey: 'val2'}),
                binding3 = new C.Binding({scope: extendedModel, path: "calculatedAttribute()", target: target, targetKey: 'val3'});

            target.destroy();

            extendedModel.set('m1', new ExtendedClass({
                a: 'a',
                b: 'b'
            }));

            expect(binding.$).to.not.exist;
            expect(binding2.$).to.not.exist;
            expect(binding3.$).to.not.exist;

        });

        it('should be called on scope.destroy', function () {
            var binding = new C.Binding({scope: extendedModel, path: "foo()", target: target, targetKey: 'val'}),
                binding2 = new C.Binding({scope: extendedModel, path: "foobar(m1.a,m1.b)", target: target, targetKey: 'val2'}),
                binding3 = new C.Binding({scope: extendedModel, path: "calculatedAttribute()", target: target, targetKey: 'val3'});

            extendedModel.destroy();

            expect(binding.$).to.not.exist;
            expect(binding2.$).to.not.exist;
            expect(binding3.$).to.not.exist;

        });

    });
});<|endoftext|>


<commit_before>"use strict"

const asset = require('./asset')
const colorDepth = require('./converter/colorDepth')
const clamp = require('lodash/clamp')
const glob = require('glob')
const createMappoSession = require('./createMappoSession')
const createMappoMap = require('./createMappoMap')
const createMappoTileset = require('./createMappoTileset')
const detectFormat = require('./detectFormat')
const path = require('path')
const fs = require('fs')
const setupKeyboard = require('./setupKeyboard')
const renderTileHighlightInvertedOutline = require('./renderTileHighlightInvertedOutline')
const renderTileHighlightInvertedSolid = require('./renderTileHighlightInvertedSolid')
const renderTileHighlightColorOutline = require('./renderTileHighlightColorOutline')
const renderTile = require('./renderTile')
const renderLayer = require('./renderLayer')
const renderTileset = require('./renderTileset')
const createCheckerboardPattern = require('./createCheckerboardPattern')
const calcAutoScroll = require('./calcAutoScroll')
const clearCanvas = require('./clearCanvas')
const loadMappoMap = require('./loadMappoMap')

// DOM REFERENCES
const pageTitle = document.querySelector('title')
const mapList = document.querySelector('.map-list')
const layerList = document.querySelector('.layer-list')
const canvas = document.querySelector('.mappo-viewport')
const context = canvas.getContext('2d')
const tilesetCanvasContainer = document.querySelector('.tileset-canvas-container')
const tilesetCanvas = document.querySelector('.tileset-canvas')
const tilesetContext = tilesetCanvas.getContext('2d')
const tilesetSelectedTileCanvas = document.querySelector('.tileset-selected-tile-canvas')
const tilesetSelectedTileContext = tilesetSelectedTileCanvas.getContext('2d')
const tilesetSelectedTileIndex = document.querySelector('.tileset-selected-tile-index')
const tilesetHoveringTileCanvas = document.querySelector('.tileset-hovering-tile-canvas')
const tilesetHoveringTileContext = tilesetHoveringTileCanvas.getContext('2d')
const tilesetHoveringTileIndex = document.querySelector('.tileset-hovering-tile-index')
const middlePanel = document.querySelector('.middle-panel')

const checkerboardPattern = createCheckerboardPattern({document})

const launchFolder = 'data' // TODO(chuck): temp hack for windows. empty string dunna work
console.log('launchFolder', launchFolder)
const mapGlob = '**/*.map'
const mapFilenames = glob.sync(mapGlob, {nocase: true})
const mappoSession = createMappoSession({
  fileSystem: {
    files: mapFilenames
  },
  launchFolder
})

const viewportScales = [0.25, 0.5, 0.75, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const DEFAULT_SCALE_INDEX = 4
const defaultMappoState = {
  scaleIndex: DEFAULT_SCALE_INDEX,
  isLoading: true,
  map: null,
  mapLayerOrder: null,
  mapLayerSelected: null,
  tileset: null,
  tilesetTileHovering: null,
  tilesetTileSelected: null,
  camera: {
    x: 0,
    y: 0,
    move: {
      x: 0,
      y: 0,
    },
  },
  cameraScrollAmount: 1,
  keyPressed: {},
  mouseDown: false,
  mouseInViewport: false,
  autoScroll: {},
}
let mappoState = Object.assign({}, defaultMappoState)

const getScale = () => viewportScales[mappoState.scaleIndex]

mappoSession.getMapFilenames().forEach(mapFilename => {
  const li = document.createElement('li')
  li.setAttribute('title', mapFilename)
  li.innerText = mapFilename
  // TODO(chuck): temp hack for windows. figure out better launchFolder shenanigans
  li.addEventListener('click', event => {
    mappoState = Object.assign({}, defaultMappoState)
    mappoState.isLoading = true

    mappoState.map = loadMappoMap({context, mapFilename: 'data/' + mapFilename})

    mappoState.mapLayerSelected = mappoState.map.tileLayers[mappoState.map.mapLayerOrder[0]]
    refreshMapLayerList()

    tilesetSelectedTileCanvas.width = mappoState.map.tileset.tileWidth
    tilesetSelectedTileCanvas.height = mappoState.map.tileset.tileHeight
    tilesetHoveringTileCanvas.width = mappoState.map.tileset.tileWidth
    tilesetHoveringTileCanvas.height = mappoState.map.tileset.tileHeight

    // TODO(chuck): shorten this crazy chain
    mappoState.map.tileset.imageBitmapPromise.then(() => {
      mappoState.isLoading = false
      resizeCanvas()
    })

    pageTitle.innerText = 'Mappo - ' + mapFilename
  })
  mapList.appendChild(li)
})

const refreshMapLayerList = () => {
  layerList.innerHTML = ''
  mappoState.map.tileLayers.forEach((layer, index) => {
    const li = document.createElement('li')
    li.setAttribute('title', layer.description)
    li.innerText = layer.description
    li.classList.add('layer-list-item')
    if (mappoState.mapLayerSelected === layer) {
      li.classList.add('selected')
    }
    li.addEventListener('click', event => {
      // TODO(chuck): use a proper element?
      if (event.offsetX < 35) {
        li.classList.toggle('is-layer-hidden')
        layer.isHidden = !layer.isHidden
      }
      mappoState.mapLayerSelected = layer
      const layerListItems = document.querySelectorAll('.layer-list .layer-list-item')
      layerListItems.forEach(layerListItem => {
        layerListItem.classList.remove('selected')
      })
      li.classList.add('selected')
    })
    layerList.appendChild(li)
  })
}

const keyboard = setupKeyboard({
  addEventListener: document.addEventListener,
  keyPressed: mappoState.keyPressed,
})

middlePanel.addEventListener('mousedown', event => {
  mappoState.mouseDown = true
})

let mapLayerTileHighlightCoord = null
middlePanel.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  const scale = getScale()
  if (mappoState.mouseDown) {
    moveCamera(
      -event.movementX / scale,
      -event.movementY / scale
    )
  }

  mappoState.autoScroll = {}
  if (mappoState.mouseInViewport) {
    mappoState.autoScroll = calcAutoScroll({
      scale,
      threshold: (mappoState.map.tileset.tileWidth + mappoState.map.tileset.tileHeight) / 2,
      cursorX: event.offsetX,
      cursorY: event.offsetY,
      viewportWidth: middlePanel.offsetWidth,
      viewportHeight: middlePanel.offsetHeight,
    })
  }

  const mapLayerSelected = mappoState.mapLayerSelected
  if (mapLayerSelected) {
    const tileWidth = mappoState.map.tileset.tileWidth
    const tileHeight = mappoState.map.tileset.tileHeight
    const scaleX = ~~(event.offsetX / scale)
    const scaleY = ~~(event.offsetY / scale)
    const parallaxX = ~~(mappoState.camera.x * mapLayerSelected.parallax.x)
    const parallaxY = ~~(mappoState.camera.y * mapLayerSelected.parallax.y)
    const x = scaleX - ((parallaxX + scaleX) % tileWidth)
    const y = scaleY - ((parallaxY + scaleY) % tileHeight)

    mapLayerTileHighlightCoord = {x, y}
  }
})

const getTileCoordAndIndex = ({
  tileset,
  containerWidth,
  pixelX,
  pixelY,
}) => {
  const scale = getScale()
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const tileX = ~~(pixelX / (tileset.tileWidth * scale))
  const tileY = ~~(pixelY / (tileset.tileHeight * scale))
  const tileIndex = (tileY * tilesetColumns) + tileX

  return {
    tileX,
    tileY,
    tileIndex,
  }
}

tilesetCanvasContainer.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.map.tilesetTileHovering = getTileCoordAndIndex({
    tileset: mappoState.map.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

tilesetCanvasContainer.addEventListener('click', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.map.tilesetTileSelected = getTileCoordAndIndex({
    tileset: mappoState.map.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

middlePanel.addEventListener('mouseup', event => {
  mappoState.mouseDown = false
})

middlePanel.addEventListener('mouseenter', event => {
  mappoState.mouseInViewport = true
})

middlePanel.addEventListener('mouseout', event => {
  mappoState.mouseInViewport = false
  mappoState.camera.move = {x: 0, y: 0}
  mappoState.autoScroll = {}
  mapLayerTileHighlightCoord = null
})

const moveCamera = (moveX, moveY) => {
  mappoState.camera.x = clamp(mappoState.camera.x + moveX, 0, (mappoState.map.tileLayers[0].width * mappoState.map.tileset.tileWidth) - canvas.width)
  mappoState.camera.y = clamp(mappoState.camera.y + moveY, 0, (mappoState.map.tileLayers[0].height * mappoState.map.tileset.tileHeight) - canvas.height)
}

const getTilesetColumns = ({tileset, containerWidth}) => {
  const scaledTileWidth = tileset.tileWidth * getScale()
  return ~~(containerWidth / scaledTileWidth)
}

const getTilesetRows = ({tileset, containerWidth}) => {
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const roundedUpRows = ~~((tileset.numTiles + (tilesetColumns - 1)) / tilesetColumns)
  return roundedUpRows
}

const tick = () => {
  clearCanvas({canvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetCanvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetSelectedTileCanvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetHoveringTileCanvas, pattern: checkerboardPattern})

  if (!mappoState.isLoading) {
    const tileset = mappoState.map.tileset
    const tileWidth = tileset.tileWidth
    const tileHeight = tileset.tileHeight
    const containerWidth = tilesetCanvasContainer.offsetWidth

    mappoState.map.mapLayerOrder.forEach(layerIndex => {
      const tileLayer = mappoState.map.tileLayers[layerIndex]
      if (!tileLayer.isHidden) {
        renderLayer({
          tileset,
          layer: tileLayer,
          x: mappoState.camera.x * tileLayer.parallax.x,
          y: mappoState.camera.y * tileLayer.parallax.y,
          transparent: layerIndex > 0,
        })
      }
    })

    if (mapLayerTileHighlightCoord) {
      renderTileHighlightInvertedOutline({
        context,
        x: mapLayerTileHighlightCoord.x,
        y: mapLayerTileHighlightCoord.y,
        width: tileWidth,
        height: tileHeight,
      })
    }

    renderTileset({
      context: tilesetContext,
      tileset: mappoState.map.tileset,
      tilesetColumns: getTilesetColumns({tileset, containerWidth})
    })

    if (mappoState.map.tilesetTileHovering) {
      renderTileHighlightInvertedSolid({
        context: tilesetContext,
        x: mappoState.map.tilesetTileHovering.tileX * tileWidth,
        y: mappoState.map.tilesetTileHovering.tileY * tileHeight,
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetHoveringTileContext,
        tileset: mappoState.map.tileset,
        tileIndex: mappoState.map.tilesetTileHovering.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetHoveringTileIndex.innerText = mappoState.map.tilesetTileHovering.tileIndex
    }

    if (mappoState.map.tilesetTileSelected) {
      renderTileHighlightColorOutline({
        context: tilesetContext,
        x: mappoState.map.tilesetTileSelected.tileX * tileWidth,
        y: mappoState.map.tilesetTileSelected.tileY * tileHeight,
        color: 'white',
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetSelectedTileContext,
        tileset: mappoState.map.tileset,
        tileIndex: mappoState.map.tilesetTileSelected.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetSelectedTileIndex.innerText = mappoState.map.tilesetTileSelected.tileIndex
    }

    mappoState.camera.move = {x: 0, y: 0}
    const autoScroll = mappoState.autoScroll
    if (autoScroll) {
      (keyboard.isPressed(keyboard.KEYCODE_UP) || autoScroll.x < 0) && (mappoState.camera.move.y = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_DOWN) || autoScroll.y > 0) && (mappoState.camera.move.y = +mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_LEFT) || autoScroll.x < 0) && (mappoState.camera.move.x = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_RIGHT) || autoScroll.x > 0) && (mappoState.camera.move.x = +mappoState.cameraScrollAmount);
      moveCamera(mappoState.camera.move.x, mappoState.camera.move.y)
    }

    // map zooming
    if (keyboard.isPressed('ctrlKey')) {
      const prevScaleIndex = mappoState.scaleIndex
      if (keyboard.isPressed(keyboard.KEYCODE_PLUS)) {
        keyboard.release(keyboard.KEYCODE_PLUS)
        mappoState.scaleIndex++
      }
      if (keyboard.isPressed(keyboard.KEYCODE_MINUS)) {
        keyboard.release(keyboard.KEYCODE_MINUS)
        mappoState.scaleIndex--
      }
      if (keyboard.isPressed(keyboard.KEYCODE_0)) {
        keyboard.release(keyboard.KEYCODE_0)
        mappoState.scaleIndex = DEFAULT_SCALE_INDEX
      }
      mappoState.scaleIndex = clamp(mappoState.scaleIndex, 0, viewportScales.length - 1)
      // TODO(chuck): redux-ify?
      if (prevScaleIndex !== mappoState.scaleIndex) {
        resizeCanvas()
      }
    }
  }

  window.requestAnimationFrame(tick)
}

const resizeCanvas = () => {
  canvas.width = ~~((middlePanel.offsetWidth + (getScale() - 1)) / getScale())
  canvas.height = ~~((middlePanel.offsetHeight + (getScale() - 1)) / getScale())
  canvas.style.width = middlePanel.offsetWidth + 'px'
  canvas.style.height = middlePanel.offsetHeight + 'px'

  if (!mappoState.isLoading) {
    const containerWidth = tilesetCanvasContainer.offsetWidth
    const tileset = mappoState.map.tileset
    tilesetCanvas.width = getTilesetColumns({tileset, containerWidth}) * tileset.tileWidth
    tilesetCanvas.height = getTilesetRows({tileset, containerWidth}) * tileset.tileHeight
    tilesetCanvas.style.width = (tilesetCanvas.width * getScale()) + 'px'
    tilesetCanvas.style.height = (tilesetCanvas.height * getScale()) + 'px'
  }
}

window.addEventListener('resize', resizeCanvas)
resizeCanvas()

tick()

<commit_msg>pull mapLayerTileHighlightCoord into global state<commit_after>"use strict"

const asset = require('./asset')
const colorDepth = require('./converter/colorDepth')
const clamp = require('lodash/clamp')
const glob = require('glob')
const createMappoSession = require('./createMappoSession')
const createMappoMap = require('./createMappoMap')
const createMappoTileset = require('./createMappoTileset')
const detectFormat = require('./detectFormat')
const path = require('path')
const fs = require('fs')
const setupKeyboard = require('./setupKeyboard')
const renderTileHighlightInvertedOutline = require('./renderTileHighlightInvertedOutline')
const renderTileHighlightInvertedSolid = require('./renderTileHighlightInvertedSolid')
const renderTileHighlightColorOutline = require('./renderTileHighlightColorOutline')
const renderTile = require('./renderTile')
const renderLayer = require('./renderLayer')
const renderTileset = require('./renderTileset')
const createCheckerboardPattern = require('./createCheckerboardPattern')
const calcAutoScroll = require('./calcAutoScroll')
const clearCanvas = require('./clearCanvas')
const loadMappoMap = require('./loadMappoMap')

// DOM REFERENCES
const pageTitle = document.querySelector('title')
const mapList = document.querySelector('.map-list')
const layerList = document.querySelector('.layer-list')
const canvas = document.querySelector('.mappo-viewport')
const context = canvas.getContext('2d')
const tilesetCanvasContainer = document.querySelector('.tileset-canvas-container')
const tilesetCanvas = document.querySelector('.tileset-canvas')
const tilesetContext = tilesetCanvas.getContext('2d')
const tilesetSelectedTileCanvas = document.querySelector('.tileset-selected-tile-canvas')
const tilesetSelectedTileContext = tilesetSelectedTileCanvas.getContext('2d')
const tilesetSelectedTileIndex = document.querySelector('.tileset-selected-tile-index')
const tilesetHoveringTileCanvas = document.querySelector('.tileset-hovering-tile-canvas')
const tilesetHoveringTileContext = tilesetHoveringTileCanvas.getContext('2d')
const tilesetHoveringTileIndex = document.querySelector('.tileset-hovering-tile-index')
const middlePanel = document.querySelector('.middle-panel')

const checkerboardPattern = createCheckerboardPattern({document})

const launchFolder = 'data' // TODO(chuck): temp hack for windows. empty string dunna work
console.log('launchFolder', launchFolder)
const mapGlob = '**/*.map'
const mapFilenames = glob.sync(mapGlob, {nocase: true})
const mappoSession = createMappoSession({
  fileSystem: {
    files: mapFilenames
  },
  launchFolder
})

const viewportScales = [0.25, 0.5, 0.75, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const DEFAULT_SCALE_INDEX = 4
const defaultMappoState = {
  scaleIndex: DEFAULT_SCALE_INDEX,
  isLoading: true,
  map: null,
  mapLayerOrder: null,
  mapLayerSelected: null,
  mapLayerTileHighlightCoord: null,
  tileset: null,
  tilesetTileHovering: null,
  tilesetTileSelected: null,
  camera: {
    x: 0,
    y: 0,
    move: {
      x: 0,
      y: 0,
    },
  },
  cameraScrollAmount: 1,
  keyPressed: {},
  mouseDown: false,
  mouseInViewport: false,
  autoScroll: {},
}
let mappoState = Object.assign({}, defaultMappoState)

const getScale = () => viewportScales[mappoState.scaleIndex]

mappoSession.getMapFilenames().forEach(mapFilename => {
  const li = document.createElement('li')
  li.setAttribute('title', mapFilename)
  li.innerText = mapFilename
  // TODO(chuck): temp hack for windows. figure out better launchFolder shenanigans
  li.addEventListener('click', event => {
    mappoState = Object.assign({}, defaultMappoState)
    mappoState.isLoading = true

    mappoState.map = loadMappoMap({context, mapFilename: 'data/' + mapFilename})

    mappoState.mapLayerSelected = mappoState.map.tileLayers[mappoState.map.mapLayerOrder[0]]
    refreshMapLayerList()

    tilesetSelectedTileCanvas.width = mappoState.map.tileset.tileWidth
    tilesetSelectedTileCanvas.height = mappoState.map.tileset.tileHeight
    tilesetHoveringTileCanvas.width = mappoState.map.tileset.tileWidth
    tilesetHoveringTileCanvas.height = mappoState.map.tileset.tileHeight

    // TODO(chuck): shorten this crazy chain
    mappoState.map.tileset.imageBitmapPromise.then(() => {
      mappoState.isLoading = false
      resizeCanvas()
    })

    pageTitle.innerText = 'Mappo - ' + mapFilename
  })
  mapList.appendChild(li)
})

const refreshMapLayerList = () => {
  layerList.innerHTML = ''
  mappoState.map.tileLayers.forEach((layer, index) => {
    const li = document.createElement('li')
    li.setAttribute('title', layer.description)
    li.innerText = layer.description
    li.classList.add('layer-list-item')
    if (mappoState.mapLayerSelected === layer) {
      li.classList.add('selected')
    }
    li.addEventListener('click', event => {
      // TODO(chuck): use a proper element?
      if (event.offsetX < 35) {
        li.classList.toggle('is-layer-hidden')
        layer.isHidden = !layer.isHidden
      }
      mappoState.mapLayerSelected = layer
      const layerListItems = document.querySelectorAll('.layer-list .layer-list-item')
      layerListItems.forEach(layerListItem => {
        layerListItem.classList.remove('selected')
      })
      li.classList.add('selected')
    })
    layerList.appendChild(li)
  })
}

const keyboard = setupKeyboard({
  addEventListener: document.addEventListener,
  keyPressed: mappoState.keyPressed,
})

middlePanel.addEventListener('mousedown', event => {
  mappoState.mouseDown = true
})

middlePanel.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  const scale = getScale()
  if (mappoState.mouseDown) {
    moveCamera(
      -event.movementX / scale,
      -event.movementY / scale
    )
  }

  mappoState.autoScroll = {}
  if (mappoState.mouseInViewport) {
    mappoState.autoScroll = calcAutoScroll({
      scale,
      threshold: (mappoState.map.tileset.tileWidth + mappoState.map.tileset.tileHeight) / 2,
      cursorX: event.offsetX,
      cursorY: event.offsetY,
      viewportWidth: middlePanel.offsetWidth,
      viewportHeight: middlePanel.offsetHeight,
    })
  }

  const mapLayerSelected = mappoState.mapLayerSelected
  if (mapLayerSelected) {
    const tileWidth = mappoState.map.tileset.tileWidth
    const tileHeight = mappoState.map.tileset.tileHeight
    const scaleX = ~~(event.offsetX / scale)
    const scaleY = ~~(event.offsetY / scale)
    const parallaxX = ~~(mappoState.camera.x * mapLayerSelected.parallax.x)
    const parallaxY = ~~(mappoState.camera.y * mapLayerSelected.parallax.y)
    const x = scaleX - ((parallaxX + scaleX) % tileWidth)
    const y = scaleY - ((parallaxY + scaleY) % tileHeight)

    mappoState.mapLayerTileHighlightCoord = {x, y}
  }
})

const getTileCoordAndIndex = ({
  tileset,
  containerWidth,
  pixelX,
  pixelY,
}) => {
  const scale = getScale()
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const tileX = ~~(pixelX / (tileset.tileWidth * scale))
  const tileY = ~~(pixelY / (tileset.tileHeight * scale))
  const tileIndex = (tileY * tilesetColumns) + tileX

  return {
    tileX,
    tileY,
    tileIndex,
  }
}

tilesetCanvasContainer.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.map.tilesetTileHovering = getTileCoordAndIndex({
    tileset: mappoState.map.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

tilesetCanvasContainer.addEventListener('click', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.map.tilesetTileSelected = getTileCoordAndIndex({
    tileset: mappoState.map.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

middlePanel.addEventListener('mouseup', event => {
  mappoState.mouseDown = false
})

middlePanel.addEventListener('mouseenter', event => {
  mappoState.mouseInViewport = true
})

middlePanel.addEventListener('mouseout', event => {
  mappoState.mouseInViewport = false
  mappoState.camera.move = {x: 0, y: 0}
  mappoState.autoScroll = {}
  mappoState.mapLayerTileHighlightCoord = null
})

const moveCamera = (moveX, moveY) => {
  mappoState.camera.x = clamp(mappoState.camera.x + moveX, 0, (mappoState.map.tileLayers[0].width * mappoState.map.tileset.tileWidth) - canvas.width)
  mappoState.camera.y = clamp(mappoState.camera.y + moveY, 0, (mappoState.map.tileLayers[0].height * mappoState.map.tileset.tileHeight) - canvas.height)
}

const getTilesetColumns = ({tileset, containerWidth}) => {
  const scaledTileWidth = tileset.tileWidth * getScale()
  return ~~(containerWidth / scaledTileWidth)
}

const getTilesetRows = ({tileset, containerWidth}) => {
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const roundedUpRows = ~~((tileset.numTiles + (tilesetColumns - 1)) / tilesetColumns)
  return roundedUpRows
}

const tick = () => {
  clearCanvas({canvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetCanvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetSelectedTileCanvas, pattern: checkerboardPattern})
  clearCanvas({canvas: tilesetHoveringTileCanvas, pattern: checkerboardPattern})

  if (!mappoState.isLoading) {
    const tileset = mappoState.map.tileset
    const tileWidth = tileset.tileWidth
    const tileHeight = tileset.tileHeight
    const containerWidth = tilesetCanvasContainer.offsetWidth

    mappoState.map.mapLayerOrder.forEach(layerIndex => {
      const tileLayer = mappoState.map.tileLayers[layerIndex]
      if (!tileLayer.isHidden) {
        renderLayer({
          tileset,
          layer: tileLayer,
          x: mappoState.camera.x * tileLayer.parallax.x,
          y: mappoState.camera.y * tileLayer.parallax.y,
          transparent: layerIndex > 0,
        })
      }
    })

    if (mappoState.mapLayerTileHighlightCoord) {
      renderTileHighlightInvertedOutline({
        context,
        x: mappoState.mapLayerTileHighlightCoord.x,
        y: mappoState.mapLayerTileHighlightCoord.y,
        width: tileWidth,
        height: tileHeight,
      })
    }

    renderTileset({
      context: tilesetContext,
      tileset: mappoState.map.tileset,
      tilesetColumns: getTilesetColumns({tileset, containerWidth})
    })

    if (mappoState.map.tilesetTileHovering) {
      renderTileHighlightInvertedSolid({
        context: tilesetContext,
        x: mappoState.map.tilesetTileHovering.tileX * tileWidth,
        y: mappoState.map.tilesetTileHovering.tileY * tileHeight,
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetHoveringTileContext,
        tileset: mappoState.map.tileset,
        tileIndex: mappoState.map.tilesetTileHovering.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetHoveringTileIndex.innerText = mappoState.map.tilesetTileHovering.tileIndex
    }

    if (mappoState.map.tilesetTileSelected) {
      renderTileHighlightColorOutline({
        context: tilesetContext,
        x: mappoState.map.tilesetTileSelected.tileX * tileWidth,
        y: mappoState.map.tilesetTileSelected.tileY * tileHeight,
        color: 'white',
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetSelectedTileContext,
        tileset: mappoState.map.tileset,
        tileIndex: mappoState.map.tilesetTileSelected.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetSelectedTileIndex.innerText = mappoState.map.tilesetTileSelected.tileIndex
    }

    mappoState.camera.move = {x: 0, y: 0}
    const autoScroll = mappoState.autoScroll
    if (autoScroll) {
      (keyboard.isPressed(keyboard.KEYCODE_UP) || autoScroll.x < 0) && (mappoState.camera.move.y = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_DOWN) || autoScroll.y > 0) && (mappoState.camera.move.y = +mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_LEFT) || autoScroll.x < 0) && (mappoState.camera.move.x = -mappoState.cameraScrollAmount);
      (keyboard.isPressed(keyboard.KEYCODE_RIGHT) || autoScroll.x > 0) && (mappoState.camera.move.x = +mappoState.cameraScrollAmount);
      moveCamera(mappoState.camera.move.x, mappoState.camera.move.y)
    }

    // map zooming
    if (keyboard.isPressed('ctrlKey')) {
      const prevScaleIndex = mappoState.scaleIndex
      if (keyboard.isPressed(keyboard.KEYCODE_PLUS)) {
        keyboard.release(keyboard.KEYCODE_PLUS)
        mappoState.scaleIndex++
      }
      if (keyboard.isPressed(keyboard.KEYCODE_MINUS)) {
        keyboard.release(keyboard.KEYCODE_MINUS)
        mappoState.scaleIndex--
      }
      if (keyboard.isPressed(keyboard.KEYCODE_0)) {
        keyboard.release(keyboard.KEYCODE_0)
        mappoState.scaleIndex = DEFAULT_SCALE_INDEX
      }
      mappoState.scaleIndex = clamp(mappoState.scaleIndex, 0, viewportScales.length - 1)
      // TODO(chuck): redux-ify?
      if (prevScaleIndex !== mappoState.scaleIndex) {
        resizeCanvas()
      }
    }
  }

  window.requestAnimationFrame(tick)
}

const resizeCanvas = () => {
  canvas.width = ~~((middlePanel.offsetWidth + (getScale() - 1)) / getScale())
  canvas.height = ~~((middlePanel.offsetHeight + (getScale() - 1)) / getScale())
  canvas.style.width = middlePanel.offsetWidth + 'px'
  canvas.style.height = middlePanel.offsetHeight + 'px'

  if (!mappoState.isLoading) {
    const containerWidth = tilesetCanvasContainer.offsetWidth
    const tileset = mappoState.map.tileset
    tilesetCanvas.width = getTilesetColumns({tileset, containerWidth}) * tileset.tileWidth
    tilesetCanvas.height = getTilesetRows({tileset, containerWidth}) * tileset.tileHeight
    tilesetCanvas.style.width = (tilesetCanvas.width * getScale()) + 'px'
    tilesetCanvas.style.height = (tilesetCanvas.height * getScale()) + 'px'
  }
}

window.addEventListener('resize', resizeCanvas)
resizeCanvas()

tick()

<|endoftext|>


<commit_before>/*
Created By MSCSTSTS
2017-09-27 18:13:50 UTC+8

Edit By MSCSTSTS
2017-12-02 19:36:17 UTC+8
+触屏滑动切换上一章/下一章。
+键盘左右方向键切换上一章/下一章。

*/

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

/*
登录、书签、加入书库等功能，都是基于刷新或者ajax
所以仅需修改部分页面
最后，将页面滑动到顶部即可。
处理逻辑：
	1：页面加载，检查浏览器是否支持 ajax 和 sessionStorage
	2：查询下一章 和 上一章是否已被缓存，若没有，则进行 ajax 请求，并将对应页面存入sessionStorage
	3：点击上一章或下一章按钮以后，将缓存中的数据取出，重新渲染页面，回到顶部，修改浏览器url，并重新执行2
需要缓存的条目：
	1：本页链接      data.url
	2：下一章链接  data.next_chapter."下一章"
	3：上一章链接  data.next_chapter."上一章"
	4：章节名         data.name
	5：小说名         data.novels_name
	6：正文内容      data.soup
	7：是否书签      data.bookmark
需要更新的页面内容：
	0：【索引项】 页面链接（相对地址）    由 缓存数据的本页链接 拼接产生
	1：书签状态          #bookMark   的 class ，根据 缓存数据 的 是否书签来定义 class
	2：隐藏表单          page_url  的 value       , 根据 缓存数据 的 本页链接
	3：页面标题          page_title          ,  根据 缓存数据 的 章节名产生
	4：正文文本          page_chapter_content      ,根据 缓存数据 的 正文内容产生
	5：上一章和下一章的地址     ，根据缓存数据的上一章链接和下一章链接产生
	6：地址栏URL ，使用replaceState来更新，根据缓存数据的 本页链接 来拼接
*/

$(document).ready(function () {

    var page_btn_pre = $("div.pre_next > a:nth-child(1)");
    var page_btn_next = $("div.pre_next > a:nth-child(2)");
    var page_title = $("title");
    var page_chapter_name = $("#content_name");
    // var page_chapter_content = $(".show-content>div");//正文
    var page_chapter_content = $($(".show-content").children("*").get(0)); //正文
    var page_bookmark = $("#bookMark");//书签，需要修改样式
    var page_url = $("#url");//页面隐藏表单，本页地址

    function get_chapter(n_url) {
        $.ajax({
            //提交数据的类型 POST GET
            type: "GET",
            //提交的网址
            url: n_url,
            //提交的数据
            data: {is_ajax: "owl_cache"},
            //返回数据的格式
            datatype: "json",//"xml", "html", "script", "json", "jsonp", "text".
            //在请求之前调用的函数
            //beforeSend:function(){$("#msg").html("logining");},
            //成功返回之后调用的函数
            success: function (data) {
                if (typeof data.name == "undefined") {

                } else { // 正确获取到数据
                    var obj = {
                        url: data.url,
                        pre_chapter_url: transform(data.next_chapter)[0],
                        next_chapter_url: transform(data.next_chapter)[1],
                        name: data.name,
                        novels_name: data.novels_name,
                        content: data.soup,
                        bookmark: data.bookmark,
                        chapter_url: data.chapter_url
                    };
                    store(n_url, obj);
                }
            },
            //调用执行后调用的函数
            complete: function () {

            },
            //调用出错执行的函数
            error: function () {
                //请求出错处理
            }
        });
    }

    function transform(obj) {
        var arr = [];
        for (var item in obj) {
            arr.push(obj[item]);
        }
        return arr;
    }

    function store(n_url, obj) {
        window.sessionStorage.setItem(n_url, JSON.stringify(obj));
    }

    function ajax_content_init() {
        if (isSupport()) {
            log("支持sessionStorage，将为你开启页面缓存");
            ajax_task();
            page_bookmark.bind("click", function () {
                cache_reset();
            });
        } else {
            //不支持
            return;
        }
    }

    function ajax_task() {
        store_query();//检查是否已缓存
        page_btn_pre.unbind("click");
        page_btn_pre.click(function () {
            event.preventDefault();
            if (window.sessionStorage.getItem(page_btn_pre.attr("href")) === null) {
                //若未缓存
                window.location.href = page_btn_pre.attr("href");
            } else {
                load(page_btn_pre.attr("href"));
            }
        });
        page_btn_next.unbind("click");
        page_btn_next.click(function () {
            event.preventDefault();
            if (window.sessionStorage.getItem(page_btn_next.attr("href")) === null) {
                //若未缓存
                window.location.href = page_btn_next.attr("href");
            } else {
                load(page_btn_next.attr("href"));
            }
        });
    }

    function load_bookmark(data) {
        page_bookmark.removeClass("bookMark");
        page_bookmark.removeClass("bookMarkAct");
        //log(data.bookmark);
        if (data.bookmark == 0) {
            page_bookmark.addClass("bookMark");
        } else {
            page_bookmark.addClass("bookMarkAct");
        }
    }

    function load_hiddenForm(data) {
        page_url.val(data.url);
    }

    function load_title(data) {
        page_title.html(data.name + " - owllook");
    }

    function stripscript(s) {//用于过滤script标签
        return s.replace(/<script>.*?<\/script>/ig, '').replace(/<.*?div.*?>/, '');
    }

    function load_chapter_content(data) {
        page_chapter_content.html(stripscript(data.content));
    }

    function load_chapter_name(data) {
        page_chapter_name.text(data.name);
    }

    function load_btn_href(data) {
        page_btn_pre.attr("href", "/owllook_content?url=" + data.pre_chapter_url + "&chapter_url=" + data.chapter_url + "&novels_name=" + data.novels_name);
        page_btn_next.attr("href", "/owllook_content?url=" + data.next_chapter_url + "&chapter_url=" + data.chapter_url + "&novels_name=" + data.novels_name);
    }

    function load_location_url(data) {
        var th_url = window.location.href + "";
        var pos = th_url.indexOf("/owllook_content?");
        var td_url = "/owllook_content?url=" + data.url + "&name=" + data.name + "&chapter_url=" + data.chapter_url + "&novels_name=" + data.novels_name;
        th_url += td_url;
        //log(th_url);
        window.history.replaceState({}, data.name + " - owllook", td_url);
    }

    function load(index) {//从缓存中加载内容
        var data = JSON.parse(window.sessionStorage.getItem(index));
        load_bookmark(data);
        load_hiddenForm(data);
        load_title(data);
        load_chapter_content(data);
        load_chapter_name(data);
        load_btn_href(data);
        load_location_url(data);
        $("body > div.container.all-content > div.move > div.move_up").click();//回到顶部
        ajax_task();
    }

    function store_query() {
        var pre_url = page_btn_pre.attr("href");
        var next_url = page_btn_next.attr("href");
        //log(pre_url);
        //log(window.sessionStorage.getItem(pre_url));
        if (window.sessionStorage.getItem(pre_url) === null) {
            get_chapter(pre_url);
        }
        if (window.sessionStorage.getItem(next_url) === null) {
            get_chapter(next_url);
        }
    }

    function cache_reset() {
        window.sessionStorage.clear();
        ajax_task();
    }

    function isSupport() {
        if (typeof window.sessionStorage == "undefined") { //判断是否支持sessionStorage
            return false;
        }
        return true;
    }

    function log(s) {
        if (1) {
            console.log(s);
        }

    }

    ajax_content_init();

	/*
		2017年12月2日 +触屏滑动切换上一章/下一章。
	*/
	//按键事件
	$(document).keydown(function(event){
		var e = event || window.event;
		var k = e.keyCode || e.which;
		switch(k) {
			case 39:
			// right
				page_btn_next.click();
				break;
			case 37:
			// left
				page_btn_pre.click();
				break;
			case 38:
			//	up
				break;
			case 40:
			// down
				break;
		}
		return true;
	});

	var b = document.getElementsByTagName("body")[0];
	b.addEventListener("touchmove", function(e){
		e.preventDefault();
	});
	(function(){
		var startX,startY,endX,endY,moveLength;
		var el = document.querySelector("body");
		//获取点击开始的坐标
		el.addEventListener("touchstart", function (e){
			startX = e.touches[0].pageX;
			startY = e.touches[0].pageY;
		});
		//获取点击结束后的坐标
		el.addEventListener("touchend", function(e){
			endX = e.changedTouches[0].pageX;
			endY = e.changedTouches[0].pageY;
			var x = (endX - startX);
			var y = (endY - startY);
			if(Math.abs(x/y)>5&&Math.abs(x)>30){
				if(x>0){
					page_btn_next.click();
				}else{
					page_btn_pre.click();
				}
			}
			//长方形的斜边长 = 两个直线的平方的和的平方根
			moveLength = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));

		});
	})();
});

//---------------------------------------------------------------------------------------<commit_msg>修改手机触摸翻页顺序<commit_after>/*
Created By MSCSTSTS
2017-09-27 18:13:50 UTC+8

Edit By MSCSTSTS
2017-12-02 19:36:17 UTC+8
+触屏滑动切换上一章/下一章。
+键盘左右方向键切换上一章/下一章。

*/

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

/*
登录、书签、加入书库等功能，都是基于刷新或者ajax
所以仅需修改部分页面
最后，将页面滑动到顶部即可。
处理逻辑：
	1：页面加载，检查浏览器是否支持 ajax 和 sessionStorage
	2：查询下一章 和 上一章是否已被缓存，若没有，则进行 ajax 请求，并将对应页面存入sessionStorage
	3：点击上一章或下一章按钮以后，将缓存中的数据取出，重新渲染页面，回到顶部，修改浏览器url，并重新执行2
需要缓存的条目：
	1：本页链接      data.url
	2：下一章链接  data.next_chapter."下一章"
	3：上一章链接  data.next_chapter."上一章"
	4：章节名         data.name
	5：小说名         data.novels_name
	6：正文内容      data.soup
	7：是否书签      data.bookmark
需要更新的页面内容：
	0：【索引项】 页面链接（相对地址）    由 缓存数据的本页链接 拼接产生
	1：书签状态          #bookMark   的 class ，根据 缓存数据 的 是否书签来定义 class
	2：隐藏表单          page_url  的 value       , 根据 缓存数据 的 本页链接
	3：页面标题          page_title          ,  根据 缓存数据 的 章节名产生
	4：正文文本          page_chapter_content      ,根据 缓存数据 的 正文内容产生
	5：上一章和下一章的地址     ，根据缓存数据的上一章链接和下一章链接产生
	6：地址栏URL ，使用replaceState来更新，根据缓存数据的 本页链接 来拼接
*/

$(document).ready(function () {

    var page_btn_pre = $("div.pre_next > a:nth-child(1)");
    var page_btn_next = $("div.pre_next > a:nth-child(2)");
    var page_title = $("title");
    var page_chapter_name = $("#content_name");
    // var page_chapter_content = $(".show-content>div");//正文
    var page_chapter_content = $($(".show-content").children("*").get(0)); //正文
    var page_bookmark = $("#bookMark");//书签，需要修改样式
    var page_url = $("#url");//页面隐藏表单，本页地址

    function get_chapter(n_url) {
        $.ajax({
            //提交数据的类型 POST GET
            type: "GET",
            //提交的网址
            url: n_url,
            //提交的数据
            data: {is_ajax: "owl_cache"},
            //返回数据的格式
            datatype: "json",//"xml", "html", "script", "json", "jsonp", "text".
            //在请求之前调用的函数
            //beforeSend:function(){$("#msg").html("logining");},
            //成功返回之后调用的函数
            success: function (data) {
                if (typeof data.name == "undefined") {

                } else { // 正确获取到数据
                    var obj = {
                        url: data.url,
                        pre_chapter_url: transform(data.next_chapter)[0],
                        next_chapter_url: transform(data.next_chapter)[1],
                        name: data.name,
                        novels_name: data.novels_name,
                        content: data.soup,
                        bookmark: data.bookmark,
                        chapter_url: data.chapter_url
                    };
                    store(n_url, obj);
                }
            },
            //调用执行后调用的函数
            complete: function () {

            },
            //调用出错执行的函数
            error: function () {
                //请求出错处理
            }
        });
    }

    function transform(obj) {
        var arr = [];
        for (var item in obj) {
            arr.push(obj[item]);
        }
        return arr;
    }

    function store(n_url, obj) {
        window.sessionStorage.setItem(n_url, JSON.stringify(obj));
    }

    function ajax_content_init() {
        if (isSupport()) {
            log("支持sessionStorage，将为你开启页面缓存");
            ajax_task();
            page_bookmark.bind("click", function () {
                cache_reset();
            });
        } else {
            //不支持
            return;
        }
    }

    function ajax_task() {
        store_query();//检查是否已缓存
        page_btn_pre.unbind("click");
        page_btn_pre.click(function () {
            event.preventDefault();
            if (window.sessionStorage.getItem(page_btn_pre.attr("href")) === null) {
                //若未缓存
                window.location.href = page_btn_pre.attr("href");
            } else {
                load(page_btn_pre.attr("href"));
            }
        });
        page_btn_next.unbind("click");
        page_btn_next.click(function () {
            event.preventDefault();
            if (window.sessionStorage.getItem(page_btn_next.attr("href")) === null) {
                //若未缓存
                window.location.href = page_btn_next.attr("href");
            } else {
                load(page_btn_next.attr("href"));
            }
        });
    }

    function load_bookmark(data) {
        page_bookmark.removeClass("bookMark");
        page_bookmark.removeClass("bookMarkAct");
        //log(data.bookmark);
        if (data.bookmark == 0) {
            page_bookmark.addClass("bookMark");
        } else {
            page_bookmark.addClass("bookMarkAct");
        }
    }

    function load_hiddenForm(data) {
        page_url.val(data.url);
    }

    function load_title(data) {
        page_title.html(data.name + " - owllook");
    }

    function stripscript(s) {//用于过滤script标签
        return s.replace(/<script>.*?<\/script>/ig, '').replace(/<.*?div.*?>/, '');
    }

    function load_chapter_content(data) {
        page_chapter_content.html(stripscript(data.content));
    }

    function load_chapter_name(data) {
        page_chapter_name.text(data.name);
    }

    function load_btn_href(data) {
        page_btn_pre.attr("href", "/owllook_content?url=" + data.pre_chapter_url + "&chapter_url=" + data.chapter_url + "&novels_name=" + data.novels_name);
        page_btn_next.attr("href", "/owllook_content?url=" + data.next_chapter_url + "&chapter_url=" + data.chapter_url + "&novels_name=" + data.novels_name);
    }

    function load_location_url(data) {
        var th_url = window.location.href + "";
        var pos = th_url.indexOf("/owllook_content?");
        var td_url = "/owllook_content?url=" + data.url + "&name=" + data.name + "&chapter_url=" + data.chapter_url + "&novels_name=" + data.novels_name;
        th_url += td_url;
        //log(th_url);
        window.history.replaceState({}, data.name + " - owllook", td_url);
    }

    function load(index) {//从缓存中加载内容
        var data = JSON.parse(window.sessionStorage.getItem(index));
        load_bookmark(data);
        load_hiddenForm(data);
        load_title(data);
        load_chapter_content(data);
        load_chapter_name(data);
        load_btn_href(data);
        load_location_url(data);
        $("body > div.container.all-content > div.move > div.move_up").click();//回到顶部
        ajax_task();
    }

    function store_query() {
        var pre_url = page_btn_pre.attr("href");
        var next_url = page_btn_next.attr("href");
        //log(pre_url);
        //log(window.sessionStorage.getItem(pre_url));
        if (window.sessionStorage.getItem(pre_url) === null) {
            get_chapter(pre_url);
        }
        if (window.sessionStorage.getItem(next_url) === null) {
            get_chapter(next_url);
        }
    }

    function cache_reset() {
        window.sessionStorage.clear();
        ajax_task();
    }

    function isSupport() {
        if (typeof window.sessionStorage == "undefined") { //判断是否支持sessionStorage
            return false;
        }
        return true;
    }

    function log(s) {
        if (1) {
            console.log(s);
        }

    }

    ajax_content_init();

	/*
		2017年12月2日 +触屏滑动切换上一章/下一章。
	*/
	//按键事件
	$(document).keydown(function(event){
		var e = event || window.event;
		var k = e.keyCode || e.which;
		switch(k) {
			case 39:
			// right
				page_btn_next.click();
				break;
			case 37:
			// left
				page_btn_pre.click();
				break;
			case 38:
			//	up
				break;
			case 40:
			// down
				break;
		}
		return true;
	});

	var b = document.getElementsByTagName("body")[0];
	b.addEventListener("touchmove", function(e){
		e.preventDefault();
	});
	(function(){
		var startX,startY,endX,endY;
		var el = document.querySelector("body");
		//获取点击开始的坐标
		el.addEventListener("touchstart", function (e){
			startX = e.touches[0].pageX;
			startY = e.touches[0].pageY;
		});
		//获取点击结束后的坐标
		el.addEventListener("touchend", function(e){
			endX = e.changedTouches[0].pageX;
			endY = e.changedTouches[0].pageY;
			var x = (endX - startX);
			var y = (endY - startY);
			if(Math.abs(x/y)>5&&Math.abs(x)>30){
				if(x<0){
					page_btn_next.click();
				}else{
					page_btn_pre.click();
				}
			}

		});
	})();
});

//---------------------------------------------------------------------------------------<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import { Router } from 'react-router';
import routes from './routes';

ReactDOM.render(<Router routes={routes} />, document.getElementById('app'))<commit_msg>using the browser history for non # urls<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import { Router } from 'react-router';
import routes from './routes';
import createBrowserHistory from 'history/lib/createBrowserHistory'

const history = createBrowserHistory()

ReactDOM.render(
  <Router routes={routes} history={history} />,
  document.getElementById('load')
)<|endoftext|>


<commit_before>'use strict';
angular.module('arethusa.core').service('keyCapture', [
  'configurator',
  '$rootScope',
  function(configurator, $rootScope) {
    var self = this;

    this.grKeyTable = function(key) {
      return configurator.configurationFor('keyCapture').grKeys[key];
     };

    this.conf = function(name) {
      var c = configurator.configurationFor('keyCapture') || {};
      return c[name] || {};
    };

    var keyCodes = {
      return: 13,
      shift: 16,
      ctrl: 17,
      alt: 18,
      esc: 27,
      ":" : 186,
      "[" : 219,
      "'" : 222,
      "]" : 221
    };

    // a-z codes
    for (var i = 97; i < 123; i++){
      keyCodes[String.fromCharCode(i)] = i - 32;
    }

    this.shiftModifier = 1000;
    this.ctrlModifier  = 2000;
    this.altModifier   = 4000;
    this.metaModifier  = 8000;

    this.getKeyCode = function(key) {
      var parsed = parseKey(key);
      var modifiers = parsed[0];
      var k = parsed[1];
      angular.forEach(modifiers, function(modifier, i) {
        var mod;
        switch (modifier) {
          case 'ctrl':  mod = self.ctrlModifier;  break;
          case 'shift': mod = self.shiftModifier; break;
          case 'alt':   mod = self.altModifier;   break;
          case 'meta':  mod = self.altModifier;   break;
        }
        k = k + mod;
      });

      return k;
    };

    function parseKey(key) {
      var parts = key.split('-');
      var k = parts.pop();
      if (k.match(/[A-Z]/)) {
        // in case someone wants to do shift-J
        if (! arethusaUtil.isIncluded(parts, 'shift')) {
          parts.push('shift');
        }
        k = k.toLowerCase();
      }

      return [parts, keyCodes[k]];
    }

    var modifiers = function() {
      return arethusaUtil.inject([], self.grKeyTable("modifiers"), function(memo, i, key) {
        memo.push(key);
      });
    };

    this.lookUpKey = [];

    this.getGreekKey = function(event) {
      var stop;
      var res = [];
      angular.forEach(keyCodes, function(code, key) {
        if (code == event.keyCode) {
          if (event.shiftKey) {
            res.push('shift');
          }
          if (arethusaUtil.isIncluded(modifiers(), key)) {
            res.push(key);
            var joined = res.join('-');
            self.lookUpKey.push(joined);
            stop = true;
            return;
          } else {
            if (arethusaUtil.isIncluded(res, 'shift')) {
              // Following lines provide that 'shift-a'
              // and 'A' is the same.
              var i = res.indexOf("shift");
              res.splice(i, 1);
              key = key.toUpperCase();
            }
            res.push(key);
          }
        }
      });
      if (stop) {
        return false;
      } else {
        var lookUp = self.lookUpKey.concat(res);
        var key = self.grKeyTable(lookUp.join('-'));
        self.lookUpKey = [];
        return key;
      }
    };

    var keyPressedCallbacks = {};

    function modifiedKeyCode(event) {
      var k = event.keyCode;

      if (event.shiftKey) k = k + self.shiftModifier;
      if (event.ctrlKey)  k = k + self.ctrlModifier;
      if (event.altKey)   k = k + self.altModifier;
      if (event.metaKey)  k = k + self.metaModifier;

      return k;
    }

    var handleCallbacks = function(event) {
      var keyCode = modifiedKeyCode(event);
      var callbacks = keyPressedCallbacks[keyCode];
      if (callbacks) {
        resolveCallbacks(callbacks, event);
        resumePropagation();
      }
    };

    this.keydown = function (event) {
      // we're only acting on keyup for now
    };

    var forbiddenTags = ['INPUT'];
    var repeater = '';

    this.keyup = function (event) {
      if (arethusaUtil.isIncluded(forbiddenTags, event.target.tagName)) {
        return;
      }
      if (isRepeater(event.keyCode)) {
        var rep = event.keyCode - 48;
        repeater = repeater + rep;
        return;
      }
      var keyCode = modifiedKeyCode(event);
      handleCallbacks(event);
      resetRepeater();
    };

    this.doRepeated = function(fn) {
      var rep = parseInt(repeater, 10) || 1; // default value (and beware octals!)
      for (; rep > 0; rep--) {
        fn();
      }
    };

    function isRepeater(code) {
      if (! isNaN(code)) {
        var numeric = parseInt(code) - 48;
        return numeric > -1 && numeric < 10;
      }
    }

    function resetRepeater() {
      repeater = '';
    }

    function Callback(callback, priority) {
      this.callback = callback;
      this.priority = priority || 0;
    }

    this.onKeyPressed = function(key, callback, priority) {
      var keyCode = self.getKeyCode(key);
      var callbacks = keyPressedCallbacks[keyCode] || [];
      callbacks.push(new Callback(callback, priority));
      keyPressedCallbacks[keyCode] = sortedByPriority(callbacks);
    };

    function sortedByPriority(callbacks) {
      return callbacks.sort(function(a, b) {
        return b.priority - a.priority;
      });
    }

    var propagationStopped = false;

    this.stopPropagation = function() {
      propagationStopped = true;
    };

    function resumePropagation() {
      propagationStopped = false;
    }

    function resolveCallbacks(callbacks, event) {
      angular.forEach(callbacks, function(callbackObj, key) {
        if (! propagationStopped) {
          callbackObj.callback(event);
        }
      });
    }

    // deprecated - use initCaptures instead
    this.registerCaptures = function(captures, scope) {
      scope = scope ? scope : $rootScope;
      angular.forEach(captures, function(fn, key) {
        if (angular.isDefined(key)) {
          self.onKeyPressed(key, function() {
            scope.$apply(fn);
          });
        }
      });
    };

    function Capture(confKey, fn, defaultKey) {
      this.confKey = confKey;
      this.fn = fn;
      this.defaultKey = defaultKey;
    }

    this.create = function(confKey, fn, defaultKey) {
      return new Capture(confKey, fn, defaultKey);
    };

    this.initCaptures = function(callback) {
      angular.forEach(callback(self), function(captures, section) {
        var conf = self.conf(section);
        angular.forEach(captures, function(capture, i) {
          var key = conf[capture.confKey] || capture.defaultKey;
          if (angular.isDefined(key)) {
            self.onKeyPressed(key, function() {
              $rootScope.$apply(capture.fn);
            });
          }
        });
      });
    };
  }
]);
<commit_msg>Refactor keyCapture.getGreekKey to getForeignKey<commit_after>'use strict';
angular.module('arethusa.core').service('keyCapture', [
  'configurator',
  '$rootScope',
  function(configurator, $rootScope) {
    var self = this;

    this.conf = function(name) {
      var c = configurator.configurationFor('keyCapture') || {};
      return c[name] || {};
    };

    var keyCodes = {
      return: 13,
      shift: 16,
      ctrl: 17,
      alt: 18,
      esc: 27,
      ":" : 186,
      "[" : 219,
      "'" : 222,
      "]" : 221
    };

    // a-z codes
    for (var i = 97; i < 123; i++){
      keyCodes[String.fromCharCode(i)] = i - 32;
    }

    var CodesToKeys = arethusaUtil.inject({}, keyCodes, function(memo, key, code) {
      memo[code] = key;
    });

    this.shiftModifier = 1000;
    this.ctrlModifier  = 2000;
    this.altModifier   = 4000;
    this.metaModifier  = 8000;

    this.getKeyCode = function(key) {
      var parsed = parseKey(key);
      var modifiers = parsed[0];
      var k = parsed[1];
      angular.forEach(modifiers, function(modifier, i) {
        var mod;
        switch (modifier) {
          case 'ctrl':  mod = self.ctrlModifier;  break;
          case 'shift': mod = self.shiftModifier; break;
          case 'alt':   mod = self.altModifier;   break;
          case 'meta':  mod = self.altModifier;   break;
        }
        k = k + mod;
      });

      return k;
    };

    function parseKey(key) {
      var parts = key.split('-');
      var k = parts.pop();
      if (k.match(/[A-Z]/)) {
        // in case someone wants to do shift-J
        if (! arethusaUtil.isIncluded(parts, 'shift')) {
          parts.push('shift');
        }
        k = k.toLowerCase();
      }

      return [parts, keyCodes[k]];
    }

    function modifiers(keys) {
      return arethusaUtil.inject([], keys.modifiers, function(memo, i, key) {
        memo.push(key);
      });
    }

    var lookUpKey = [];
    this.getForeignKey = function(event, language) {
      var res = [];
      var keys = keysFor(language);
      var key = CodesToKeys[event.keyCode];
      if (key) {
        if (event.shiftKey) {
          res.push('shift');
        }
        if (arethusaUtil.isIncluded(modifiers(keys), key)) {
          res.push(key);
          var joined = res.join('-');
          lookUpKey.push(joined);
          return false;
        } else {
          if (arethusaUtil.isIncluded(res, 'shift')) {
            // Following lines provide that 'shift-a'
            // and 'A' is the same.
            var i = res.indexOf("shift");
            res.splice(i, 1);
            key = key.toUpperCase();
          }
          res.push(key);
        }
      }

      var lookUp = lookUpKey.concat(res);
      var foreignKey = keys[lookUp.join('-')];
      lookUpKey = [];
      return foreignKey;
    };

    function keysFor(language) {
      var keys = (self.conf('keys') || {})[language];
      return keys || {};
    }

    var keyPressedCallbacks = {};

    function modifiedKeyCode(event) {
      var k = event.keyCode;

      if (event.shiftKey) k = k + self.shiftModifier;
      if (event.ctrlKey)  k = k + self.ctrlModifier;
      if (event.altKey)   k = k + self.altModifier;
      if (event.metaKey)  k = k + self.metaModifier;

      return k;
    }

    var handleCallbacks = function(event) {
      var keyCode = modifiedKeyCode(event);
      var callbacks = keyPressedCallbacks[keyCode];
      if (callbacks) {
        resolveCallbacks(callbacks, event);
        resumePropagation();
      }
    };

    this.keydown = function (event) {
      // we're only acting on keyup for now
    };

    var forbiddenTags = ['INPUT'];
    var repeater = '';

    this.keyup = function (event) {
      if (arethusaUtil.isIncluded(forbiddenTags, event.target.tagName)) {
        return;
      }
      if (isRepeater(event.keyCode)) {
        var rep = event.keyCode - 48;
        repeater = repeater + rep;
        return;
      }
      var keyCode = modifiedKeyCode(event);
      handleCallbacks(event);
      resetRepeater();
    };

    this.doRepeated = function(fn) {
      var rep = parseInt(repeater, 10) || 1; // default value (and beware octals!)
      for (; rep > 0; rep--) {
        fn();
      }
    };

    function isRepeater(code) {
      if (! isNaN(code)) {
        var numeric = parseInt(code) - 48;
        return numeric > -1 && numeric < 10;
      }
    }

    function resetRepeater() {
      repeater = '';
    }

    function Callback(callback, priority) {
      this.callback = callback;
      this.priority = priority || 0;
    }

    this.onKeyPressed = function(key, callback, priority) {
      var keyCode = self.getKeyCode(key);
      var callbacks = keyPressedCallbacks[keyCode] || [];
      callbacks.push(new Callback(callback, priority));
      keyPressedCallbacks[keyCode] = sortedByPriority(callbacks);
    };

    function sortedByPriority(callbacks) {
      return callbacks.sort(function(a, b) {
        return b.priority - a.priority;
      });
    }

    var propagationStopped = false;

    this.stopPropagation = function() {
      propagationStopped = true;
    };

    function resumePropagation() {
      propagationStopped = false;
    }

    function resolveCallbacks(callbacks, event) {
      angular.forEach(callbacks, function(callbackObj, key) {
        if (! propagationStopped) {
          callbackObj.callback(event);
        }
      });
    }

    // deprecated - use initCaptures instead
    this.registerCaptures = function(captures, scope) {
      scope = scope ? scope : $rootScope;
      angular.forEach(captures, function(fn, key) {
        if (angular.isDefined(key)) {
          self.onKeyPressed(key, function() {
            scope.$apply(fn);
          });
        }
      });
    };

    function Capture(confKey, fn, defaultKey) {
      this.confKey = confKey;
      this.fn = fn;
      this.defaultKey = defaultKey;
    }

    this.create = function(confKey, fn, defaultKey) {
      return new Capture(confKey, fn, defaultKey);
    };

    this.initCaptures = function(callback) {
      angular.forEach(callback(self), function(captures, section) {
        var conf = self.conf(section);
        angular.forEach(captures, function(capture, i) {
          var key = conf[capture.confKey] || capture.defaultKey;
          if (angular.isDefined(key)) {
            self.onKeyPressed(key, function() {
              $rootScope.$apply(capture.fn);
            });
          }
        });
      });
    };
  }
]);
<|endoftext|>


<commit_before>'use strict';
angular.module('arethusa.core').service('keyCapture', [
  'configurator',
  '$rootScope',
  function(configurator, $rootScope) {
    var self = this;

    this.conf = function(name) {
      var c = configurator.configurationFor('keyCapture') || {};
      return c[name] || {};
    };

    var keyCodes = {
      return: 13,
      shift: 16,
      ctrl: 17,
      alt: 18,
      esc: 27,
      ":" : 186,
      "[" : 219,
      "'" : 222,
      "]" : 221
    };

    // a-z codes
    for (var i = 97; i < 123; i++){
      keyCodes[String.fromCharCode(i)] = i - 32;
    }

    var CodesToKeys = arethusaUtil.inject({}, keyCodes, function(memo, key, code) {
      memo[code] = key;
    });

    this.shiftModifier = 1000;
    this.ctrlModifier  = 2000;
    this.altModifier   = 4000;
    this.metaModifier  = 8000;

    this.getKeyCode = function(key) {
      var parsed = parseKey(key);
      var modifiers = parsed[0];
      var k = parsed[1];
      angular.forEach(modifiers, function(modifier, i) {
        var mod;
        switch (modifier) {
          case 'ctrl':  mod = self.ctrlModifier;  break;
          case 'shift': mod = self.shiftModifier; break;
          case 'alt':   mod = self.altModifier;   break;
          case 'meta':  mod = self.metaModifier;   break;
        }
        k = k + mod;
      });

      return k;
    };

    function parseKey(key) {
      var parts = key.split('-');
      var k = parts.pop();
      if (k.match(/[A-Z]/)) {
        // in case someone wants to do shift-J
        if (! arethusaUtil.isIncluded(parts, 'shift')) {
          parts.push('shift');
        }
        k = k.toLowerCase();
      }

      return [parts, keyCodes[k]];
    }

    function modifiers(keys) {
      return Object.keys(keys.modifiers);
    }

    var lookUpKey = [];
    this.getForeignKey = function(event, language) {
      var res = [];
      var keys = keysFor(language);
      var key = CodesToKeys[event.keyCode];
      var mod = keys.modifiers;
      if (key) {
        // We don't want to match 'shift' as a key, so
        // we return if it's the case.
        if (key == 'shift') {
          return;
        }
        if (event.shiftKey) {
          res.push('shift');
        }
        if (arethusaUtil.isIncluded(modifiers(keys), key)) {
          res.push(key);
          var joined = res.join('-');
          lookUpKey.push(joined);
          return false;
        } else {
          if (arethusaUtil.isIncluded(res, 'shift')) {
            // Following lines provide that 'shift-a'
            // and 'A' is the same.
            var i = res.indexOf("shift");
            res.splice(i, 1);
            key = key.toUpperCase();
          }
          res.push(key);
        }
      }

      var lookUp = lookUpKey.concat(res);
      var sortedLookUp = sortLookUp(lookUp, mod);
      var foreignKey = keys[sortedLookUp.join('-')];
      lookUpKey = [];
      return foreignKey;
    };

    function sortLookUp(lookUp, modifiers) {
      var lastItem = lookUp.length - 1;
      var letter = lookUp[lastItem];
      var mod = lookUp.slice(0, lastItem);
      mod.sort(function(a,b) {
        return modifiers[a]-modifiers[b];
      });
      return mod.concat(letter);
    }

    function keysFor(language) {
      var keys = (self.conf('keys') || {})[language];
      return keys || {};
    }

    var keyPressedCallbacks = {};

    function modifiedKeyCode(event) {
      var k = event.keyCode;

      if (event.shiftKey) k = k + self.shiftModifier;
      if (event.ctrlKey)  k = k + self.ctrlModifier;
      if (event.altKey)   k = k + self.altModifier;
      if (event.metaKey)  k = k + self.metaModifier;

      return k;
    }

    var handleCallbacks = function(event) {
      var keyCode = modifiedKeyCode(event);
      var callbacks = keyPressedCallbacks[keyCode];
      if (callbacks) {
        resolveCallbacks(callbacks, event);
        resumePropagation();
      }
    };

    this.keydown = function (event) {
      // we're only acting on keyup for now
    };

    var forbiddenTags = ['INPUT'];
    var repeater = '';

    this.keyup = function (event) {
      if (arethusaUtil.isIncluded(forbiddenTags, event.target.tagName)) {
        return;
      }
      if (isRepeater(event.keyCode)) {
        var rep = event.keyCode - 48;
        repeater = repeater + rep;
        return;
      }
      var keyCode = modifiedKeyCode(event);
      handleCallbacks(event);
      resetRepeater();
    };

    this.doRepeated = function(fn) {
      var rep = parseInt(repeater, 10) || 1; // default value (and beware octals!)
      for (; rep > 0; rep--) {
        fn();
      }
    };

    function isRepeater(code) {
      if (! isNaN(code)) {
        var numeric = parseInt(code) - 48;
        return numeric > -1 && numeric < 10;
      }
    }

    function resetRepeater() {
      repeater = '';
    }

    function Callback(callback, priority) {
      this.callback = callback;
      this.priority = priority || 0;
    }

    this.onKeyPressed = function(key, callback, priority) {
      var keyCode = self.getKeyCode(key);
      var callbacks = keyPressedCallbacks[keyCode] || [];
      callbacks.push(new Callback(callback, priority));
      keyPressedCallbacks[keyCode] = sortedByPriority(callbacks);
    };

    function sortedByPriority(callbacks) {
      return callbacks.sort(function(a, b) {
        return b.priority - a.priority;
      });
    }

    var propagationStopped = false;

    this.stopPropagation = function() {
      propagationStopped = true;
    };

    function resumePropagation() {
      propagationStopped = false;
    }

    function resolveCallbacks(callbacks, event) {
      angular.forEach(callbacks, function(callbackObj, key) {
        if (! propagationStopped) {
          callbackObj.callback(event);
        }
      });
    }

    // deprecated - use initCaptures instead
    this.registerCaptures = function(captures, scope) {
      scope = scope ? scope : $rootScope;
      angular.forEach(captures, function(fn, key) {
        if (angular.isDefined(key)) {
          self.onKeyPressed(key, function() {
            scope.$apply(fn);
          });
        }
      });
    };

    function Capture(confKey, fn, defaultKey) {
      this.confKey = confKey;
      this.fn = fn;
      this.defaultKey = defaultKey;
    }

    this.create = function(confKey, fn, defaultKey) {
      return new Capture(confKey, fn, defaultKey);
    };

    function addToKeys(keys, sec, name, key) {
      if (!keys[sec]) keys[sec] = {};
      keys[sec][name] = key;
    }

    // Tries to init keyCaptures - returns every successful keybinding in the format:
    //
    // { section: { nameOfAction: key }
    //
    // keyCapture stores them in keyCapture.activeKeys as well.
    this.initCaptures = function(callback) {
      var keys = arethusaUtil.inject({}, callback(self), function(memo, section, captures) {
        var conf = self.conf(section);
        angular.forEach(captures, function(capture, i) {
          var key = conf[capture.confKey] || capture.defaultKey;
          if (angular.isDefined(key)) {
            addToKeys(memo, section, capture.confKey, key);
            self.onKeyPressed(key, function() {
              $rootScope.$apply(capture.fn);
            });
          }
        });
      });
      if (!angular.equals({}, keys)) {
        angular.extend(self.activeKeys, keys);
      }
      return keys;
    };

    // We might have to reinit this at some point
    this.activeKeys = {};
  }
]);
<commit_msg>Delete an obsolete LoC in keyCapture<commit_after>'use strict';
angular.module('arethusa.core').service('keyCapture', [
  'configurator',
  '$rootScope',
  function(configurator, $rootScope) {
    var self = this;

    this.conf = function(name) {
      var c = configurator.configurationFor('keyCapture') || {};
      return c[name] || {};
    };

    var keyCodes = {
      return: 13,
      shift: 16,
      ctrl: 17,
      alt: 18,
      esc: 27,
      ":" : 186,
      "[" : 219,
      "'" : 222,
      "]" : 221
    };

    // a-z codes
    for (var i = 97; i < 123; i++){
      keyCodes[String.fromCharCode(i)] = i - 32;
    }

    var CodesToKeys = arethusaUtil.inject({}, keyCodes, function(memo, key, code) {
      memo[code] = key;
    });

    this.shiftModifier = 1000;
    this.ctrlModifier  = 2000;
    this.altModifier   = 4000;
    this.metaModifier  = 8000;

    this.getKeyCode = function(key) {
      var parsed = parseKey(key);
      var modifiers = parsed[0];
      var k = parsed[1];
      angular.forEach(modifiers, function(modifier, i) {
        var mod;
        switch (modifier) {
          case 'ctrl':  mod = self.ctrlModifier;  break;
          case 'shift': mod = self.shiftModifier; break;
          case 'alt':   mod = self.altModifier;   break;
          case 'meta':  mod = self.metaModifier;   break;
        }
        k = k + mod;
      });

      return k;
    };

    function parseKey(key) {
      var parts = key.split('-');
      var k = parts.pop();
      if (k.match(/[A-Z]/)) {
        // in case someone wants to do shift-J
        if (! arethusaUtil.isIncluded(parts, 'shift')) {
          parts.push('shift');
        }
        k = k.toLowerCase();
      }

      return [parts, keyCodes[k]];
    }

    function modifiers(keys) {
      return Object.keys(keys.modifiers);
    }

    var lookUpKey = [];
    this.getForeignKey = function(event, language) {
      var res = [];
      var keys = keysFor(language);
      var key = CodesToKeys[event.keyCode];
      var mod = keys.modifiers;
      if (key) {
        // We don't want to match 'shift' as a key, so
        // we return if it's the case.
        if (key == 'shift') {
          return;
        }
        if (event.shiftKey) {
          res.push('shift');
        }
        if (arethusaUtil.isIncluded(modifiers(keys), key)) {
          res.push(key);
          var joined = res.join('-');
          lookUpKey.push(joined);
          return false;
        } else {
          if (arethusaUtil.isIncluded(res, 'shift')) {
            // Following lines provide that 'shift-a'
            // and 'A' is the same.
            var i = res.indexOf("shift");
            res.splice(i, 1);
            key = key.toUpperCase();
          }
          res.push(key);
        }
      }

      var lookUp = lookUpKey.concat(res);
      var sortedLookUp = sortLookUp(lookUp, mod);
      var foreignKey = keys[sortedLookUp.join('-')];
      lookUpKey = [];
      return foreignKey;
    };

    function sortLookUp(lookUp, modifiers) {
      var lastItem = lookUp.length - 1;
      var letter = lookUp[lastItem];
      var mod = lookUp.slice(0, lastItem);
      mod.sort(function(a,b) {
        return modifiers[a]-modifiers[b];
      });
      return mod.concat(letter);
    }

    function keysFor(language) {
      var keys = (self.conf('keys') || {})[language];
      return keys || {};
    }

    var keyPressedCallbacks = {};

    function modifiedKeyCode(event) {
      var k = event.keyCode;

      if (event.shiftKey) k = k + self.shiftModifier;
      if (event.ctrlKey)  k = k + self.ctrlModifier;
      if (event.altKey)   k = k + self.altModifier;
      if (event.metaKey)  k = k + self.metaModifier;

      return k;
    }

    var handleCallbacks = function(event) {
      var keyCode = modifiedKeyCode(event);
      var callbacks = keyPressedCallbacks[keyCode];
      if (callbacks) {
        resolveCallbacks(callbacks, event);
        resumePropagation();
      }
    };

    this.keydown = function (event) {
      // we're only acting on keyup for now
    };

    var forbiddenTags = ['INPUT'];
    var repeater = '';

    this.keyup = function (event) {
      if (arethusaUtil.isIncluded(forbiddenTags, event.target.tagName)) {
        return;
      }
      if (isRepeater(event.keyCode)) {
        var rep = event.keyCode - 48;
        repeater = repeater + rep;
        return;
      }
      handleCallbacks(event);
      resetRepeater();
    };

    this.doRepeated = function(fn) {
      var rep = parseInt(repeater, 10) || 1; // default value (and beware octals!)
      for (; rep > 0; rep--) {
        fn();
      }
    };

    function isRepeater(code) {
      if (! isNaN(code)) {
        var numeric = parseInt(code) - 48;
        return numeric > -1 && numeric < 10;
      }
    }

    function resetRepeater() {
      repeater = '';
    }

    function Callback(callback, priority) {
      this.callback = callback;
      this.priority = priority || 0;
    }

    this.onKeyPressed = function(key, callback, priority) {
      var keyCode = self.getKeyCode(key);
      var callbacks = keyPressedCallbacks[keyCode] || [];
      callbacks.push(new Callback(callback, priority));
      keyPressedCallbacks[keyCode] = sortedByPriority(callbacks);
    };

    function sortedByPriority(callbacks) {
      return callbacks.sort(function(a, b) {
        return b.priority - a.priority;
      });
    }

    var propagationStopped = false;

    this.stopPropagation = function() {
      propagationStopped = true;
    };

    function resumePropagation() {
      propagationStopped = false;
    }

    function resolveCallbacks(callbacks, event) {
      angular.forEach(callbacks, function(callbackObj, key) {
        if (! propagationStopped) {
          callbackObj.callback(event);
        }
      });
    }

    // deprecated - use initCaptures instead
    this.registerCaptures = function(captures, scope) {
      scope = scope ? scope : $rootScope;
      angular.forEach(captures, function(fn, key) {
        if (angular.isDefined(key)) {
          self.onKeyPressed(key, function() {
            scope.$apply(fn);
          });
        }
      });
    };

    function Capture(confKey, fn, defaultKey) {
      this.confKey = confKey;
      this.fn = fn;
      this.defaultKey = defaultKey;
    }

    this.create = function(confKey, fn, defaultKey) {
      return new Capture(confKey, fn, defaultKey);
    };

    function addToKeys(keys, sec, name, key) {
      if (!keys[sec]) keys[sec] = {};
      keys[sec][name] = key;
    }

    // Tries to init keyCaptures - returns every successful keybinding in the format:
    //
    // { section: { nameOfAction: key }
    //
    // keyCapture stores them in keyCapture.activeKeys as well.
    this.initCaptures = function(callback) {
      var keys = arethusaUtil.inject({}, callback(self), function(memo, section, captures) {
        var conf = self.conf(section);
        angular.forEach(captures, function(capture, i) {
          var key = conf[capture.confKey] || capture.defaultKey;
          if (angular.isDefined(key)) {
            addToKeys(memo, section, capture.confKey, key);
            self.onKeyPressed(key, function() {
              $rootScope.$apply(capture.fn);
            });
          }
        });
      });
      if (!angular.equals({}, keys)) {
        angular.extend(self.activeKeys, keys);
      }
      return keys;
    };

    // We might have to reinit this at some point
    this.activeKeys = {};
  }
]);
<|endoftext|>


<commit_before>import isDate from 'lodash/isDate'
import isEmpty from 'lodash/isEmpty'
import isPlainObject from 'lodash/isPlainObject'
import isString from 'lodash/isString'

// Generate a new random key. Probably unique.
export function nextId() {
  return Math.random().toString(36).substr(6)
  .substring(1, 9)
}
// Check to see if object has required fields to be an entity.
export function isEntity(value) {
  return isPlainObject(value) && isString(value.type)
}

// Check to see if the entity has been populated with fields required for save.
export function isEntityCreated(entity, checkDate = false) {
  if (!isEntity(entity) || isEmpty(entity.id)) return false
  if (checkDate && !isDate(entity.dateCreated)) return false
  return true
}
// Add fields required for save.
export function insertFields(data) {
  if (isEntityCreated(data, true)) return data
  return {
    ...data,
    dateCreated: data.dateCreated || new Date(),
    id: data.id || nextId(),
    type: data.type || 'Thing',
  }
}
<commit_msg>uniqEntity creates sample entity<commit_after>import isDate from 'lodash/isDate'
import isEmpty from 'lodash/isEmpty'
import isPlainObject from 'lodash/isPlainObject'
import isString from 'lodash/isString'

// Generate a new random key. Probably unique.
export function nextId() {
  return Math.random().toString(36).substr(6)
  .substring(1, 9)
}
// Check to see if object has required fields to be an entity.
export function isEntity(value) {
  return isPlainObject(value) && isString(value.type)
}

// Check to see if the entity has been populated with fields required for save.
export function isEntityCreated(entity, checkDate = false) {
  if (!isEntity(entity) || isEmpty(entity.id)) return false
  if (checkDate && !isDate(entity.dateCreated)) return false
  return true
}
// Add fields required for save.
export function insertFields(data) {
  if (isEntityCreated(data, true)) return data
  return {
    ...data,
    dateCreated: data.dateCreated || new Date(),
    id: data.id || nextId(),
    type: data.type || 'Thing',
  }
}
export function uniqEntity(type) { return insertFields({ type }) }
<|endoftext|>