<commit_before>(function (clique, _, Backbone, $) {
    "use strict";

    clique.adapter = {};

    clique.Adapter = function (options) {
        var accessors;

        // Keep track of accessors.
        this.accessors = accessors = {};
        this.onNewAccessor = this.onNewAccessor || _.noop;
        this.addAccessor = function (blob) {
            var mut;

            if (!_.has(accessors, blob.key)) {
                accessors[blob.key] = new clique.util.Accessor(blob);
                this.onNewAccessor(mut);
            }

            mut = accessors[blob.key];

            return mut;
        };

        // Define methods.
        this.getAccessor = _.propertyOf(accessors);

        this.findNodes = function (spec, offset, limit) {
            return $.when(this.findNodesRaw(spec, offset, limit))
                .then(_.partial(_.map, _, this.addAccessor, this));
        };

        this.findNode = function (spec) {
            return $.when(this.findNodesRaw(spec)).then(_.bind(function (results) {
                if (_.isEmpty(results)) {
                    return undefined;
                }

                return this.addAccessor(results[0]);
            }, this));
        };

        this.findNodeByKey = function (key) {
            return this.findNode({
                key: key
            });
        };

        this.findLinks = function (_spec, offset, limit) {
            var spec = clique.util.deepCopy(_spec),
                directed = spec.directed,
                source = spec.source,
                target = spec.target;

            delete spec.directed;
            delete spec.source;
            delete spec.target;

            return $.when(this.findLinksRaw(spec, source, target, directed, offset, limit))
                .then(_.partial(_.map, _, this.addAccessor, this));
        };

        this.findLink = function (spec) {
            return $.when(this.findLinksRaw(spec)).then(_.bind(function (results) {
                if (_.isEmpty(results)) {
                    return undefined;
                }

                return this.addAccessor(results[0]);
            }, this));
        };

        this.findLinkByKey = function (key) {
            return this.findLink({
                key: key
            });
        };

        this.neighborLinkCount = function (node, opts) {
            return this.neighborLinks(node, opts).then(_.size);
        };

        this.outgoingLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: true,
                incoming: false,
                undirected: false
            });
        };

        this.outflowingLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: true,
                incoming: false,
                undirected: true
            });
        };

        this.incomingLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: false,
                incoming: true,
                undirected: false
            });
        };

        this.inflowingLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: false,
                incoming: true,
                undirected: true
            });
        };

        this.undirectedLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: false,
                incoming: false,
                undirected: true
            });
        };

        this.directedLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: true,
                incoming: true,
                undirected: false
            });
        };

        this.neighborLinks = function (node, opts, offset, limit) {
            return this.neighborLinksRaw(node, opts, offset, limit)
                .then(_.partial(_.map, _, this.addAccessor, this));
        };

        this.neighborLinksRaw = function (node, opts, offset, limit) {
            var reqs = [];

            opts = opts || {};
            _.each(["outgoing", "incoming", "undirected"], function (mode) {
                opts[mode] = _.isUndefined(opts[mode]) ? true : opts[mode];
            });

            if (opts.outgoing) {
                reqs.push(this.findLinksRaw(undefined, node.key(), undefined, true));
            }

            if (opts.incoming) {
                reqs.push(this.findLinksRaw(undefined, undefined, node.key(), true));
            }

            if (opts.undirected) {
                reqs.push(this.findLinksRaw(undefined, node.key(), undefined, false));
                reqs.push(this.findLinksRaw(undefined, undefined, node.key(), false));
            }

            return $.when.apply($, reqs).then(function () {
                // This pipeline zips together the list of results, then
                // flattens the resulting list-of-lists, removes all undefineds
                // (which may arise if some of the lists are shorter than
                // others), then finally slices the result to get the final list
                // of links.
                return _.filter(_.flatten(_.zip.apply(_, _.toArray(arguments))), _.negate(_.isUndefined))
                    .slice(offset || 0, (offset + limit) || undefined);
            });
        };

        this.outgoingLinks = function (node) {
            return this.neighborLinks(node, {
                outgoing: true,
                incoming: false,
                undirected: false
            });
        };

        this.outflowingLinks = function (node) {
            return this.neighborLinks(node, {
                outgoing: true,
                incoming: false,
                undirected: true
            });
        };

        this.incomingLinks = function (node) {
            return this.neighborLinks(node, {
                outgoing: false,
                incoming: true,
                undirected: false
            });
        };

        this.inflowingLinks = function (node) {
            return this.neighborLinks(node, {
                outgoing: false,
                incoming: true,
                undirected: true
            });
        };

        this.undirectedLinks = function (node) {
            return this.neighborLinks(node, {
                outgoing: false,
                incoming: false,
                undirected: true
            });
        };

        this.directedLinks = function (node) {
            return this.neighborLinks(node, {
                outgoing: true,
                incoming: true,
                undirected: false
            });
        };

        this.neighborCount = function (node, opts) {
            return this.neighbors(node, opts).then(function (nbrs) {
                return _.size(nbrs.nodes);
            });
        };

        this.outgoingNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: true,
                incoming: false,
                undirected: false
            });
        };

        this.outflowingNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: true,
                incoming: false,
                undirected: true
            });
        };

        this.incomingNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: false,
                incoming: true,
                undirected: false
            });
        };

        this.inflowingNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: false,
                incoming: true,
                undirected: true
            });
        };

        this.undirectedNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: false,
                incoming: false,
                undirected: true
            });
        };

        this.directedNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: true,
                incoming: true,
                undirected: false
            });
        };

        this.neighbors = function (node, opts) {
            var key = node.key(),
                links;

            return this.neighborLinks(node, opts).then(_.bind(function (links_) {
                var neighborKeys,
                    muts;

                links = links_;

                neighborKeys = _.map(links, function (link) {
                    return key === link.source() ? link.target() : link.source();
                });

                // Attempt to retrieve the accessors.  The ones we don't have
                // yet will show up as undefined in this array.
                muts = _.map(neighborKeys, this.getAccessor, this);

                _.each(muts, function (mut, i) {
                    if (_.isUndefined(mut)) {
                        muts[i] = this.findNodeByKey(neighborKeys[i]);
                    } else {
                        muts[i] = $.when(muts[i]);
                    }
                }, this);

                return $.when.apply($, muts);
            }, this)).then(function () {
                return {
                    nodes: _.toArray(arguments),
                    links: links
                };
            });
        };

        this.outgoingNeighbors = function (node) {
            return this.neighbors(node, {
                outgoing: true,
                incoming: false,
                undirected: false
            });
        };

        this.outflowingNeighbors = function (node) {
            return this.neighbors(node, {
                outgoing: true,
                incoming: false,
                undirected: true
            });
        };

        this.incomingNeighbors = function (node) {
            return this.neighbors(node, {
                outgoing: false,
                incoming: true,
                undirected: false
            });
        };

        this.inflowingNeighbors = function (node) {
            return this.neighbors(node, {
                outgoing: false,
                incoming: true,
                undirected: true
            });
        };

        this.undirectedNeighbors = function (node) {
            return this.neighbors(node, {
                outgoing: false,
                incoming: false,
                undirected: true
            });
        };

        this.directedNeighbors = function (node) {
            return this.neighbors(node, {
                outgoing: true,
                incoming: true,
                undirected: false
            });
        };

        this.createNode = function (data) {
            return $.when(this.createNodeRaw(data || {}))
                .then(_.bind(this.addAccessor, this));
        };

        this.createLink = function (source, target, _data, undirected) {
            var data;

            // If source/target is a accessor, call its key method to get the
            // key; otherwise, assume it is a string describing the key already.
            source = _.result(source, "key", source);
            target = _.result(target, "key", target);

            data = _data || {};
            undirected = _.isUndefined(undirected) ? false : undirected;

            return $.when(this.createLinkRaw(source, target, data, undirected))
                .then(_.bind(this.addAccessor, this));
        };

        this.destroyNode = function (node) {
            var key = node.key();
            return this.destroyNodeRaw(key).then(function (response) {
                return {
                    key: key,
                    response: response
                };
            });
        };

        this.destroyLink = function (link) {
            var key = link.key();
            return this.destroyLinkRaw(key).then(function (response) {
                return {
                    key: key,
                    response: response
                };
            });
        };

        options = options || {};
        this.initialize.apply(this, arguments);
    };

    clique.Adapter.extend = Backbone.Model.extend;

    clique.adapter.NodeLinkList = clique.Adapter.extend({
        initialize: function (options) {
            var table = {};

            this.nodes = clique.util.deepCopy(options.nodes);
            this.links = clique.util.deepCopy(options.links);

            _.each(this.nodes, function (node, i) {
                var tmpNs = "datax",
                    ns;

                // Promote the data elements into a dedicated namespace.
                //
                // First figure out a suitable temporary name to use for the
                // namespace.
                while (_.has(node, tmpNs)) {
                    tmpNs += "x";
                }

                // Create the namespace.
                ns = node[tmpNs] = {};

                // Move all top-level properties into the namespace.
                _.each(node, function (v, k) {
                    if (k !== tmpNs) {
                        ns[k] = v;
                        delete node[k];
                    }
                });

                // Rename the temporary namespace as "data".
                if (tmpNs !== "data") {
                    node.data = ns;
                    delete node[tmpNs];
                }

                table[i] = node;

                // Assign a unique key to this node.
                node.key = _.uniqueId("node_");
            });

            _.each(this.links, function (link) {
                link.key = _.uniqueId("link_");

                link.source = table[link.source];
                link.target = table[link.target];
            }, this);
        },

        findNodesRaw: function (_spec, offset, limit) {
            var spec = clique.util.deepCopy(_spec),
                searchspace = this.nodes,
                result;

            if (spec.key) {
                searchspace = _.filter(searchspace, function (node) {
                    return node.key === spec.key;
                });

                delete spec.key;
            }

            result = _.filter(searchspace, function (node) {
                return _.isMatch(node.data, spec);
            });

            return result.slice(offset || 0, (offset + limit) || undefined);
        },

        findLinksRaw: function (spec, source, target, directed, offset, limit) {
            return _.filter(this.links, function (link) {
                var directedMatch,
                    sourceMatch = _.isUndefined(source) || (link.source.key === source),
                    targetMatch = _.isUndefined(target) || (link.target.key === target),
                    dataMatch = _.isMatch(spec || {}, link.data);

                if (_.isUndefined(directed) || _.isNull(directed)) {
                    directedMatch = true;
                } else if (directed) {
                    directedMatch = !link.undirected;
                } else {
                    directedMatch = link.undirected;
                }

                return _.every([sourceMatch, targetMatch, dataMatch, directedMatch]);
            }).slice(offset || 0, (offset + limit) || undefined);
        }
    });
}(window.clique, window._, window.Backbone, window.jQuery));
<commit_msg>Switch to cfg-object based calling convention to incorporate limit/offset<commit_after>(function (clique, _, Backbone, $) {
    "use strict";

    clique.adapter = {};

    clique.Adapter = function (options) {
        var accessors;

        // Keep track of accessors.
        this.accessors = accessors = {};
        this.onNewAccessor = this.onNewAccessor || _.noop;
        this.addAccessor = function (blob) {
            var mut;

            if (!_.has(accessors, blob.key)) {
                accessors[blob.key] = new clique.util.Accessor(blob);
                this.onNewAccessor(mut);
            }

            mut = accessors[blob.key];

            return mut;
        };

        // Define methods.
        this.getAccessor = _.propertyOf(accessors);

        this.findNodes = function (cfg) {
            var spec = cfg.spec || {},
                offset = cfg.offset || 0,
                limit = cfg.limit;

            return $.when(this.findNodesRaw(spec, offset, limit))
                .then(_.partial(_.map, _, this.addAccessor, this));
        };

        this.findNode = function (spec) {
            var req = this.findNodes({
                spec: spec,
                offset: 0,
                limit: 1
            });

            return $.when(req).then(_.bind(function (results) {
                return results && results[0];
            }, this));
        };

        this.findNodeByKey = function (key) {
            return this.findNode({
                key: key
            });
        };

        this.findLinks = function (cfg) {
            var spec = cfg.spec,
                source = cfg.source,
                target = cfg.target,
                directed = cfg.directed,
                offset = cfg.offset,
                limit = cfg.limit;

            return $.when(this.findLinksRaw(spec, source, target, directed, offset, limit))
                .then(_.partial(_.map, _, this.addAccessor, this));
        };

        this.findLink = function (_cfg) {
            var cfg = _.extend({}, _cfg, {
                offset: 0,
                limit: 1
            });

            return $.when(this.findLinks(cfg)).then(_.bind(function (results) {
                return results && results[0];
            }, this));
        };

        this.findLinkByKey = function (key) {
            return this.findLink({
                key: key
            });
        };

        this.neighborLinkCount = function (node, opts) {
            return this.neighborLinks(node, opts).then(_.size);
        };

        this.outgoingLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: true,
                incoming: false,
                undirected: false
            });
        };

        this.outflowingLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: true,
                incoming: false,
                undirected: true
            });
        };

        this.incomingLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: false,
                incoming: true,
                undirected: false
            });
        };

        this.inflowingLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: false,
                incoming: true,
                undirected: true
            });
        };

        this.undirectedLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: false,
                incoming: false,
                undirected: true
            });
        };

        this.directedLinkCount = function (node) {
            return this.neighborLinkCount(node, {
                outgoing: true,
                incoming: true,
                undirected: false
            });
        };

        this.neighborLinks = function (node, cfg) {
            var types,
                offset,
                limit;

            cfg = cfg || {};

            types = cfg.types || {};
            offset = cfg.offset;
            limit = cfg.limit;

            return this.neighborLinksRaw(node, types, offset, limit)
                .then(_.partial(_.map, _, this.addAccessor, this));
        };

        this.neighborLinksRaw = function (node, _types, offset, limit) {
            var reqs = [],
                types = {};

            _.each(["outgoing", "incoming", "undirected"], function (mode) {
                types[mode] = _.isUndefined(_types[mode]) ? true : _types[mode];
            });

            if (types.outgoing) {
                reqs.push(this.findLinksRaw(undefined, node.key(), undefined, true));
            }

            if (types.incoming) {
                reqs.push(this.findLinksRaw(undefined, undefined, node.key(), true));
            }

            if (types.undirected) {
                reqs.push(this.findLinksRaw(undefined, node.key(), undefined, false));
                reqs.push(this.findLinksRaw(undefined, undefined, node.key(), false));
            }

            return $.when.apply($, reqs).then(function () {
                // This pipeline zips together the list of results, then
                // flattens the resulting list-of-lists, removes all undefineds
                // (which may arise if some of the lists are shorter than
                // others), then finally slices the result to get the final list
                // of links.
                return _.filter(_.flatten(_.zip.apply(_, _.toArray(arguments))), _.negate(_.isUndefined))
                    .slice(offset || 0, (offset + limit) || undefined);
            });
        };

        this.outgoingLinks = function (node, offset, limit) {
            return this.neighborLinks(node, {
                types: {
                    outgoing: true,
                    incoming: false,
                    undirected: false
                },
                offset: offset,
                limit: limit
            });
        };

        this.outflowingLinks = function (node, offset, limit) {
            return this.neighborLinks(node, {
                types: {
                    outgoing: true,
                    incoming: false,
                    undirected: true
                },
                offset: offset,
                limit: limit
            });
        };

        this.incomingLinks = function (node, offset, limit) {
            return this.neighborLinks(node, {
                types: {
                    outgoing: false,
                    incoming: true,
                    undirected: false
                },
                offset: offset,
                limit: limit
            });
        };

        this.inflowingLinks = function (node, offset, limit) {
            return this.neighborLinks(node, {
                types: {
                    outgoing: false,
                    incoming: true,
                    undirected: true
                },
                offset: offset,
                limit: limit
            });
        };

        this.undirectedLinks = function (node, offset, limit) {
            return this.neighborLinks(node, {
                types: {
                    outgoing: false,
                    incoming: false,
                    undirected: true
                },
                offset: offset,
                limit: limit
            });
        };

        this.directedLinks = function (node, offset, limit) {
            return this.neighborLinks(node, {
                types: {
                    outgoing: true,
                    incoming: true,
                    undirected: false
                },
                offset: offset,
                limit: limit
            });
        };

        this.neighborCount = function (node, opts) {
            return this.neighbors(node, opts).then(function (nbrs) {
                return _.size(nbrs.nodes);
            });
        };

        this.outgoingNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: true,
                incoming: false,
                undirected: false
            });
        };

        this.outflowingNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: true,
                incoming: false,
                undirected: true
            });
        };

        this.incomingNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: false,
                incoming: true,
                undirected: false
            });
        };

        this.inflowingNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: false,
                incoming: true,
                undirected: true
            });
        };

        this.undirectedNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: false,
                incoming: false,
                undirected: true
            });
        };

        this.directedNeighborCount = function (node) {
            return this.neighborCount(node, {
                outgoing: true,
                incoming: true,
                undirected: false
            });
        };

        this.neighbors = function (node, opts) {
            var key = node.key(),
                links;

            return this.neighborLinks(node, opts).then(_.bind(function (links_) {
                var neighborKeys,
                    muts;

                links = links_;

                neighborKeys = _.map(links, function (link) {
                    return key === link.source() ? link.target() : link.source();
                });

                // Attempt to retrieve the accessors.  The ones we don't have
                // yet will show up as undefined in this array.
                muts = _.map(neighborKeys, this.getAccessor, this);

                _.each(muts, function (mut, i) {
                    if (_.isUndefined(mut)) {
                        muts[i] = this.findNodeByKey(neighborKeys[i]);
                    } else {
                        muts[i] = $.when(muts[i]);
                    }
                }, this);

                return $.when.apply($, muts);
            }, this)).then(function () {
                return {
                    nodes: _.toArray(arguments),
                    links: links
                };
            });
        };

        this.outgoingNeighbors = function (node, offset, limit) {
            return this.neighbors(node, {
                types: {
                    outgoing: true,
                    incoming: false,
                    undirected: false
                },
                offset: offset,
                limit: limit
            });
        };

        this.outflowingNeighbors = function (node, offset, limit) {
            return this.neighbors(node, {
                types: {
                    outgoing: true,
                    incoming: false,
                    undirected: true
                },
                offset: offset,
                limit: limit
            });
        };

        this.incomingNeighbors = function (node, offset, limit) {
            return this.neighbors(node, {
                types: {
                    outgoing: false,
                    incoming: true,
                    undirected: false
                },
                offset: offset,
                limit: limit
            });
        };

        this.inflowingNeighbors = function (node, offset, limit) {
            return this.neighbors(node, {
                types: {
                    outgoing: false,
                    incoming: true,
                    undirected: true
                },
                offset: offset,
                limit: limit
            });
        };

        this.undirectedNeighbors = function (node, offset, limit) {
            return this.neighbors(node, {
                types: {
                    outgoing: false,
                    incoming: false,
                    undirected: true
                },
                offset: offset,
                limit: limit
            });
        };

        this.directedNeighbors = function (node, offset, limit) {
            return this.neighbors(node, {
                types: {
                    outgoing: true,
                    incoming: true,
                    undirected: false
                },
                offset: offset,
                limit: limit
            });
        };

        this.createNode = function (data) {
            return $.when(this.createNodeRaw(data || {}))
                .then(_.bind(this.addAccessor, this));
        };

        this.createLink = function (source, target, _data, undirected) {
            var data;

            // If source/target is a accessor, call its key method to get the
            // key; otherwise, assume it is a string describing the key already.
            source = _.result(source, "key", source);
            target = _.result(target, "key", target);

            data = _data || {};
            undirected = _.isUndefined(undirected) ? false : undirected;

            return $.when(this.createLinkRaw(source, target, data, undirected))
                .then(_.bind(this.addAccessor, this));
        };

        this.destroyNode = function (node) {
            var key = node.key();
            return this.destroyNodeRaw(key).then(function (response) {
                return {
                    key: key,
                    response: response
                };
            });
        };

        this.destroyLink = function (link) {
            var key = link.key();
            return this.destroyLinkRaw(key).then(function (response) {
                return {
                    key: key,
                    response: response
                };
            });
        };

        options = options || {};
        this.initialize.apply(this, arguments);
    };

    clique.Adapter.extend = Backbone.Model.extend;

    clique.adapter.NodeLinkList = clique.Adapter.extend({
        initialize: function (options) {
            var table = {};

            this.nodes = clique.util.deepCopy(options.nodes);
            this.links = clique.util.deepCopy(options.links);

            _.each(this.nodes, function (node, i) {
                var tmpNs = "datax",
                    ns;

                // Promote the data elements into a dedicated namespace.
                //
                // First figure out a suitable temporary name to use for the
                // namespace.
                while (_.has(node, tmpNs)) {
                    tmpNs += "x";
                }

                // Create the namespace.
                ns = node[tmpNs] = {};

                // Move all top-level properties into the namespace.
                _.each(node, function (v, k) {
                    if (k !== tmpNs) {
                        ns[k] = v;
                        delete node[k];
                    }
                });

                // Rename the temporary namespace as "data".
                if (tmpNs !== "data") {
                    node.data = ns;
                    delete node[tmpNs];
                }

                table[i] = node;

                // Assign a unique key to this node.
                node.key = _.uniqueId("node_");
            });

            _.each(this.links, function (link) {
                link.key = _.uniqueId("link_");

                link.source = table[link.source];
                link.target = table[link.target];
            }, this);
        },

        findNodesRaw: function (_spec, offset, limit) {
            var spec = clique.util.deepCopy(_spec),
                searchspace = this.nodes,
                result;

            if (spec.key) {
                searchspace = _.filter(searchspace, function (node) {
                    return node.key === spec.key;
                });

                delete spec.key;
            }

            result = _.filter(searchspace, function (node) {
                return _.isMatch(node.data, spec);
            });

            return result.slice(offset || 0, (offset + limit) || undefined);
        },

        findLinksRaw: function (spec, source, target, directed, offset, limit) {
            return _.filter(this.links, function (link) {
                var directedMatch,
                    sourceMatch = _.isUndefined(source) || (link.source.key === source),
                    targetMatch = _.isUndefined(target) || (link.target.key === target),
                    dataMatch = _.isMatch(spec || {}, link.data);

                if (_.isUndefined(directed) || _.isNull(directed)) {
                    directedMatch = true;
                } else if (directed) {
                    directedMatch = !link.undirected;
                } else {
                    directedMatch = link.undirected;
                }

                return _.every([sourceMatch, targetMatch, dataMatch, directedMatch]);
            }).slice(offset || 0, (offset + limit) || undefined);
        }
    });
}(window.clique, window._, window.Backbone, window.jQuery));
<|endoftext|>


<commit_before>import {
  setTransform,
  createElement,
} from './util/util.js';

/** @typedef {import('./photoswipe.js').default} PhotoSwipe */
/** @typedef {import('./slide/slide.js').default} Slide */

/** @typedef {{ el: HTMLDivElement; slide?: Slide }} ItemHolder */

const MAIN_SCROLL_END_FRICTION = 0.35;

// const MIN_SWIPE_TRANSITION_DURATION = 250;
// const MAX_SWIPE_TRABSITION_DURATION = 500;
// const DEFAULT_SWIPE_TRANSITION_DURATION = 333;

/**
 * Handles movement of the main scrolling container
 * (for example, it repositions when user swipes left or right).
 *
 * Also stores its state.
 */
class MainScroll {
  /**
   * @param {PhotoSwipe} pswp
   */
  constructor(pswp) {
    this.pswp = pswp;
    this.x = 0;

    /** @type {number} */
    this.slideWidth = undefined;

    /** @type {ItemHolder[]} */
    this.itemHolders = undefined;

    this.resetPosition();
  }

  /**
   * Position the scroller and slide containers
   * according to viewport size.
   *
   * @param {boolean=} resizeSlides Whether slides content should resized
   */
  resize(resizeSlides) {
    const { pswp } = this;
    const newSlideWidth = Math.round(
      pswp.viewportSize.x + pswp.viewportSize.x * pswp.options.spacing
    );
    // Mobile browsers might trigger a resize event during a gesture.
    // (due to toolbar appearing or hiding).
    // Avoid re-adjusting main scroll position if width wasn't changed
    const slideWidthChanged = (newSlideWidth !== this.slideWidth);

    if (slideWidthChanged) {
      this.slideWidth = newSlideWidth;
      this.moveTo(this.getCurrSlideX());
    }

    this.itemHolders.forEach((itemHolder, index) => {
      if (slideWidthChanged) {
        setTransform(itemHolder.el, (index + this._containerShiftIndex)
                                    * this.slideWidth);
      }

      if (resizeSlides && itemHolder.slide) {
        itemHolder.slide.resize();
      }
    });
  }

  /**
   * Reset X position of the main scroller to zero
   */
  resetPosition() {
    // Position on the main scroller (offset)
    // it is independent from slide index
    this._currPositionIndex = 0;
    this._prevPositionIndex = 0;

    // This will force recalculation of size on next resize()
    this.slideWidth = 0;

    // _containerShiftIndex*viewportSize will give you amount of transform of the current slide
    this._containerShiftIndex = -1;
  }

  /**
   * Create and append array of three items
   * that hold data about slides in DOM
   */
  appendHolders() {
    this.itemHolders = [];

    // append our three slide holders -
    // previous, current, and next
    for (let i = 0; i < 3; i++) {
      const el = createElement('pswp__item', false, this.pswp.container);

      // hide nearby item holders until initial zoom animation finishes (to avoid extra Paints)
      el.style.display = (i === 1) ? 'block' : 'none';

      this.itemHolders.push({
        el,
        //index: -1
      });
    }
  }

  /**
   * Whether the main scroll can be horizontally swiped to the next or previous slide.
   */
  canBeSwiped() {
    return this.pswp.getNumItems() > 1;
  }

  /**
   * Move main scroll by X amount of slides.
   * For example:
   *   `-1` will move to the previous slide,
   *    `0` will reset the scroll position of the current slide,
   *    `3` will move three slides forward
   *
   * If loop option is enabled - index will be automatically looped too,
   * (for example `-1` will move to the last slide of the gallery).
   *
   * @param {number} diff
   * @param {boolean=} animate
   * @param {number=} velocityX
   * @returns {boolean} whether index was changed or not
   */
  moveIndexBy(diff, animate, velocityX) {
    const { pswp } = this;
    let newIndex = pswp.potentialIndex + diff;
    const numSlides = pswp.getNumItems();

    if (pswp.canLoop()) {
      newIndex = pswp.getLoopedIndex(newIndex);
      const distance = (diff + numSlides) % numSlides;
      if (distance <= numSlides / 2) {
        // go forward
        diff = distance;
      } else {
        // go backwards
        diff = distance - numSlides;
      }
    } else {
      if (newIndex < 0) {
        newIndex = 0;
      } else if (newIndex >= numSlides) {
        newIndex = numSlides - 1;
      }
      diff = newIndex - pswp.potentialIndex;
    }

    pswp.potentialIndex = newIndex;
    this._currPositionIndex -= diff;

    pswp.animations.stopMainScroll();

    const destinationX = this.getCurrSlideX();
    if (!animate) {
      this.moveTo(destinationX);
      this.updateCurrItem();
    } else {
      pswp.animations.startSpring({
        isMainScroll: true,
        start: this.x,
        end: destinationX,
        velocity: velocityX || 0,
        naturalFrequency: 30,
        dampingRatio: 1, //0.7,
        onUpdate: (x) => {
          this.moveTo(x);
        },
        onComplete: () => {
          this.updateCurrItem();
          pswp.appendHeavy();
        }
      });

      let currDiff = pswp.potentialIndex - pswp.currIndex;
      if (pswp.canLoop()) {
        const currDistance = (currDiff + numSlides) % numSlides;
        if (currDistance <= numSlides / 2) {
          // go forward
          currDiff = currDistance;
        } else {
          // go backwards
          currDiff = currDistance - numSlides;
        }
      }

      // Force-append new slides during transition
      // if difference between slides is more than 1
      if (Math.abs(currDiff) > 1) {
        this.updateCurrItem();
      }
    }

    if (diff) {
      return true;
    }
  }

  /**
   * X position of the main scroll for the current slide
   * (ignores position during dragging)
   */
  getCurrSlideX() {
    return this.slideWidth * this._currPositionIndex;
  }

  /**
   * Whether scroll position is shifted.
   * For example, it will return true if the scroll is being dragged or animated.
   */
  isShifted() {
    return this.x !== this.getCurrSlideX();
  }

  /**
   * Update slides X positions and set their content
   */
  updateCurrItem() {
    const { pswp } = this;
    const positionDifference = this._prevPositionIndex - this._currPositionIndex;

    if (!positionDifference) {
      return;
    }

    this._prevPositionIndex = this._currPositionIndex;

    pswp.currIndex = pswp.potentialIndex;

    let diffAbs = Math.abs(positionDifference);
    let tempHolder;

    if (diffAbs >= 3) {
      this._containerShiftIndex += positionDifference + (positionDifference > 0 ? -3 : 3);
      diffAbs = 3;
    }

    for (let i = 0; i < diffAbs; i++) {
      if (positionDifference > 0) {
        tempHolder = this.itemHolders.shift();
        this.itemHolders[2] = tempHolder; // move first to last

        this._containerShiftIndex++;

        setTransform(tempHolder.el, (this._containerShiftIndex + 2) * this.slideWidth);

        pswp.setContent(tempHolder, (pswp.currIndex - diffAbs) + i + 2);
      } else {
        tempHolder = this.itemHolders.pop();
        this.itemHolders.unshift(tempHolder); // move last to first

        this._containerShiftIndex--;

        setTransform(tempHolder.el, this._containerShiftIndex * this.slideWidth);

        pswp.setContent(tempHolder, (pswp.currIndex + diffAbs) - i - 2);
      }
    }

    // Reset transfrom every 50ish navigations in one direction.
    //
    // Otherwise transform will keep growing indefinitely,
    // which might cause issues as browsers have a maximum transform limit.
    // I wasn't able to reach it, but just to be safe.
    // This should not cause noticable lag.
    if (Math.abs(this._containerShiftIndex) > 50 && !this.isShifted()) {
      this.resetPosition();
      this.resize();
    }

    // Pan transition might be running (and consntantly updating pan position)
    pswp.animations.stopAllPan();

    this.itemHolders.forEach((itemHolder, i) => {
      if (itemHolder.slide) {
        // Slide in the 2nd holder is always active
        itemHolder.slide.setIsActive(i === 1);
      }
    });

    pswp.currSlide = this.itemHolders[1].slide;
    pswp.contentLoader.updateLazy(positionDifference);

    pswp.currSlide.applyCurrentZoomPan();
    pswp.dispatch('change');
  }

  /**
   * Move the X position of the main scroll container
   *
   * @param {number} x
   * @param {boolean=} dragging
   */
  moveTo(x, dragging) {
    /** @type {number} */
    let newSlideIndexOffset;
    /** @type {number} */
    let delta;

    if (!this.pswp.canLoop() && dragging) {
      // Apply friction
      newSlideIndexOffset = ((this.slideWidth * this._currPositionIndex) - x) / this.slideWidth;
      newSlideIndexOffset += this.pswp.currIndex;
      delta = Math.round(x - this.x);

      if ((newSlideIndexOffset < 0 && delta > 0)
          || (newSlideIndexOffset >= this.pswp.getNumItems() - 1 && delta < 0)) {
        x = this.x + (delta * MAIN_SCROLL_END_FRICTION);
      }
    }

    this.x = x;
    setTransform(this.pswp.container, x);

    this.pswp.dispatch('moveMainScroll', { x, dragging });
  }
}

export default MainScroll;
<commit_msg>Fix Cannot read properties of undefined (reading 'applyCurrentZoomPan')<commit_after>import {
  setTransform,
  createElement,
} from './util/util.js';

/** @typedef {import('./photoswipe.js').default} PhotoSwipe */
/** @typedef {import('./slide/slide.js').default} Slide */

/** @typedef {{ el: HTMLDivElement; slide?: Slide }} ItemHolder */

const MAIN_SCROLL_END_FRICTION = 0.35;

// const MIN_SWIPE_TRANSITION_DURATION = 250;
// const MAX_SWIPE_TRABSITION_DURATION = 500;
// const DEFAULT_SWIPE_TRANSITION_DURATION = 333;

/**
 * Handles movement of the main scrolling container
 * (for example, it repositions when user swipes left or right).
 *
 * Also stores its state.
 */
class MainScroll {
  /**
   * @param {PhotoSwipe} pswp
   */
  constructor(pswp) {
    this.pswp = pswp;
    this.x = 0;

    /** @type {number} */
    this.slideWidth = undefined;

    /** @type {ItemHolder[]} */
    this.itemHolders = undefined;

    this.resetPosition();
  }

  /**
   * Position the scroller and slide containers
   * according to viewport size.
   *
   * @param {boolean=} resizeSlides Whether slides content should resized
   */
  resize(resizeSlides) {
    const { pswp } = this;
    const newSlideWidth = Math.round(
      pswp.viewportSize.x + pswp.viewportSize.x * pswp.options.spacing
    );
    // Mobile browsers might trigger a resize event during a gesture.
    // (due to toolbar appearing or hiding).
    // Avoid re-adjusting main scroll position if width wasn't changed
    const slideWidthChanged = (newSlideWidth !== this.slideWidth);

    if (slideWidthChanged) {
      this.slideWidth = newSlideWidth;
      this.moveTo(this.getCurrSlideX());
    }

    this.itemHolders.forEach((itemHolder, index) => {
      if (slideWidthChanged) {
        setTransform(itemHolder.el, (index + this._containerShiftIndex)
                                    * this.slideWidth);
      }

      if (resizeSlides && itemHolder.slide) {
        itemHolder.slide.resize();
      }
    });
  }

  /**
   * Reset X position of the main scroller to zero
   */
  resetPosition() {
    // Position on the main scroller (offset)
    // it is independent from slide index
    this._currPositionIndex = 0;
    this._prevPositionIndex = 0;

    // This will force recalculation of size on next resize()
    this.slideWidth = 0;

    // _containerShiftIndex*viewportSize will give you amount of transform of the current slide
    this._containerShiftIndex = -1;
  }

  /**
   * Create and append array of three items
   * that hold data about slides in DOM
   */
  appendHolders() {
    this.itemHolders = [];

    // append our three slide holders -
    // previous, current, and next
    for (let i = 0; i < 3; i++) {
      const el = createElement('pswp__item', false, this.pswp.container);

      // hide nearby item holders until initial zoom animation finishes (to avoid extra Paints)
      el.style.display = (i === 1) ? 'block' : 'none';

      this.itemHolders.push({
        el,
        //index: -1
      });
    }
  }

  /**
   * Whether the main scroll can be horizontally swiped to the next or previous slide.
   */
  canBeSwiped() {
    return this.pswp.getNumItems() > 1;
  }

  /**
   * Move main scroll by X amount of slides.
   * For example:
   *   `-1` will move to the previous slide,
   *    `0` will reset the scroll position of the current slide,
   *    `3` will move three slides forward
   *
   * If loop option is enabled - index will be automatically looped too,
   * (for example `-1` will move to the last slide of the gallery).
   *
   * @param {number} diff
   * @param {boolean=} animate
   * @param {number=} velocityX
   * @returns {boolean} whether index was changed or not
   */
  moveIndexBy(diff, animate, velocityX) {
    const { pswp } = this;
    let newIndex = pswp.potentialIndex + diff;
    const numSlides = pswp.getNumItems();

    if (pswp.canLoop()) {
      newIndex = pswp.getLoopedIndex(newIndex);
      const distance = (diff + numSlides) % numSlides;
      if (distance <= numSlides / 2) {
        // go forward
        diff = distance;
      } else {
        // go backwards
        diff = distance - numSlides;
      }
    } else {
      if (newIndex < 0) {
        newIndex = 0;
      } else if (newIndex >= numSlides) {
        newIndex = numSlides - 1;
      }
      diff = newIndex - pswp.potentialIndex;
    }

    pswp.potentialIndex = newIndex;
    this._currPositionIndex -= diff;

    pswp.animations.stopMainScroll();

    const destinationX = this.getCurrSlideX();
    if (!animate) {
      this.moveTo(destinationX);
      this.updateCurrItem();
    } else {
      pswp.animations.startSpring({
        isMainScroll: true,
        start: this.x,
        end: destinationX,
        velocity: velocityX || 0,
        naturalFrequency: 30,
        dampingRatio: 1, //0.7,
        onUpdate: (x) => {
          this.moveTo(x);
        },
        onComplete: () => {
          this.updateCurrItem();
          pswp.appendHeavy();
        }
      });

      let currDiff = pswp.potentialIndex - pswp.currIndex;
      if (pswp.canLoop()) {
        const currDistance = (currDiff + numSlides) % numSlides;
        if (currDistance <= numSlides / 2) {
          // go forward
          currDiff = currDistance;
        } else {
          // go backwards
          currDiff = currDistance - numSlides;
        }
      }

      // Force-append new slides during transition
      // if difference between slides is more than 1
      if (Math.abs(currDiff) > 1) {
        this.updateCurrItem();
      }
    }

    if (diff) {
      return true;
    }
  }

  /**
   * X position of the main scroll for the current slide
   * (ignores position during dragging)
   */
  getCurrSlideX() {
    return this.slideWidth * this._currPositionIndex;
  }

  /**
   * Whether scroll position is shifted.
   * For example, it will return true if the scroll is being dragged or animated.
   */
  isShifted() {
    return this.x !== this.getCurrSlideX();
  }

  /**
   * Update slides X positions and set their content
   */
  updateCurrItem() {
    const { pswp } = this;
    const positionDifference = this._prevPositionIndex - this._currPositionIndex;

    if (!positionDifference) {
      return;
    }

    this._prevPositionIndex = this._currPositionIndex;

    pswp.currIndex = pswp.potentialIndex;

    let diffAbs = Math.abs(positionDifference);
    let tempHolder;

    if (diffAbs >= 3) {
      this._containerShiftIndex += positionDifference + (positionDifference > 0 ? -3 : 3);
      diffAbs = 3;
    }

    for (let i = 0; i < diffAbs; i++) {
      if (positionDifference > 0) {
        tempHolder = this.itemHolders.shift();
        this.itemHolders[2] = tempHolder; // move first to last

        this._containerShiftIndex++;

        setTransform(tempHolder.el, (this._containerShiftIndex + 2) * this.slideWidth);

        pswp.setContent(tempHolder, (pswp.currIndex - diffAbs) + i + 2);
      } else {
        tempHolder = this.itemHolders.pop();
        this.itemHolders.unshift(tempHolder); // move last to first

        this._containerShiftIndex--;

        setTransform(tempHolder.el, this._containerShiftIndex * this.slideWidth);

        pswp.setContent(tempHolder, (pswp.currIndex + diffAbs) - i - 2);
      }
    }

    // Reset transfrom every 50ish navigations in one direction.
    //
    // Otherwise transform will keep growing indefinitely,
    // which might cause issues as browsers have a maximum transform limit.
    // I wasn't able to reach it, but just to be safe.
    // This should not cause noticable lag.
    if (Math.abs(this._containerShiftIndex) > 50 && !this.isShifted()) {
      this.resetPosition();
      this.resize();
    }

    // Pan transition might be running (and consntantly updating pan position)
    pswp.animations.stopAllPan();

    this.itemHolders.forEach((itemHolder, i) => {
      if (itemHolder.slide) {
        // Slide in the 2nd holder is always active
        itemHolder.slide.setIsActive(i === 1);
      }
    });

    pswp.currSlide = this.itemHolders[1].slide;
    pswp.contentLoader.updateLazy(positionDifference);

    if (pswp.currSlide) {
      pswp.currSlide.applyCurrentZoomPan();
    }

    pswp.dispatch('change');
  }

  /**
   * Move the X position of the main scroll container
   *
   * @param {number} x
   * @param {boolean=} dragging
   */
  moveTo(x, dragging) {
    /** @type {number} */
    let newSlideIndexOffset;
    /** @type {number} */
    let delta;

    if (!this.pswp.canLoop() && dragging) {
      // Apply friction
      newSlideIndexOffset = ((this.slideWidth * this._currPositionIndex) - x) / this.slideWidth;
      newSlideIndexOffset += this.pswp.currIndex;
      delta = Math.round(x - this.x);

      if ((newSlideIndexOffset < 0 && delta > 0)
          || (newSlideIndexOffset >= this.pswp.getNumItems() - 1 && delta < 0)) {
        x = this.x + (delta * MAIN_SCROLL_END_FRICTION);
      }
    }

    this.x = x;
    setTransform(this.pswp.container, x);

    this.pswp.dispatch('moveMainScroll', { x, dragging });
  }
}

export default MainScroll;
<|endoftext|>


<commit_before>(function () {
  var ns = $.namespace('pskl.model');
  var __idCounter = 0;
  ns.Frame = function (width, height) {
    if (width && height) {
      this.width = width;
      this.height = height;
      this.id = __idCounter++;
      this.version = 0;
      this.pixels = ns.Frame.createEmptyPixelGrid_(width, height);
      this.stateIndex = 0;
    } else {
      throw 'Bad arguments in pskl.model.Frame constructor : ' + width + ', ' + height;
    }
  };

  ns.Frame.fromPixelGrid = function (pixels, width, height) {
    if (pixels.length) {
      if (pixels[0].length) {
        var w = pixels.length;
        var h = pixels[0].length;
        var buffer = [];
        for (var y = 0; y < h; y++) {
          for (var x = 0; x < w; x++ ) {
            if (typeof pixels[x][y] == 'string') {
              buffer[y * w + x] = pskl.utils.colorToInt(pixels[x][y]);
            } else {
              buffer[y * w + x] = pixels[x][y];
            }
          }
        }
      } else if (width && height) {
        var w = width;
        var h = height;
        buffer = pixels;
      } else {
        throw 'Bad arguments in pskl.model.frame.fromPixelGrid, missing width and height';
      }

      var frame = new pskl.model.Frame(w, h);
      frame.setPixels(buffer);
      return frame;
    } else {
      throw 'Bad arguments in pskl.model.Frame.fromPixelGrid';
    }
  };

  var _emptyPixelGridCache = {};
  ns.Frame.createEmptyPixelGrid_ = function (width, height) {
    var pixels;
    var key = width+"-"+height;
    if (_emptyPixelGridCache[key]) {
      pixels = _emptyPixelGridCache[key];
    } else {
      pixels = _emptyPixelGridCache[key] = new Uint32Array(width * height);
      var transparentColorInt = pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR);
      if (!pixels.fill) { // PhantomJS ='(
        for (var i = 0; i < width * height; i++) {
          pixels[i] = transparentColorInt;
        }
      } else {
        pixels.fill(transparentColorInt);
      }
    }

    return new Uint32Array(pixels);
  };

  ns.Frame.createEmptyFromFrame = function (frame) {
    return new ns.Frame(frame.getWidth(), frame.getHeight());
  };

  ns.Frame.prototype.clone = function () {
    var clone = new ns.Frame(this.width, this.height);
    clone.setPixels(this.pixels);
    return clone;
  };

  /**
   * Returns a copy of the pixels used by the frame
   */
  ns.Frame.prototype.getPixels = function () {
    return this.clonePixels_(this.pixels);
  };

  /**
   * Copies the passed pixels into the frame.
   */
  ns.Frame.prototype.setPixels = function (pixels) {
    this.pixels = this.clonePixels_(pixels);
    this.version++;
  };

  ns.Frame.prototype.clear = function () {
    this.pixels = ns.Frame.createEmptyPixelGrid_(this.getWidth(), this.getHeight());
    this.version++;
  };

  /**
   * Clone a set of pixels. Should be static utility method
   * @private
   */
  ns.Frame.prototype.clonePixels_ = function (pixels) {
    return new Uint32Array(pixels);
  };

  ns.Frame.prototype.getHash = function () {
    return [this.id, this.version].join('-');
  };

  ns.Frame.prototype.setPixel = function (x, y, color) {
    if (this.containsPixel(x, y)) {
      var index = y * this.width + x;
      var p = this.pixels[index];
      if (typeof color === 'string') {
        color = pskl.utils.colorToInt(color);
      }

      if (p !== color) {
        this.pixels[index] = color || pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR);
        this.version++;
      }
    }
  };

  ns.Frame.prototype.getPixel = function (x, y) {
    if (this.containsPixel(x, y)) {
      return this.pixels[y * this.width + x];
    } else {
      return null;
    }
  };

  ns.Frame.prototype.forEachPixel = function (callback) {
    var width = this.getWidth();
    var height = this.getHeight();
    for (var x = 0 ; x < width ; x++) {
      for (var y = 0 ; y < height ; y++) {
        callback(this.pixels[y * this.width + x], x, y, this);
      }
    }
  };

  ns.Frame.prototype.getWidth = function () {
    return this.width;
  };

  ns.Frame.prototype.getHeight = function () {
    return this.height;
  };

  ns.Frame.prototype.containsPixel = function (col, row) {
    return col >= 0 && row >= 0 && col < this.width && row < this.height;
  };

  ns.Frame.prototype.isSameSize = function (otherFrame) {
    return this.getHeight() == otherFrame.getHeight() && this.getWidth() == otherFrame.getWidth();
  };
})();
<commit_msg>Remove nested loop<commit_after>(function () {
  var ns = $.namespace('pskl.model');
  var __idCounter = 0;
  ns.Frame = function (width, height) {
    if (width && height) {
      this.width = width;
      this.height = height;
      this.id = __idCounter++;
      this.version = 0;
      this.pixels = ns.Frame.createEmptyPixelGrid_(width, height);
      this.stateIndex = 0;
    } else {
      throw 'Bad arguments in pskl.model.Frame constructor : ' + width + ', ' + height;
    }
  };

  ns.Frame.fromPixelGrid = function (pixels, width, height) {
    if (pixels.length) {
      if (pixels[0].length) {
        var w = pixels.length;
        var h = pixels[0].length;
        var buffer = [];
        for (var y = 0; y < h; y++) {
          for (var x = 0; x < w; x++ ) {
            if (typeof pixels[x][y] == 'string') {
              buffer[y * w + x] = pskl.utils.colorToInt(pixels[x][y]);
            } else {
              buffer[y * w + x] = pixels[x][y];
            }
          }
        }
      } else if (width && height) {
        var w = width;
        var h = height;
        buffer = pixels;
      } else {
        throw 'Bad arguments in pskl.model.frame.fromPixelGrid, missing width and height';
      }

      var frame = new pskl.model.Frame(w, h);
      frame.setPixels(buffer);
      return frame;
    } else {
      throw 'Bad arguments in pskl.model.Frame.fromPixelGrid';
    }
  };

  var _emptyPixelGridCache = {};
  ns.Frame.createEmptyPixelGrid_ = function (width, height) {
    var pixels;
    var key = width+"-"+height;
    if (_emptyPixelGridCache[key]) {
      pixels = _emptyPixelGridCache[key];
    } else {
      pixels = _emptyPixelGridCache[key] = new Uint32Array(width * height);
      var transparentColorInt = pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR);
      if (!pixels.fill) { // PhantomJS ='(
        for (var i = 0; i < width * height; i++) {
          pixels[i] = transparentColorInt;
        }
      } else {
        pixels.fill(transparentColorInt);
      }
    }

    return new Uint32Array(pixels);
  };

  ns.Frame.createEmptyFromFrame = function (frame) {
    return new ns.Frame(frame.getWidth(), frame.getHeight());
  };

  ns.Frame.prototype.clone = function () {
    var clone = new ns.Frame(this.width, this.height);
    clone.setPixels(this.pixels);
    return clone;
  };

  /**
   * Returns a copy of the pixels used by the frame
   */
  ns.Frame.prototype.getPixels = function () {
    return this.clonePixels_(this.pixels);
  };

  /**
   * Copies the passed pixels into the frame.
   */
  ns.Frame.prototype.setPixels = function (pixels) {
    this.pixels = this.clonePixels_(pixels);
    this.version++;
  };

  ns.Frame.prototype.clear = function () {
    this.pixels = ns.Frame.createEmptyPixelGrid_(this.getWidth(), this.getHeight());
    this.version++;
  };

  /**
   * Clone a set of pixels. Should be static utility method
   * @private
   */
  ns.Frame.prototype.clonePixels_ = function (pixels) {
    return new Uint32Array(pixels);
  };

  ns.Frame.prototype.getHash = function () {
    return [this.id, this.version].join('-');
  };

  ns.Frame.prototype.setPixel = function (x, y, color) {
    if (this.containsPixel(x, y)) {
      var index = y * this.width + x;
      var p = this.pixels[index];
      if (typeof color === 'string') {
        color = pskl.utils.colorToInt(color);
      }

      if (p !== color) {
        this.pixels[index] = color || pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR);
        this.version++;
      }
    }
  };

  ns.Frame.prototype.getPixel = function (x, y) {
    if (this.containsPixel(x, y)) {
      return this.pixels[y * this.width + x];
    } else {
      return null;
    }
  };

  ns.Frame.prototype.forEachPixel = function (callback) {
    var width = this.getWidth();
    var height = this.getHeight();
    var length = width * height;
    for (var i = 0; i < length ; ++i) {
      callback(this.pixels[i], i % width, Math.floor(i / width), this);
    }
  };

  ns.Frame.prototype.getWidth = function () {
    return this.width;
  };

  ns.Frame.prototype.getHeight = function () {
    return this.height;
  };

  ns.Frame.prototype.containsPixel = function (col, row) {
    return col >= 0 && row >= 0 && col < this.width && row < this.height;
  };

  ns.Frame.prototype.isSameSize = function (otherFrame) {
    return this.getHeight() == otherFrame.getHeight() && this.getWidth() == otherFrame.getWidth();
  };
})();
<|endoftext|>


<commit_before>import Item from './Item';

module.exports = class Job extends Item {
  constructor(options = {}) {
    super(...arguments);

    const id = this.getId();
    if (id !== '/' && (!id.startsWith('/') || id.endsWith('/'))) {
      throw new Error(`Id (${id}) must start with a leading slash ("/") and should not end with a slash, except for root id which is only a slash`);
    }
  }

  getId() {
    return this.get('id') || '/';
  }

  getName() {
    return this.getId().split('/').pop();
  }

};
<commit_msg>Removing unused `options`<commit_after>import Item from './Item';

module.exports = class Job extends Item {
  constructor() {
    super(...arguments);

    const id = this.getId();
    if (id !== '/' && (!id.startsWith('/') || id.endsWith('/'))) {
      throw new Error(`Id (${id}) must start with a leading slash ("/") and should not end with a slash, except for root id which is only a slash`);
    }
  }

  getId() {
    return this.get('id') || '/';
  }

  getName() {
    return this.getId().split('/').pop();
  }

};
<|endoftext|>


<commit_before>/* api
 *
 */

var api = (function(hyperaudio) {

	return {
		init: function(options) {
			this.options = hyperaudio.extend({
				protocol: 'http://',
				org: '', // The organisations namespace / sub-domain. EG. 'chattanooga.'
				api: 'api.hyperaud.io/v1/',
				// Command syntax
				transcripts: 'transcripts/',
				mixes: 'mixes/',
				channels: 'channels/',
				signin: 'login/',
				whoami: 'whoami/',
				// Specific user (bgm) for music
				bgm: 'bgm/media/'
			}, options);

			// The base url of the API
			this.url = null;
			this._updateInternals();

			// API State
			this.error = false;

			// User Properties
			this.guest = false; // False to force 1st call
			this.username = ''; // Falsey to force 1st call

			// Stored requested data
			this.transcripts = null;
			this.transcript = null;
			this.mixes = null;
			this.mix = null;
			this.bgm = null;
		},
		option: function(options, value) {
			if(typeof options === 'string') { // Enable option to be set/get by name.
				if(typeof value !== 'undefined') {
					this.options[options] = value;
				} else {
					return this.options[options];
				}
			} else if(typeof options === 'object') { // Enable options to be set/get by object.
				hyperaudio.extend(this.options, options);
			} else {
				return hyperaudio.extend({}, this.options); // Return a copy of the options object.
			}
			this._updateInternals();
		},
		_updateInternals: function() {
			this.url = this.options.protocol + this.options.org + this.options.api;
		},
		callback: function(callback, success) {
			if(typeof callback === 'function') {
				callback.call(this, success);
			}
		},
		signin: function(auth, callback) {
			var self = this;
			// auth = {username,password}
			xhr({
				url: this.url + this.options.signin,
				type: 'POST',
				data: JSON.stringify(auth),
				complete: function(event) {
					var json = JSON.parse(this.responseText);
					self.guest = !json.user;
					if(!self.guest) {
						self.username = json.user;

						hyperaudio.gaEvent({
							type: 'API',
							action: 'login: User signed in'
						});

						self.callback(callback, true);
					} else {
						self.username = '';
						self.callback(callback, false);
					}
				},
				error: function(event) {
					self.error = true;
					self.callback(callback, false);
				}
			});
		},
		getUsername: function(callback, force) {
			var self = this;

			// force = typeof force === 'undefined' ? true : force; // default force = true.

			if(!force && (this.guest || this.username)) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				xhr({
					url: this.url + this.options.whoami,
					complete: function(event) {
						var json = JSON.parse(this.responseText);
						self.guest = !json.user;
						if(!self.guest) {
							self.username = json.user;
						} else {
							self.username = '';
						}
						self.callback(callback, true);
					},
					error: function(event) {
						self.error = true;
						self.callback(callback, false);
					}
				});
			}
		},
		getTranscripts: function(callback, force) {
			var self = this;
			if(!force && this.transcripts) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				xhr({
					// In future may want a version that returns only your own transcripts.
					// url: self.url + (self.guest ? '' : self.username + '/') + self.options.transcripts,
					url: this.url + this.options.transcripts,
					complete: function(event) {
						var json = JSON.parse(this.responseText);
						self.transcripts = json;
						self.callback(callback, true);
					},
					error: function(event) {
						self.error = true;
						self.callback(callback, false);
					}
				});
			}
		},
		getTranscript: function(id, callback, force) {
			var self = this;
			if(!force && this.transcript && this.transcript._id === id) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				// Do not need to get username for an ID specific request.
				this.getUsername(function(success) {
					if(success && id) {
						xhr({
							// url: self.url + (self.guest ? '' : self.username + '/') + self.options.transcripts + id,
							url: self.url + self.options.transcripts + id,
							complete: function(event) {
								var json = JSON.parse(this.responseText);
								self.transcript = json;
								self.callback(callback, true);
							},
							error: function(event) {
								self.error = true;
								self.callback(callback, false);
							}
						});
					} else {
						self.error = true; // Setting the common error prop is redundant, since it would have been set in getUsername failure.
						self.callback(callback, false);
					}
				});
			}
		},
		getMixes: function(callback, force) {
			var self = this;
			if(!force && this.mixes) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				// Do not need to get username for a general request.
				this.getUsername(function(success) {
					if(success) {
						xhr({
							url: self.url + (self.guest ? '' : self.username + '/') + self.options.mixes,
							complete: function(event) {
								var json = JSON.parse(this.responseText);
								self.mixes = json;
								self.callback(callback, true);
							},
							error: function(event) {
								self.error = true;
								self.callback(callback, false);
							}
						});
					} else {
						self.error = true; // Setting the common error prop is redundant, since it would have been set in getUsername failure.
						self.callback(callback, false);
					}
				});
			}
		},
		getMix: function(id, callback, force) {
			var self = this;
			if(!force && this.mix && this.mix._id === id) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				// Do not need to get username for an ID specific request.
				this.getUsername(function(success) {
					if(success && id) {
						xhr({
							url: this.url + (this.guest ? '' : this.username + '/') + this.options.mixes + id,
							complete: function(event) {
								var json = JSON.parse(this.responseText);
								self.mix = json;
								self.callback(callback, true);
							},
							error: function(event) {
								self.error = true;
								self.callback(callback, false);
							}
						});
					} else {
						self.error = true; // Setting the common error prop is redundant, since it would have been set in getUsername failure.
						self.callback(callback, false);
					}
				});
			}
		},
		putMix: function(mix, callback) {
			var self = this;

			// Are we storing the current Mix we're editing in here?
			// Yes, but only refreshing the mix data here on Load and Save.
			// The current mix data will be in the stage's HTML.

			if(typeof mix === 'object') {
				var type = 'POST',
					id = '';

				this.getUsername(function(success) {

					if(success && !self.guest && self.username) {

						// Check: Mix IDs match and user is owner.

						if(self.mix && self.mix._id && self.mix._id === mix._id && self.username === mix.owner) {
							type = 'PUT';
							id = self.mix._id;
							// Check some stuff?
						} else {
							// Check some stuff?
						}

						xhr({
							url: self.url + self.username + '/' + self.options.mixes + id,
							type: type,
							data: JSON.stringify(mix),
							complete: function(event) {
								var json = JSON.parse(this.responseText);
								self.mix = json;
								self.callback(callback, {
									saved: true
								});
							},
							error: function(event) {
								self.error = true;
								self.callback(callback, false);
							}
						});
					} else if(success) {
						// The user needs to login
						self.callback(callback, {
							needLogin: true
						});
					} else {
						self.callback(callback, false);
					}
				}, true); // Force the call to get username before attempting to save.
			} else {
				setTimeout(function() {
					self.callback(callback, false);
				}, 0);
			}
		},
		getBGM: function(callback, force) {
			var self = this;
			if(!force && this.bgm) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				xhr({
					url: this.url + this.options.bgm,
					complete: function(event) {
						var json = JSON.parse(this.responseText);
						self.bgm = json;
						self.callback(callback, true);
					},
					error: function(event) {
						self.error = true;
						self.callback(callback, false);
					}
				});
			}
		}
	};

}(hyperaudio));
<commit_msg>Started changing API for channels, but WIP due to no support atm.<commit_after>/* api
 *
 */

var api = (function(hyperaudio) {

	return {
		init: function(options) {
			this.options = hyperaudio.extend({
				protocol: 'http://',
				org: '', // The organisations namespace / sub-domain. EG. 'chattanooga.'
				api: 'api.hyperaud.io/v1/',
				// Command syntax
				transcripts: 'transcripts/',
				mixes: 'mixes/',
				channels: 'channels/',
				signin: 'login/',
				whoami: 'whoami/',
				// Specific user (bgm) for music
				bgm: 'bgm/media/'
			}, options);

			// The base url of the API
			this.url = null;
			this._updateInternals();

			// API State
			this.error = false;

			// User Properties
			this.guest = false; // False to force 1st call
			this.username = ''; // Falsey to force 1st call

			// Stored requested data
			this.transcripts = null;
			this.transcript = null;
			this.mixes = null;
			this.mix = null;
			this.bgm = null;

			this.channels = null;
		},
		option: function(options, value) {
			if(typeof options === 'string') { // Enable option to be set/get by name.
				if(typeof value !== 'undefined') {
					this.options[options] = value;
				} else {
					return this.options[options];
				}
			} else if(typeof options === 'object') { // Enable options to be set/get by object.
				hyperaudio.extend(this.options, options);
			} else {
				return hyperaudio.extend({}, this.options); // Return a copy of the options object.
			}
			this._updateInternals();
		},
		_updateInternals: function() {
			this.url = this.options.protocol + this.options.org + this.options.api;
		},
		callback: function(callback, success) {
			if(typeof callback === 'function') {
				callback.call(this, success);
			}
		},
		signin: function(auth, callback) {
			var self = this;
			// auth = {username,password}
			xhr({
				url: this.url + this.options.signin,
				type: 'POST',
				data: JSON.stringify(auth),
				complete: function(event) {
					var json = JSON.parse(this.responseText);
					self.guest = !json.user;
					if(!self.guest) {
						self.username = json.user;

						hyperaudio.gaEvent({
							type: 'API',
							action: 'login: User signed in'
						});

						self.callback(callback, true);
					} else {
						self.username = '';
						self.callback(callback, false);
					}
				},
				error: function(event) {
					self.error = true;
					self.callback(callback, false);
				}
			});
		},
		getUsername: function(callback, force) {
			var self = this;

			// force = typeof force === 'undefined' ? true : force; // default force = true.

			if(!force && (this.guest || this.username)) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				xhr({
					url: this.url + this.options.whoami,
					complete: function(event) {
						var json = JSON.parse(this.responseText);
						self.guest = !json.user;
						if(!self.guest) {
							self.username = json.user;
						} else {
							self.username = '';
						}
						self.callback(callback, true);
					},
					error: function(event) {
						self.error = true;
						self.callback(callback, false);
					}
				});
			}
		},
		getTranscripts_WIP: function(options) {
			var self = this,
				url, getUsername, setUrl, getTranscripts;

			options = hyperaudio.extend({
				user: false, // When true, the api returns the current user's transcripts.
				channel: '', // The channel name. Empty string disables feature. See 'nochannel' for media without any channel.
				callback: null
			}, options);

			getUsername = function() {
				self.getUsername(function(success) {
					if(success && !self.guest) {
						getTranscripts();
					} else {
						self.callback(options.callback, false);
					}
				});
			};

			setUrl = function() {
				url = this.url;
				if(options.user) {
					url += self.username;
				}
				if(options.user) {
					url += self.username;
				}
			};

			getTranscripts = function() {
				xhr({
					// In future may want a version that returns only your own transcripts.
					// url: self.url + (self.guest ? '' : self.username + '/') + self.options.transcripts,
					url: url + self.options.transcripts,
					complete: function(event) {
						var json = JSON.parse(this.responseText);
						// self.transcripts = json;
						self.callback(options.callback, json);
					},
					error: function(event) {
						self.error = true;
						self.callback(options.callback, false);
					}
				});
			};

		},
		getTranscripts: function(callback, force) {
			var self = this;
			if(!force && this.transcripts) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				xhr({
					// In future may want a version that returns only your own transcripts.
					// url: self.url + (self.guest ? '' : self.username + '/') + self.options.transcripts,
					url: this.url + this.options.transcripts,
					complete: function(event) {
						var json = JSON.parse(this.responseText);
						self.transcripts = json;
						self.callback(callback, true);
					},
					error: function(event) {
						self.error = true;
						self.callback(callback, false);
					}
				});
			}
		},
		getTranscript: function(id, callback, force) {
			var self = this;
			if(!force && this.transcript && this.transcript._id === id) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				// Do not need to get username for an ID specific request.
				this.getUsername(function(success) {
					if(success && id) {
						xhr({
							// url: self.url + (self.guest ? '' : self.username + '/') + self.options.transcripts + id,
							url: self.url + self.options.transcripts + id,
							complete: function(event) {
								var json = JSON.parse(this.responseText);
								self.transcript = json;
								self.callback(callback, true);
							},
							error: function(event) {
								self.error = true;
								self.callback(callback, false);
							}
						});
					} else {
						self.error = true; // Setting the common error prop is redundant, since it would have been set in getUsername failure.
						self.callback(callback, false);
					}
				});
			}
		},
		getMixes: function(callback, force) {
			var self = this;
			if(!force && this.mixes) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				// Do not need to get username for a general request.
				this.getUsername(function(success) {
					if(success) {
						xhr({
							url: self.url + (self.guest ? '' : self.username + '/') + self.options.mixes,
							complete: function(event) {
								var json = JSON.parse(this.responseText);
								self.mixes = json;
								self.callback(callback, true);
							},
							error: function(event) {
								self.error = true;
								self.callback(callback, false);
							}
						});
					} else {
						self.error = true; // Setting the common error prop is redundant, since it would have been set in getUsername failure.
						self.callback(callback, false);
					}
				});
			}
		},
		getMix: function(id, callback, force) {
			var self = this;
			if(!force && this.mix && this.mix._id === id) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				// Do not need to get username for an ID specific request.
				this.getUsername(function(success) {
					if(success && id) {
						xhr({
							url: this.url + (this.guest ? '' : this.username + '/') + this.options.mixes + id,
							complete: function(event) {
								var json = JSON.parse(this.responseText);
								self.mix = json;
								self.callback(callback, true);
							},
							error: function(event) {
								self.error = true;
								self.callback(callback, false);
							}
						});
					} else {
						self.error = true; // Setting the common error prop is redundant, since it would have been set in getUsername failure.
						self.callback(callback, false);
					}
				});
			}
		},
		putMix: function(mix, callback) {
			var self = this;

			// Are we storing the current Mix we're editing in here?
			// Yes, but only refreshing the mix data here on Load and Save.
			// The current mix data will be in the stage's HTML.

			if(typeof mix === 'object') {
				var type = 'POST',
					id = '';

				this.getUsername(function(success) {

					if(success && !self.guest && self.username) {

						// Check: Mix IDs match and user is owner.

						if(self.mix && self.mix._id && self.mix._id === mix._id && self.username === mix.owner) {
							type = 'PUT';
							id = self.mix._id;
							// Check some stuff?
						} else {
							// Check some stuff?
						}

						xhr({
							url: self.url + self.username + '/' + self.options.mixes + id,
							type: type,
							data: JSON.stringify(mix),
							complete: function(event) {
								var json = JSON.parse(this.responseText);
								self.mix = json;
								self.callback(callback, {
									saved: true
								});
							},
							error: function(event) {
								self.error = true;
								self.callback(callback, false);
							}
						});
					} else if(success) {
						// The user needs to login
						self.callback(callback, {
							needLogin: true
						});
					} else {
						self.callback(callback, false);
					}
				}, true); // Force the call to get username before attempting to save.
			} else {
				setTimeout(function() {
					self.callback(callback, false);
				}, 0);
			}
		},
		getBGM: function(callback, force) {
			var self = this;
			if(!force && this.bgm) {
				setTimeout(function() {
					self.callback(callback, true);
				}, 0);
			} else {
				xhr({
					url: this.url + this.options.bgm,
					complete: function(event) {
						var json = JSON.parse(this.responseText);
						self.bgm = json;
						self.callback(callback, true);
					},
					error: function(event) {
						self.error = true;
						self.callback(callback, false);
					}
				});
			}
		}
	};

}(hyperaudio));
<|endoftext|>


<commit_before>export default function(items, props) {
  const { columnWidth, itemHeight = 150, columns,
    gutterWidth, gutterHeight } = props;

  const positions = items.map((itemProps, i) => {
    const column = i % columns;
    const row = Math.floor(i / columns);

    const x = (column * columnWidth) + (column * gutterWidth);
    const y = (row * itemHeight) + (row * gutterHeight);

    return [x, y];
  });

  const gridWidth = (columns * columnWidth) + ((columns - 1) * gutterWidth);
  const gridHeight = (Math.ceil(items.length / columns) *
    (itemHeight + gutterHeight)) - gutterHeight;

  return { positions, gridWidth, gridHeight };
}
<commit_msg>add prop-types package<commit_after>
export default function(items, props) {
  const { columnWidth, itemHeight = 150, columns,
    gutterWidth, gutterHeight } = props;

  const positions = items.map((itemProps, i) => {
    const column = i % columns;
    const row = Math.floor(i / columns);

    const x = (column * columnWidth) + (column * gutterWidth);
    const y = (row * itemHeight) + (row * gutterHeight);

    return [x, y];
  });

  const gridWidth = (columns * columnWidth) + ((columns - 1) * gutterWidth);
  const gridHeight = (Math.ceil(items.length / columns) *
    (itemHeight + gutterHeight)) - gutterHeight;

  return { positions, gridWidth, gridHeight };
}
<|endoftext|>


<commit_before>L.Marker.Handle = L.Marker.extend({
  options: {
    color: 'red',
    draggable: true,
    type: 'vector',
  },

  initialize: function(pos, options) {
    L.Marker.prototype.initialize.call(this, pos, options);
    L.Util.setOptions(this, options);

    var type = options.type;
    var icon = L.divIcon({
      className: 'leaflet-angkot-handle leaflet-angkot-handle-' + type,
      iconSize: new L.Point(10, 10),
    });
    this.options.icon = icon;
  },

  onAdd: function(map) {
    L.Marker.prototype.onAdd.apply(this, arguments);
    this._icon.style.borderColor = this.options.color;
  },

  setColor: function(color) {
    this.options.color = color;
    if (this._icon) {
      this._icon.style.borderColor = this.options.color;
    }
  },
});

L.Polyline.Colorable = L.Polyline.extend({
  setColor: function(color) {
    this.options.color = color;
    this._updateStyle();
  },
});

L.Polyline.Editable = L.Polyline.Colorable.extend({
  options: {
    editable: false,
  },

  initialize: function(path, options) {
    L.Polyline.Colorable.prototype.initialize.call(this, path, options);
    L.Util.setOptions(this, options);

    this._handles = [];
    this._handleGroup = new L.LayerGroup();

    var opacity = Math.min(this.options.opacity/2.0, 0.4);
    this._shadow = new L.Polyline.Colorable([], {
      opacity: opacity,
      weight: this.options.weight,
      color: this.options.color,
    });

    this._resetHandles();
  },

  onAdd: function(map) {
    L.Polyline.Colorable.prototype.onAdd.apply(this, arguments);

    this._updateHandlePositions(map);
    if (this.options.editable) {
      this._shadow.addTo(map);
      this._handleGroup.addTo(map);
    }
  },

  onRemove: function(map) {
    L.Polyline.Colorable.prototype.onRemove.apply(this, arguments);

    map.removeLayer(this._handleGroup);
    map.removeLayer(this._shadow);
  },

  setColor: function(color) {
    L.Polyline.Colorable.prototype.setColor.apply(this, arguments);
    for (var i=0; i<this._handles.length; i++) {
      this._handles[i].setColor(color);
    }
    this._shadow.setColor(color);
  },

  addLatLng: function(latlng) {
    L.Polyline.Colorable.prototype.addLatLng.apply(this, arguments);
    if (this.options.editable) this._addHandle(latlng);
  },

  spliceLatLngs: function() {
    L.Polyline.Colorable.prototype.spliceLatLngs.apply(this, arguments);
    this._resetHandles();
  },

  setEditable: function(editable) {
    this.options.editable = editable;
    if (this._map) {
      if (editable) {
        this._shadow.addTo(map);
        this._handleGroup.addTo(map);
      }
      else {
        this._map.removeLayer(this._shadow);
        this._map.removeLayer(this._handleGroup);
      }
    }
    this._resetHandles();
  },

  _resetHandles: function() {
    for (var i=0; i<this._handles.length; i++) {
      this._handleGroup.removeLayer(this._handles[i]);
    }
    this._handles.splice(0, this._handles.length);

    if (!this.options.editable) return;

    for (var i=0; i<this._latlngs.length; i++) {
      this._addHandle(this._latlngs[i]);
    }
  },

  _addHandle: function(latlng) {
    var edge, vertex;
    if (this._handles.length > 0) {
      var edge = this._createHandle('edge');
      edge.addTo(this._handleGroup);
      this._handles.push(edge);
    }

    var vertex = this._createHandle('vertex');
    vertex.addTo(this._handleGroup);
    this._handles.push(vertex);

    if (this._map) {
      if (edge) {
        this._updateEdgeHandlePosition(edge);
      }
      vertex.setLatLng(latlng);
    }
  },

  _updateEdgeHandlePosition: function(h) {
    var index = this._handles.indexOf(h);
    if (!this._map || index<0) return;

    var prev = this._latlngs[parseInt(index/2)];
    var next = this._latlngs[parseInt(index/2)+1];
    var mid = this._getMidLatLng(this._map, prev, next);
    h.setLatLng(mid);
  },

  _createHandle: function(type) {
    var opts = {
      type: type,
      color: this.options.color,
    }

    var handle = new L.Marker.Handle([0,0], opts);

    handle.on('dragstart', this._onHandleDragStart, this);
    handle.on('drag', this._onHandleDrag, this);
    handle.on('dragend', this._onHandleDragEnd, this);

    handle.on('mouseover', this._onHandleMouseEvent, this);
    handle.on('mouseout', this._onHandleMouseEvent, this);
    handle.on('click', this._onHandleMouseEvent, this);

    return handle;
  },

  _updateHandlePositions: function(map) {
    for (var i=0; i<this._handles.length; i++) {
      var h = this._handles[i];
      if (h.options.type === 'vertex') {
        h.setLatLng(this._latlngs[parseInt(i/2)]);
      }
      else {
        var prev = this._latlngs[parseInt(i/2)];
        var next = this._latlngs[parseInt(i/2)+1];
        var mid = this._getMidLatLng(map, prev, next);
        h.setLatLng(mid);
      }
    }
  },

  _getMidLatLng: function(map, a, b) {
    var aa = map.latLngToLayerPoint(a);
    var bb = map.latLngToLayerPoint(b);
    return map.layerPointToLatLng(aa._add(bb)._divideBy(2));
  },

  _onHandleDragStart: function(e) {
    var h = e.target;

    var d = 2;
    if (h.options.type === 'edge') d = 1;

    var p = [];
    var index = this._handles.indexOf(h);
    if (index > 0) {
      p.push(this._handles[index-d]._latlng);
    }
    if (index < this._handles.length-1) {
      p.push(this._handles[index+d]._latlng);
    }
    p.splice(1, 0, h._latlng);

    this._shadow.setLatLngs(p);
  },

  _onHandleDrag: function(e) {
    this._shadow.spliceLatLngs(1, 1, e.target._latlng);
  },

  _onHandleDragEnd: function(e) {
    this._shadow.setLatLngs([]);

    var h = e.target;
    var index = this._handles.indexOf(h);

    if (h.options.type === 'vertex') {
      this._latlngs.splice(parseInt(index/2), 1, h._latlng);
      this._updateEdgeHandlePosition(this._handles[index-1]);
      this._updateEdgeHandlePosition(this._handles[index+1]);
      this.redraw();
    }
    else {
      this._latlngs.splice(parseInt(index/2)+1, 0, h._latlng);
      h.options.type = 'vertex';
      h._icon.className = h._icon.className.replace('leaflet-angkot-handle-edge', 'leaflet-angkot-handle-vertex');

      var left = this._createHandle('edge');
      var right = this._createHandle('edge');

      this._handles.splice(index+1, 0, right);
      this._handles.splice(index, 0, left);

      if (this._map) {
        left.addTo(this._map);
        right.addTo(this._map);
        this._updateEdgeHandlePosition(left);
        this._updateEdgeHandlePosition(right);
      }
      this.redraw();
    }
  },

  _onHandleMouseEvent: function(e) {
    var h = e.target;

    var index = parseInt(this._handles.indexOf(h)/2);
    if (h.options.type === 'vertex') {
      e.vertex = index;
    }
    else {
      e.edge = index;
    }

    this.fire('handle:' + e.type, e);
  },

});

<commit_msg>Update fileformat to UNIX<commit_after>L.Marker.Handle = L.Marker.extend({
  options: {
    color: 'red',
    draggable: true,
    type: 'vector',
  },

  initialize: function(pos, options) {
    L.Marker.prototype.initialize.call(this, pos, options);
    L.Util.setOptions(this, options);

    var type = options.type;
    var icon = L.divIcon({
      className: 'leaflet-angkot-handle leaflet-angkot-handle-' + type,
      iconSize: new L.Point(10, 10),
    });
    this.options.icon = icon;
  },

  onAdd: function(map) {
    L.Marker.prototype.onAdd.apply(this, arguments);
    this._icon.style.borderColor = this.options.color;
  },

  setColor: function(color) {
    this.options.color = color;
    if (this._icon) {
      this._icon.style.borderColor = this.options.color;
    }
  },
});

L.Polyline.Colorable = L.Polyline.extend({
  setColor: function(color) {
    this.options.color = color;
    this._updateStyle();
  },
});

L.Polyline.Editable = L.Polyline.Colorable.extend({
  options: {
    editable: false,
  },

  initialize: function(path, options) {
    L.Polyline.Colorable.prototype.initialize.call(this, path, options);
    L.Util.setOptions(this, options);

    this._handles = [];
    this._handleGroup = new L.LayerGroup();

    var opacity = Math.min(this.options.opacity/2.0, 0.4);
    this._shadow = new L.Polyline.Colorable([], {
      opacity: opacity,
      weight: this.options.weight,
      color: this.options.color,
    });

    this._resetHandles();
  },

  onAdd: function(map) {
    L.Polyline.Colorable.prototype.onAdd.apply(this, arguments);

    this._updateHandlePositions(map);
    if (this.options.editable) {
      this._shadow.addTo(map);
      this._handleGroup.addTo(map);
    }
  },

  onRemove: function(map) {
    L.Polyline.Colorable.prototype.onRemove.apply(this, arguments);

    map.removeLayer(this._handleGroup);
    map.removeLayer(this._shadow);
  },

  setColor: function(color) {
    L.Polyline.Colorable.prototype.setColor.apply(this, arguments);
    for (var i=0; i<this._handles.length; i++) {
      this._handles[i].setColor(color);
    }
    this._shadow.setColor(color);
  },

  addLatLng: function(latlng) {
    L.Polyline.Colorable.prototype.addLatLng.apply(this, arguments);
    if (this.options.editable) this._addHandle(latlng);
  },

  spliceLatLngs: function() {
    L.Polyline.Colorable.prototype.spliceLatLngs.apply(this, arguments);
    this._resetHandles();
  },

  setEditable: function(editable) {
    this.options.editable = editable;
    if (this._map) {
      if (editable) {
        this._shadow.addTo(map);
        this._handleGroup.addTo(map);
      }
      else {
        this._map.removeLayer(this._shadow);
        this._map.removeLayer(this._handleGroup);
      }
    }
    this._resetHandles();
  },

  _resetHandles: function() {
    for (var i=0; i<this._handles.length; i++) {
      this._handleGroup.removeLayer(this._handles[i]);
    }
    this._handles.splice(0, this._handles.length);

    if (!this.options.editable) return;

    for (var i=0; i<this._latlngs.length; i++) {
      this._addHandle(this._latlngs[i]);
    }
  },

  _addHandle: function(latlng) {
    var edge, vertex;
    if (this._handles.length > 0) {
      var edge = this._createHandle('edge');
      edge.addTo(this._handleGroup);
      this._handles.push(edge);
    }

    var vertex = this._createHandle('vertex');
    vertex.addTo(this._handleGroup);
    this._handles.push(vertex);

    if (this._map) {
      if (edge) {
        this._updateEdgeHandlePosition(edge);
      }
      vertex.setLatLng(latlng);
    }
  },

  _updateEdgeHandlePosition: function(h) {
    var index = this._handles.indexOf(h);
    if (!this._map || index<0) return;

    var prev = this._latlngs[parseInt(index/2)];
    var next = this._latlngs[parseInt(index/2)+1];
    var mid = this._getMidLatLng(this._map, prev, next);
    h.setLatLng(mid);
  },

  _createHandle: function(type) {
    var opts = {
      type: type,
      color: this.options.color,
    }

    var handle = new L.Marker.Handle([0,0], opts);

    handle.on('dragstart', this._onHandleDragStart, this);
    handle.on('drag', this._onHandleDrag, this);
    handle.on('dragend', this._onHandleDragEnd, this);

    handle.on('mouseover', this._onHandleMouseEvent, this);
    handle.on('mouseout', this._onHandleMouseEvent, this);
    handle.on('click', this._onHandleMouseEvent, this);

    return handle;
  },

  _updateHandlePositions: function(map) {
    for (var i=0; i<this._handles.length; i++) {
      var h = this._handles[i];
      if (h.options.type === 'vertex') {
        h.setLatLng(this._latlngs[parseInt(i/2)]);
      }
      else {
        var prev = this._latlngs[parseInt(i/2)];
        var next = this._latlngs[parseInt(i/2)+1];
        var mid = this._getMidLatLng(map, prev, next);
        h.setLatLng(mid);
      }
    }
  },

  _getMidLatLng: function(map, a, b) {
    var aa = map.latLngToLayerPoint(a);
    var bb = map.latLngToLayerPoint(b);
    return map.layerPointToLatLng(aa._add(bb)._divideBy(2));
  },

  _onHandleDragStart: function(e) {
    var h = e.target;

    var d = 2;
    if (h.options.type === 'edge') d = 1;

    var p = [];
    var index = this._handles.indexOf(h);
    if (index > 0) {
      p.push(this._handles[index-d]._latlng);
    }
    if (index < this._handles.length-1) {
      p.push(this._handles[index+d]._latlng);
    }
    p.splice(1, 0, h._latlng);

    this._shadow.setLatLngs(p);
  },

  _onHandleDrag: function(e) {
    this._shadow.spliceLatLngs(1, 1, e.target._latlng);
  },

  _onHandleDragEnd: function(e) {
    this._shadow.setLatLngs([]);

    var h = e.target;
    var index = this._handles.indexOf(h);

    if (h.options.type === 'vertex') {
      this._latlngs.splice(parseInt(index/2), 1, h._latlng);
      this._updateEdgeHandlePosition(this._handles[index-1]);
      this._updateEdgeHandlePosition(this._handles[index+1]);
      this.redraw();
    }
    else {
      this._latlngs.splice(parseInt(index/2)+1, 0, h._latlng);
      h.options.type = 'vertex';
      h._icon.className = h._icon.className.replace('leaflet-angkot-handle-edge', 'leaflet-angkot-handle-vertex');

      var left = this._createHandle('edge');
      var right = this._createHandle('edge');

      this._handles.splice(index+1, 0, right);
      this._handles.splice(index, 0, left);

      if (this._map) {
        left.addTo(this._map);
        right.addTo(this._map);
        this._updateEdgeHandlePosition(left);
        this._updateEdgeHandlePosition(right);
      }
      this.redraw();
    }
  },

  _onHandleMouseEvent: function(e) {
    var h = e.target;

    var index = parseInt(this._handles.indexOf(h)/2);
    if (h.options.type === 'vertex') {
      e.vertex = index;
    }
    else {
      e.edge = index;
    }

    this.fire('handle:' + e.type, e);
  },

});

<|endoftext|>