|
1 | 1 | import core from 'core';
|
2 | 2 | import { stepToZoomFactorRangesMap, getMaxZoomLevel, getMinZoomLevel } from 'constants/zoomFactors';
|
3 | 3 |
|
4 |
| -export const zoomIn = () => { |
5 |
| - const currentZoomFactor = core.getZoom(); |
6 |
| - if (currentZoomFactor === getMaxZoomLevel()) { |
7 |
| - return; |
8 |
| - } |
9 |
| - |
10 |
| - const step = getStep(currentZoomFactor); |
11 |
| - const newZoomFactor = currentZoomFactor + step; |
12 |
| - zoomTo(Math.min(newZoomFactor, getMaxZoomLevel())); |
13 |
| -}; |
14 |
| - |
15 |
| -export const zoomOut = () => { |
16 |
| - const currentZoomFactor = core.getZoom(); |
17 |
| - if (currentZoomFactor === getMinZoomLevel()) { |
18 |
| - return; |
19 |
| - } |
20 |
| - |
21 |
| - const step = getStep(currentZoomFactor); |
22 |
| - const newZoomFactor = currentZoomFactor - step; |
23 |
| - zoomTo(Math.max(newZoomFactor, getMinZoomLevel())); |
24 |
| -}; |
25 |
| - |
26 |
| -const getStep = currentZoomFactor => { |
| 4 | +function getStep(currentZoomFactor) { |
27 | 5 | const steps = Object.keys(stepToZoomFactorRangesMap);
|
28 | 6 | const step = steps.find(step => {
|
29 | 7 | const zoomFactorRanges = stepToZoomFactorRangesMap[step];
|
30 |
| - |
31 | 8 | return isCurrentZoomFactorInRange(currentZoomFactor, zoomFactorRanges);
|
32 | 9 | });
|
33 | 10 |
|
34 | 11 | return parseFloat(step);
|
35 |
| -}; |
| 12 | +} |
36 | 13 |
|
37 |
| -const isCurrentZoomFactorInRange = (zoomFactor, ranges) => { |
38 |
| - if (ranges[0] === null) { |
39 |
| - return zoomFactor <= ranges[1]; |
| 14 | +function isCurrentZoomFactorInRange(zoomFactor, ranges) { |
| 15 | + const [rangeLowBound, rangeHighBound] = ranges; |
| 16 | + if (rangeLowBound === null) { |
| 17 | + return zoomFactor < rangeHighBound; |
40 | 18 | }
|
41 |
| - if (ranges[1] === null) { |
42 |
| - return zoomFactor >= ranges[0]; |
| 19 | + if (rangeHighBound === null) { |
| 20 | + return zoomFactor >= rangeLowBound; |
43 | 21 | }
|
44 |
| - return zoomFactor >= ranges[0] && zoomFactor <= ranges[1]; |
45 |
| -}; |
46 |
| - |
47 |
| -export const zoomTo = newZoomFactor => { |
48 |
| - const currentZoomFactor = core.getZoom(); |
49 |
| - const scale = newZoomFactor / currentZoomFactor; |
50 |
| - const { x, y } = getViewCenterAfterScale(scale); |
51 |
| - |
52 |
| - core.zoomTo(newZoomFactor, x, y); |
53 |
| -}; |
| 22 | + return zoomFactor >= rangeLowBound && zoomFactor < rangeHighBound; |
| 23 | +} |
54 | 24 |
|
55 |
| -const getViewCenterAfterScale = scale => { |
| 25 | +function getViewCenterAfterScale(scale) { |
56 | 26 | const documentContainer = document.getElementsByClassName('DocumentContainer')[0];
|
57 | 27 | const documentWrapper = document.getElementsByClassName('document')[0];
|
58 | 28 | const clientX = window.innerWidth / 2;
|
59 | 29 | const clientY = window.innerHeight / 2;
|
60 | 30 |
|
61 | 31 | const x = (clientX + documentContainer.scrollLeft - documentWrapper.offsetLeft) * scale - clientX + documentContainer.offsetLeft;
|
62 | 32 | const y = (clientY + documentContainer.scrollTop - documentWrapper.offsetTop) * scale - clientY + documentContainer.offsetTop;
|
63 |
| - |
64 | 33 | return { x, y };
|
65 |
| -}; |
| 34 | +} |
| 35 | + |
| 36 | +let zoomStepHistory = []; |
| 37 | + |
| 38 | +// Keeping track of changes to zoomFactor outside this helper functions |
| 39 | +let storedZoomFactor = -1; |
| 40 | + |
| 41 | +function zoomToInternal(currentZoomFactor, newZoomFactor) { |
| 42 | + const scale = newZoomFactor / currentZoomFactor; |
| 43 | + const { x, y } = getViewCenterAfterScale(scale); |
| 44 | + core.zoomTo(newZoomFactor, x, y); |
| 45 | + storedZoomFactor = newZoomFactor; |
| 46 | +} |
| 47 | + |
| 48 | +function resetZoomStepHistory() { |
| 49 | + zoomStepHistory = []; |
| 50 | +} |
| 51 | + |
| 52 | +export function fitToWidth() { |
| 53 | + resetZoomStepHistory(); |
| 54 | + core.fitToWidth(); |
| 55 | +} |
| 56 | + |
| 57 | +export function fitToPage() { |
| 58 | + resetZoomStepHistory(); |
| 59 | + core.fitToPage(); |
| 60 | +} |
| 61 | + |
| 62 | +/** |
| 63 | + * zoomIn works as follows. Every time user zooms in we take current zoom level and compare it to |
| 64 | + * zoom factors map which currently is : |
| 65 | + * { |
| 66 | + '0.075': [null, 0.8], |
| 67 | + '0.25': [0.8, 1.5], |
| 68 | + '0.5': [1.5, 2.5], |
| 69 | + '1': [2.5, 4], |
| 70 | + '2': [4, 8], |
| 71 | + '4': [8, 32], |
| 72 | + '8': [32, 64], |
| 73 | + '16': [64, null], |
| 74 | + } |
| 75 | + * Based on the range we select step size from this map. For example if current zoom level is 110% (1.1) |
| 76 | + * then we can see that it falls to range [0.8, 1.5] and step size for this is 0.25. We add this step |
| 77 | + * to current level so we end up to 1.35. We also store this step to stack so we can follow how after few steps we got there. |
| 78 | + * |
| 79 | + * We need to keep the step history to make our steps predictable in all cases. |
| 80 | + * Consider case where we start from zoom level 140% (1.4) and zoomIn. We would end up to 1.4 + 0.25 = 1.65 (165%). |
| 81 | + * If user would now click zoomOut, we would end up 1.65 - 0.5 = 1.15 (115%) which is not the same 140% where we started. |
| 82 | + * But as we store the step history we do 1.65 - 0.25 (value from step history) and end up to 1.4 (140%). |
| 83 | + */ |
| 84 | +export function zoomIn() { |
| 85 | + const currentZoomFactor = core.getZoom(); |
| 86 | + if (storedZoomFactor > 0 && currentZoomFactor !== storedZoomFactor) { |
| 87 | + // zoom level was changed by external side effect (like one of core's function to change zoom level) |
| 88 | + // in these cases we need to reset step history |
| 89 | + resetZoomStepHistory(); |
| 90 | + } |
| 91 | + if (currentZoomFactor === getMaxZoomLevel()) { |
| 92 | + return; |
| 93 | + } |
| 94 | + |
| 95 | + let step = getStep(currentZoomFactor); |
| 96 | + if (zoomStepHistory.length > 0 && zoomStepHistory[zoomStepHistory.length - 1] < 0) { |
| 97 | + // if step history has steps and it has been opposite direction (zoomOut) |
| 98 | + // We use that step. This makes sure that when crossing step range, zoom level goes to same |
| 99 | + // as it was when zoomOut was done. |
| 100 | + // We differentiate zoomIn and zoomOut steps by zoomOut steps are negative and zoomIn are positive |
| 101 | + // thus here using absolute value |
| 102 | + step = Math.abs(zoomStepHistory.pop()); |
| 103 | + } else { |
| 104 | + // We differentiate zoomIn and zoomOut steps by zoomOut steps are negative and zoomIn are positive |
| 105 | + zoomStepHistory.push(step); |
| 106 | + } |
| 107 | + const newZoomFactor = Math.min(currentZoomFactor + step, getMaxZoomLevel()); |
| 108 | + zoomToInternal(currentZoomFactor, newZoomFactor); |
| 109 | +} |
| 110 | + |
| 111 | +/** |
| 112 | + * See functionality from zoomIn. zoomOut works same but opposite direction. |
| 113 | + */ |
| 114 | +export function zoomOut() { |
| 115 | + const currentZoomFactor = core.getZoom(); |
| 116 | + if (storedZoomFactor > 0 && currentZoomFactor !== storedZoomFactor) { |
| 117 | + // zoom level was changed by external side effect (like one of core's function to change zoom level) |
| 118 | + // in these cases we need to reset step history |
| 119 | + resetZoomStepHistory(); |
| 120 | + } |
| 121 | + if (currentZoomFactor === getMinZoomLevel()) { |
| 122 | + return; |
| 123 | + } |
| 124 | + |
| 125 | + let step = getStep(currentZoomFactor); |
| 126 | + if (zoomStepHistory.length > 0 && zoomStepHistory[zoomStepHistory.length -1] > 0) { |
| 127 | + // if step history has steps and it has been opposite direction (zoomIn) |
| 128 | + // We use that step. This makes sure that when crossing step range, zoom level goes to same |
| 129 | + // as it was when zoomIn was done. |
| 130 | + // We differentiate zoomIn and zoomOut steps by zoomOut steps are negative and zoomIn are positive |
| 131 | + step = zoomStepHistory.pop(); |
| 132 | + } else { |
| 133 | + // We differentiate zoomIn and zoomOut steps by zoomOut steps are negative and zoomIn are positive |
| 134 | + zoomStepHistory.push(-1 * step); |
| 135 | + } |
| 136 | + const newZoomFactor = Math.max(currentZoomFactor - step, getMinZoomLevel()); |
| 137 | + zoomToInternal(currentZoomFactor, newZoomFactor); |
| 138 | +} |
| 139 | + |
| 140 | +export function zoomTo(newZoomFactor) { |
| 141 | + // if user sets certain zoom level, then we reset the step history |
| 142 | + resetZoomStepHistory(); |
| 143 | + const currentZoomFactor = core.getZoom(); |
| 144 | + zoomToInternal(currentZoomFactor, newZoomFactor); |
| 145 | +} |
0 commit comments