OpenSourcePolitics / decidim-ditp

Decidim instance for DITP OGP
1 stars 2 forks source link

refactor to be able to adjust map pane position after zoom #38

Open github-actions[bot] opened 1 year ago

github-actions[bot] commented 1 year ago

@property dragging: Handler

Map dragging handler (by both mouse and touch).

https://api.github.com/OpenSourcePolitics/decidim-ditp/blob/cf68cc605e2b45b5d16865ebf4eadd87b89a898f/packages/core/node_modules/leaflet/src/map/handler/Map.Drag.js#L174


import {Map} from '../Map';
import * as Browser from '../../core/Browser';
import {Handler} from '../../core/Handler';
import {Draggable} from '../../dom/Draggable';
import * as Util from '../../core/Util';
import * as DomUtil from '../../dom/DomUtil';
import {toLatLngBounds as latLngBounds} from '../../geo/LatLngBounds';
import {toBounds} from '../../geometry/Bounds';

/*
 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
 */

// @namespace Map
// @section Interaction Options
Map.mergeOptions({
    // @option dragging: Boolean = true
    // Whether the map be draggable with mouse/touch or not.
    dragging: true,

    // @section Panning Inertia Options
    // @option inertia: Boolean = *
    // If enabled, panning of the map will have an inertia effect where
    // the map builds momentum while dragging and continues moving in
    // the same direction for some time. Feels especially nice on touch
    // devices. Enabled by default unless running on old Android devices.
    inertia: !Browser.android23,

    // @option inertiaDeceleration: Number = 3000
    // The rate with which the inertial movement slows down, in pixels/second².
    inertiaDeceleration: 3400, // px/s^2

    // @option inertiaMaxSpeed: Number = Infinity
    // Max speed of the inertial movement, in pixels/second.
    inertiaMaxSpeed: Infinity, // px/s

    // @option easeLinearity: Number = 0.2
    easeLinearity: 0.2,

    // TODO refactor, move to CRS
    // @option worldCopyJump: Boolean = false
    // With this option enabled, the map tracks when you pan to another "copy"
    // of the world and seamlessly jumps to the original one so that all overlays
    // like markers and vector layers are still visible.
    worldCopyJump: false,

    // @option maxBoundsViscosity: Number = 0.0
    // If `maxBounds` is set, this option will control how solid the bounds
    // are when dragging the map around. The default value of `0.0` allows the
    // user to drag outside the bounds at normal speed, higher values will
    // slow down map dragging outside bounds, and `1.0` makes the bounds fully
    // solid, preventing the user from dragging outside the bounds.
    maxBoundsViscosity: 0.0
});

export var Drag = Handler.extend({
    addHooks: function () {
        if (!this._draggable) {
            var map = this._map;

            this._draggable = new Draggable(map._mapPane, map._container);

            this._draggable.on({
                dragstart: this._onDragStart,
                drag: this._onDrag,
                dragend: this._onDragEnd
            }, this);

            this._draggable.on('predrag', this._onPreDragLimit, this);
            if (map.options.worldCopyJump) {
                this._draggable.on('predrag', this._onPreDragWrap, this);
                map.on('zoomend', this._onZoomEnd, this);

                map.whenReady(this._onZoomEnd, this);
            }
        }
        DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
        this._draggable.enable();
        this._positions = [];
        this._times = [];
    },

    removeHooks: function () {
        DomUtil.removeClass(this._map._container, 'leaflet-grab');
        DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
        this._draggable.disable();
    },

    moved: function () {
        return this._draggable && this._draggable._moved;
    },

    moving: function () {
        return this._draggable && this._draggable._moving;
    },

    _onDragStart: function () {
        var map = this._map;

        map._stop();
        if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
            var bounds = latLngBounds(this._map.options.maxBounds);

            this._offsetLimit = toBounds(
                this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
                this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
                    .add(this._map.getSize()));

            this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
        } else {
            this._offsetLimit = null;
        }

        map
            .fire('movestart')
            .fire('dragstart');

        if (map.options.inertia) {
            this._positions = [];
            this._times = [];
        }
    },

    _onDrag: function (e) {
        if (this._map.options.inertia) {
            var time = this._lastTime = +new Date(),
                pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;

            this._positions.push(pos);
            this._times.push(time);

            this._prunePositions(time);
        }

        this._map
            .fire('move', e)
            .fire('drag', e);
    },

    _prunePositions: function (time) {
        while (this._positions.length > 1 && time - this._times[0] > 50) {
            this._positions.shift();
            this._times.shift();
        }
    },

    _onZoomEnd: function () {
        var pxCenter = this._map.getSize().divideBy(2),
            pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);

        this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
        this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
    },

    _viscousLimit: function (value, threshold) {
        return value - (value - threshold) * this._viscosity;
    },

    _onPreDragLimit: function () {
        if (!this._viscosity || !this._offsetLimit) { return; }

        var offset = this._draggable._newPos.subtract(this._draggable._startPos);

        var limit = this._offsetLimit;
        if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
        if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
        if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
        if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }

        this._draggable._newPos = this._draggable._startPos.add(offset);
    },

    _onPreDragWrap: function () {
        // TODO refactor to be able to adjust map pane position after zoom
        var worldWidth = this._worldWidth,
            halfWidth = Math.round(worldWidth / 2),
            dx = this._initialWorldOffset,
            x = this._draggable._newPos.x,
            newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
            newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
            newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;

        this._draggable._absPos = this._draggable._newPos.clone();
        this._draggable._newPos.x = newX;
    },

    _onDragEnd: function (e) {
        var map = this._map,
            options = map.options,

            noInertia = !options.inertia || this._times.length < 2;

        map.fire('dragend', e);

        if (noInertia) {
            map.fire('moveend');

        } else {
            this._prunePositions(+new Date());

            var direction = this._lastPos.subtract(this._positions[0]),
                duration = (this._lastTime - this._times[0]) / 1000,
                ease = options.easeLinearity,

                speedVector = direction.multiplyBy(ease / duration),
                speed = speedVector.distanceTo([0, 0]),

                limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
                limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),

                decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
                offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();

            if (!offset.x && !offset.y) {
                map.fire('moveend');

            } else {
                offset = map._limitOffset(offset, map.options.maxBounds);

                Util.requestAnimFrame(function () {
                    map.panBy(offset, {
                        duration: decelerationDuration,
                        easeLinearity: ease,
                        noMoveStart: true,
                        animate: true
                    });
                });
            }
        }
    }
});

// @section Handlers
// @property dragging: Handler
// Map dragging handler (by both mouse and touch).
Map.addInitHook('addHandler', 'dragging', Drag);