"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.cameraReducer = void 0; var _typescriptFsaReducers = require("typescript-fsa-reducers"); var _scaling_constants = require("./scaling_constants"); var _methods = require("./methods"); var vector2 = _interopRequireWildcard(require("../../models/vector2")); var selectors = _interopRequireWildcard(require("./selectors")); var _math = require("../../lib/math"); var _helpers = require("../helpers"); var _action = require("./action"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ const cameraReducer = (0, _typescriptFsaReducers.reducerWithInitialState)(_helpers.initialAnalyzerState).withHandling((0, _helpers.immerCase)(_action.userSetZoomLevel, (draft, { id, zoomLevel }) => { /** * Handle the scale being explicitly set, for example by a 'reset zoom' feature, or by a range slider with exact scale values */ const state = draft[id].camera; state.scalingFactor = (0, _math.clamp)(zoomLevel, 0, 1); return draft; })).withHandling((0, _helpers.immerCase)(_action.userClickedZoomIn, (draft, { id }) => { const state = draft[id].camera; state.scalingFactor = (0, _math.clamp)(state.scalingFactor + 0.1, 0, 1); return draft; })).withHandling((0, _helpers.immerCase)(_action.userClickedZoomOut, (draft, { id }) => { const state = draft[id].camera; state.scalingFactor = (0, _math.clamp)(state.scalingFactor - 0.1, 0, 1); return draft; })).withHandling((0, _helpers.immerCase)(_action.userZoomed, (draft, { id, zoomChange, time }) => { const state = draft[id].camera; const stateWithNewScaling = { ...state, scalingFactor: (0, _math.clamp)(state.scalingFactor + zoomChange, 0, 1) }; /** * Zooming fundamentally just changes the scale, but that would always zoom in (or out) around the center of the map. The user might be interested in * something else, like a node. If the user has moved their pointer on to the map, we can keep the pointer over the same point in the map by adjusting the * panning when we zoom. * * You can see this in action by moving your pointer over a node that isn't directly in the center of the map and then changing the zoom level. Do it by * using CTRL and the mousewheel, or by pinching the trackpad on a Mac. The node will stay under your mouse cursor and other things in the map will get * nearer or further from the mouse cursor. This lets you keep your context when changing zoom levels. */ if (state.latestFocusedWorldCoordinates !== null && !selectors.isAnimating(state)(time)) { const rasterOfLastFocusedWorldCoordinates = vector2.applyMatrix3(state.latestFocusedWorldCoordinates, selectors.projectionMatrix(state)(time)); const newWorldCoordinatesAtLastFocusedPosition = vector2.applyMatrix3(rasterOfLastFocusedWorldCoordinates, selectors.inverseProjectionMatrix(stateWithNewScaling)(time)); /** * The change in world position incurred by changing scale. */ const delta = vector2.subtract(newWorldCoordinatesAtLastFocusedPosition, state.latestFocusedWorldCoordinates); /** * Adjust for the change in position due to scale. */ const translationNotCountingCurrentPanning = vector2.subtract(stateWithNewScaling.translationNotCountingCurrentPanning, delta); draft[id].camera = { ...stateWithNewScaling, translationNotCountingCurrentPanning }; } else { draft[id].camera = stateWithNewScaling; } return draft; })).withHandling((0, _helpers.immerCase)(_action.userSetPositionOfCamera, (draft, { id, cameraView }) => { /** * Handle the case where the position of the camera is explicitly set, for example by a 'back to center' feature. */ const state = draft[id].camera; state.animation = undefined; state.translationNotCountingCurrentPanning[0] = cameraView[0]; state.translationNotCountingCurrentPanning[1] = cameraView[1]; return draft; })).withHandling((0, _helpers.immerCase)(_action.userStartedPanning, (draft, { id, screenCoordinates, time }) => { const state = draft[id].camera; if (selectors.isAnimating(state)(time)) { return draft; } /** * When the user begins panning with a mousedown event we mark the starting position for later comparisons. */ state.animation = undefined; state.panning = { ...state.panning, origin: screenCoordinates, currentOffset: screenCoordinates }; return draft; })).withHandling((0, _helpers.immerCase)(_action.userStoppedPanning, (draft, { id, time }) => { /** * When the user stops panning (by letting up on the mouse) we calculate the new translation of the camera. */ const state = draft[id].camera; state.translationNotCountingCurrentPanning = selectors.translation(state)(time); state.panning = undefined; return draft; })).withHandling((0, _helpers.immerCase)(_action.userNudgedCamera, (draft, { id, direction, time }) => { const state = draft[id].camera; /** * Nudge less when zoomed in. */ const nudge = vector2.multiply(vector2.divide([_scaling_constants.unitsPerNudge, _scaling_constants.unitsPerNudge], selectors.scale(state)(time)), direction); draft[id].camera = (0, _methods.animatePanning)(state, time, vector2.add(state.translationNotCountingCurrentPanning, nudge), _scaling_constants.nudgeAnimationDuration); return draft; })).withHandling((0, _helpers.immerCase)(_action.userSetRasterSize, (draft, { id, dimensions }) => { /** * Handle resizes of the Resolver component. We need to know the size in order to convert between screen * and world coordinates. */ draft[id].camera.rasterSize = dimensions; return draft; })).withHandling((0, _helpers.immerCase)(_action.userMovedPointer, (draft, { id, screenCoordinates, time }) => { const state = draft[id].camera; let stateWithUpdatedPanning = draft[id].camera; if (state.panning) { stateWithUpdatedPanning = { ...state, panning: { origin: state.panning.origin, currentOffset: screenCoordinates } }; } draft[id].camera = { ...stateWithUpdatedPanning, /** * keep track of the last world coordinates the user moved over. * When the scale of the projection matrix changes, we adjust the camera's world transform in order * to keep the same point under the pointer. * In order to do this, we need to know the position of the mouse when changing the scale. */ latestFocusedWorldCoordinates: vector2.applyMatrix3(screenCoordinates, selectors.inverseProjectionMatrix(stateWithUpdatedPanning)(time)) }; return draft; })).build(); exports.cameraReducer = cameraReducer;