Tweak controls for mobile devices
- Updated scenery zoom pan wrapper, track hover info and distance meter to work correctly on touch screen / mobile devices - Fixed SideMenu still responding to tab navigation even if closed
This commit is contained in:
parent
a1a27a9ca0
commit
0bedd56cc8
@ -1,6 +1,8 @@
|
||||
.zoom-pan-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.zoom-pan-wrapper svg {
|
||||
|
||||
@ -10,6 +10,13 @@ function boundZoom(zoom) {
|
||||
return Math.max(Constants.map.zoomMin, Math.min(Constants.map.zoomMax, zoom));
|
||||
}
|
||||
|
||||
const getDistance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
||||
const getAngle = (a, b) => Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI;
|
||||
const getMidpoint = (a, b) => ({
|
||||
x: (a.x + b.x) / 2,
|
||||
y: (a.y + b.y) / 2
|
||||
});
|
||||
|
||||
export default function ZoomPanWrapper({children}) {
|
||||
const wrapperRef = useRef(null);
|
||||
const svgRef = useRef(null);
|
||||
@ -18,7 +25,9 @@ export default function ZoomPanWrapper({children}) {
|
||||
const cameraRef = useRef({ x: 0, y: 0, zoom: 1, rotation: 0 });
|
||||
const clientRectRef = useRef(null);
|
||||
const rafScheduledRef = useRef(false);
|
||||
const isMouseDownRef = useRef(false);
|
||||
const pointersRef = useRef(new Map());
|
||||
const lastPointerRef = useRef(null);
|
||||
const twoFingerStartRef = useRef(null);
|
||||
|
||||
const getViewBoxString = () => {
|
||||
const { x, y, w, h } = viewBoxRef.current;
|
||||
@ -59,28 +68,157 @@ export default function ZoomPanWrapper({children}) {
|
||||
clientRectRef.current = rect;
|
||||
}, [updateViewBox]);
|
||||
|
||||
const handleRotation = (e) => {
|
||||
const deltaAngle = e.movementX * Constants.map.rotationSensitivity;
|
||||
if(deltaAngle === 0) return;
|
||||
const applyScreenPan = useCallback((dxScreen, dyScreen) => {
|
||||
const { zoom, rotation } = cameraRef.current;
|
||||
const dx = dxScreen / zoom;
|
||||
const dy = dyScreen / zoom;
|
||||
const rad = AngleHelper.degToRad(rotation);
|
||||
const cos = Math.cos(rad);
|
||||
const sin = Math.sin(rad);
|
||||
|
||||
cameraRef.current.rotation = cameraRef.current.rotation + deltaAngle
|
||||
cameraRef.current.x -= dx * cos + dy * sin;
|
||||
cameraRef.current.y -= dy * cos - dx * sin;
|
||||
|
||||
scheduleCameraUpdate();
|
||||
}, [scheduleCameraUpdate]);
|
||||
|
||||
const getScreenPoint = (clientX, clientY) => {
|
||||
const rect = clientRectRef.current;
|
||||
return {
|
||||
x: clientX - rect.left - rect.width / 2,
|
||||
y: clientY - rect.top - rect.height / 2
|
||||
};
|
||||
};
|
||||
|
||||
const screenPointToWorld = (point, camera) => {
|
||||
const rad = AngleHelper.degToRad(camera.rotation);
|
||||
const cos = Math.cos(rad);
|
||||
const sin = Math.sin(rad);
|
||||
const sx = point.x / camera.zoom;
|
||||
const sy = point.y / camera.zoom;
|
||||
|
||||
return {
|
||||
x: camera.x + sx * cos + sy * sin,
|
||||
y: camera.y - sx * sin + sy * cos
|
||||
};
|
||||
};
|
||||
|
||||
const startTwoFingerGesture = (pointers) => {
|
||||
const [first, second] = pointers;
|
||||
const distance = getDistance(first, second);
|
||||
const angle = getAngle(first, second);
|
||||
const midpoint = getMidpoint(first, second);
|
||||
const screenPoint = getScreenPoint(midpoint.x, midpoint.y);
|
||||
const worldPoint = screenPointToWorld(screenPoint, cameraRef.current);
|
||||
|
||||
twoFingerStartRef.current = {
|
||||
distance,
|
||||
angle,
|
||||
zoom: cameraRef.current.zoom,
|
||||
rotation: cameraRef.current.rotation,
|
||||
worldPoint
|
||||
};
|
||||
};
|
||||
|
||||
const updateTwoFingerGesture = (pointers) => {
|
||||
const start = twoFingerStartRef.current;
|
||||
if (!start) return;
|
||||
|
||||
const [first, second] = pointers;
|
||||
const distance = getDistance(first, second);
|
||||
const angle = getAngle(first, second);
|
||||
const midpoint = getMidpoint(first, second);
|
||||
|
||||
const newZoom = boundZoom(start.zoom * (distance / start.distance));
|
||||
const deltaAngle = AngleHelper.normalizeAngleDelta(angle - start.angle);
|
||||
const newRotation = AngleHelper.normalizeDegAngle(start.rotation + deltaAngle);
|
||||
|
||||
const screenPoint = getScreenPoint(midpoint.x, midpoint.y);
|
||||
const rad = AngleHelper.degToRad(newRotation);
|
||||
const cos = Math.cos(rad);
|
||||
const sin = Math.sin(rad);
|
||||
const sx = screenPoint.x / newZoom;
|
||||
const sy = screenPoint.y / newZoom;
|
||||
|
||||
cameraRef.current.zoom = newZoom;
|
||||
cameraRef.current.rotation = newRotation;
|
||||
cameraRef.current.x = start.worldPoint.x - (sx * cos + sy * sin);
|
||||
cameraRef.current.y = start.worldPoint.y - (-sx * sin + sy * cos);
|
||||
|
||||
scheduleCameraUpdate();
|
||||
};
|
||||
|
||||
const handleMove = (e) => {
|
||||
const cx = e.movementX / cameraRef.current.zoom;
|
||||
const cy = e.movementY / cameraRef.current.zoom;
|
||||
const cos = Math.cos(AngleHelper.degToRad(cameraRef.current.rotation));
|
||||
const sin = Math.sin(AngleHelper.degToRad(cameraRef.current.rotation));
|
||||
const onPointerDown = (e) => {
|
||||
pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
||||
|
||||
cameraRef.current.x -= cx * cos + cy * sin;
|
||||
cameraRef.current.y -= cy * cos - cx * sin;
|
||||
|
||||
scheduleCameraUpdate();
|
||||
if (pointersRef.current.size === 1) {
|
||||
lastPointerRef.current = { x: e.clientX, y: e.clientY };
|
||||
twoFingerStartRef.current = null;
|
||||
} else if (pointersRef.current.size === 2) {
|
||||
startTwoFingerGesture(Array.from(pointersRef.current.values()));
|
||||
lastPointerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
// update viewbox when the window resizes
|
||||
const onPointerMove = (e) => {
|
||||
if (!pointersRef.current.has(e.pointerId)) return;
|
||||
|
||||
pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
||||
|
||||
if (pointersRef.current.size === 1) {
|
||||
const last = lastPointerRef.current;
|
||||
if (!last) return;
|
||||
|
||||
const dx = e.clientX - last.x;
|
||||
const dy = e.clientY - last.y;
|
||||
lastPointerRef.current = { x: e.clientX, y: e.clientY };
|
||||
|
||||
if (e.pointerType === 'mouse' && e.altKey) {
|
||||
const deltaAngle = dx * Constants.map.rotationSensitivity;
|
||||
if (deltaAngle !== 0) {
|
||||
cameraRef.current.rotation += deltaAngle;
|
||||
scheduleCameraUpdate();
|
||||
}
|
||||
} else {
|
||||
applyScreenPan(dx, dy);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (pointersRef.current.size >= 2) {
|
||||
const firstTwo = Array.from(pointersRef.current.values()).slice(0, 2);
|
||||
if (!twoFingerStartRef.current) {
|
||||
startTwoFingerGesture(firstTwo);
|
||||
} else {
|
||||
updateTwoFingerGesture(firstTwo);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = (e) => {
|
||||
pointersRef.current.delete(e.pointerId);
|
||||
|
||||
if (pointersRef.current.size === 0) {
|
||||
lastPointerRef.current = null;
|
||||
twoFingerStartRef.current = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pointersRef.current.size === 1) {
|
||||
const remaining = Array.from(pointersRef.current.values())[0];
|
||||
lastPointerRef.current = { x: remaining.x, y: remaining.y };
|
||||
twoFingerStartRef.current = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pointersRef.current.size >= 2) {
|
||||
startTwoFingerGesture(Array.from(pointersRef.current.values()).slice(0, 2));
|
||||
lastPointerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerCancel = onPointerUp;
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
handleResize(); // Initial call to set the viewBox
|
||||
@ -113,6 +251,8 @@ export default function ZoomPanWrapper({children}) {
|
||||
useZoomPanSubscriber(setCamera, alignView);
|
||||
|
||||
const onWheel = (e) => {
|
||||
if (!clientRectRef.current) return;
|
||||
|
||||
const { left, top } = clientRectRef.current;
|
||||
const { x, y, zoom } = cameraRef.current;
|
||||
const { w, h } = viewBoxRef.current;
|
||||
@ -146,37 +286,16 @@ export default function ZoomPanWrapper({children}) {
|
||||
scheduleCameraUpdate();
|
||||
};
|
||||
|
||||
const onMouseDown = (e) => {
|
||||
e.preventDefault();
|
||||
isMouseDownRef.current = true;
|
||||
};
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
if (!isMouseDownRef.current) return;
|
||||
e.preventDefault();
|
||||
|
||||
if(e.altKey) {
|
||||
handleRotation(e);
|
||||
} else {
|
||||
handleMove(e);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = (e) => {
|
||||
e.preventDefault();
|
||||
isMouseDownRef.current = false;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="zoom-pan-wrapper" ref={wrapperRef}>
|
||||
<svg
|
||||
ref={svgRef}
|
||||
viewBox={getViewBoxString()}
|
||||
onWheel={onWheel}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseMove={onMouseMove}
|
||||
onMouseUp={onMouseUp}
|
||||
onMouseLeave={onMouseUp}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerCancel={onPointerCancel}
|
||||
>
|
||||
<g ref={cameraWrapperRef} transform={getCameraTransformString()}>
|
||||
{children}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useContext, useEffect } from "react";
|
||||
import { useContext, useEffect, useRef, useCallback } from "react";
|
||||
import Constants from '../../../helpers/constants';
|
||||
import SceneryContext from "../../../contexts/SceneryContext";
|
||||
import DistanceMeterContext from "../../../contexts/DistanceMeterContext";
|
||||
import { getSVGCoords } from "./get-svg-coords";
|
||||
@ -7,8 +8,24 @@ import DistanceMeterView from "./DistanceMeterView";
|
||||
export default function DistanceMeter() {
|
||||
const { distancePoints, setDistancePoints } = useContext(DistanceMeterContext);
|
||||
const { scenery } = useContext(SceneryContext);
|
||||
const pointersRef = useRef(new Set());
|
||||
const mainPointerIdRef = useRef(null);
|
||||
const pointerDownPosRef = useRef(null);
|
||||
|
||||
const onPointerUp = useCallback((event) => {
|
||||
const dx = pointerDownPosRef.current?.x - event.clientX;
|
||||
const dy = pointerDownPosRef.current?.y - event.clientY;
|
||||
|
||||
pointersRef.current.delete(event.pointerId);
|
||||
if (mainPointerIdRef.current !== event.pointerId) return;
|
||||
mainPointerIdRef.current = null;
|
||||
pointerDownPosRef.current = null;
|
||||
|
||||
if (pointersRef.current.size > 0) return;
|
||||
|
||||
const dist = Math.sqrt(dx*dx + dy*dy);
|
||||
if (dist > Constants.map.distanceMeterMaxMovePx) return;
|
||||
|
||||
const onClick = (event) => {
|
||||
setDistancePoints(oldDistancePoints => {
|
||||
if(!Array.isArray(oldDistancePoints)) return oldDistancePoints;
|
||||
const pos = getSVGCoords(event);
|
||||
@ -16,7 +33,26 @@ export default function DistanceMeter() {
|
||||
|
||||
return [...oldDistancePoints, pos];
|
||||
});
|
||||
}
|
||||
}, [setDistancePoints]);
|
||||
|
||||
const onPointerDown = useCallback((event) => {
|
||||
pointersRef.current.add(event.pointerId);
|
||||
if(mainPointerIdRef.current !== null) return;
|
||||
|
||||
mainPointerIdRef.current = event.pointerId;
|
||||
pointerDownPosRef.current = {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onPointerLeave = useCallback((event) => {
|
||||
pointersRef.current.delete(event.pointerId);
|
||||
if (mainPointerIdRef.current !== event.pointerId)
|
||||
|
||||
mainPointerIdRef.current = null;
|
||||
pointerDownPosRef.current = null;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setDistancePoints([]);
|
||||
@ -32,7 +68,10 @@ export default function DistanceMeter() {
|
||||
y={-scenery.bounds.maxZ}
|
||||
width={scenery.bounds.maxX - scenery.bounds.minX}
|
||||
height={scenery.bounds.maxZ - scenery.bounds.minZ}
|
||||
onClick={onClick}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerCancel={onPointerLeave}
|
||||
onPointerLeave={onPointerLeave}
|
||||
no-export="true"
|
||||
/>
|
||||
<DistanceMeterView distancePoints={distancePoints} setDistancePoints={setDistancePoints} />
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { useRef } from 'react';
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { getSVGCoords } from './get-svg-coords';
|
||||
|
||||
export default function DistanceMeterCircle(props) {
|
||||
const { point, index, setDistancePoints } = props;
|
||||
const isDragged = useRef(false);
|
||||
const isDraggingRef = useRef(false);
|
||||
|
||||
const updatePointPosition = (event) => {
|
||||
const updatePointPosition = useCallback((event) => {
|
||||
const pos = getSVGCoords(event);
|
||||
if (!pos) return;
|
||||
|
||||
@ -15,57 +15,75 @@ export default function DistanceMeterCircle(props) {
|
||||
newPoints[index] = pos;
|
||||
return newPoints;
|
||||
});
|
||||
}
|
||||
}, [setDistancePoints, index]);
|
||||
|
||||
const removePoint = () => {
|
||||
const removePoint = useCallback(() => {
|
||||
setDistancePoints(oldPoints => {
|
||||
if (!Array.isArray(oldPoints)) return oldPoints;
|
||||
const newPoints = [...oldPoints];
|
||||
newPoints.splice(index, 1);
|
||||
return newPoints;
|
||||
});
|
||||
}
|
||||
}, [setDistancePoints, index]);
|
||||
|
||||
const onMouseDown = (event) => {
|
||||
const onPointerDown = useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if (isDragged.current) return;
|
||||
isDragged.current = true;
|
||||
event.currentTarget.setPointerCapture(event.pointerId);
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp, { once: true });
|
||||
}
|
||||
isDraggingRef.current = true;
|
||||
}, []);
|
||||
|
||||
const onMouseUp = (event) => {
|
||||
const onPointerUp = useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if (!isDragged.current) return;
|
||||
isDragged.current = false;
|
||||
if (!isDraggingRef.current) return;
|
||||
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId);
|
||||
}
|
||||
|
||||
isDraggingRef.current = false;
|
||||
updatePointPosition(event);
|
||||
}
|
||||
}, [updatePointPosition]);
|
||||
|
||||
const onMouseMove = (event) => {
|
||||
if (!isDragged.current) return;
|
||||
const onPointerMove = useCallback((event) => {
|
||||
if (!isDraggingRef.current) return;
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
updatePointPosition(event);
|
||||
}, [updatePointPosition]);
|
||||
|
||||
const onPointerCancel = useCallback((event) => {
|
||||
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId);
|
||||
}
|
||||
|
||||
const onContextMenu = (event) => {
|
||||
isDraggingRef.current = false;
|
||||
}, []);
|
||||
|
||||
const onContextMenu = useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId);
|
||||
}
|
||||
|
||||
isDraggingRef.current = false;
|
||||
removePoint();
|
||||
}
|
||||
}, [removePoint]);
|
||||
|
||||
return (
|
||||
<g
|
||||
className="distance-meter-circle"
|
||||
onMouseDown={onMouseDown}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerCancel={onPointerCancel}
|
||||
onContextMenu={onContextMenu}
|
||||
>
|
||||
<circle
|
||||
|
||||
@ -13,7 +13,7 @@ export default function DistanceMeterLine(props) {
|
||||
});
|
||||
}
|
||||
|
||||
const onMouseDown = (event) => {
|
||||
const onPointerDown = (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
const pos = getSVGCoords(event);
|
||||
@ -33,7 +33,7 @@ export default function DistanceMeterLine(props) {
|
||||
y2={end[1]}
|
||||
stroke="transparent"
|
||||
strokeWidth={3}
|
||||
onMouseDown={onMouseDown}
|
||||
onPointerDown={onPointerDown}
|
||||
/>
|
||||
<line
|
||||
x1={start[0]}
|
||||
|
||||
@ -3,7 +3,7 @@ import SettingsContext from "../../../contexts/SettingsContext";
|
||||
import Constants from "../../../helpers/constants";
|
||||
import {ElectrificationStatus} from "../../../model/electrification-status";
|
||||
import MiscHelper from "../../../helpers/miscHelper";
|
||||
import {setHoveredTrack, unsetHoveredTrack} from "../../../services/trackHoverInfoService";
|
||||
import {setHoveredTrack, setTouchClickedTrack, unsetHoveredTrack} from "../../../services/trackHoverInfoService";
|
||||
import GradientsContext from "../../../contexts/GradientsContext";
|
||||
import { TrackShape, TrackSource } from "../../../model/tracks/track";
|
||||
import {useZoomPanEmitter} from "../../../hooks/useZoomPubSub";
|
||||
@ -36,16 +36,20 @@ function StatelessTrackRenderer(props) {
|
||||
const unscaledPathRef = React.useRef(null);
|
||||
const scaledPathRef = React.useRef(null);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
setTouchClickedTrack(object);
|
||||
}, [object]);
|
||||
|
||||
const onMouseEnter = useCallback(() => {
|
||||
setHoveredTrack(object);
|
||||
}, [object]);
|
||||
|
||||
const onMouseLeave = useCallback(() => {
|
||||
unsetHoveredTrack(null);
|
||||
}, []);
|
||||
unsetHoveredTrack(object);
|
||||
}, [object]);
|
||||
|
||||
const onClick = useCallback((event) => {
|
||||
if (object.shape === TrackShape.STRAIGHT && event.detail === 2) {
|
||||
const onContextMenu = useCallback((event) => {
|
||||
if (object.shape === TrackShape.STRAIGHT) {
|
||||
event.preventDefault();
|
||||
onAlign(event);
|
||||
}
|
||||
@ -96,6 +100,7 @@ function StatelessTrackRenderer(props) {
|
||||
className="track"
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onContextMenu={onContextMenu}
|
||||
onClick={onClick}
|
||||
export-force-visible="true"
|
||||
/>
|
||||
|
||||
@ -9,7 +9,7 @@ export default function TrackHoverInfoPopup({track}) {
|
||||
<TrackHoverInfoPopupTable track={track} />
|
||||
{showAlignHint && (
|
||||
<div className="track-hover-info-popup__align">
|
||||
Double click to align the view
|
||||
Right click to align the view
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -15,14 +15,14 @@ export default function SideMenu() {
|
||||
|
||||
return (
|
||||
<div className='side-menu-wrapper'>
|
||||
<div className={sideMenuClass}>
|
||||
<aside className={sideMenuClass} inert={!sideMenuOpen}>
|
||||
<SceneryInfoButton />
|
||||
<DistanceMeterButton />
|
||||
<ExportViewButton />
|
||||
<LayersMenu />
|
||||
<LayerOptionsMenu />
|
||||
<InfoFooter />
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -42,13 +42,20 @@ function normalizeDegVector(vector) {
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeAngleDelta(delta) {
|
||||
if (delta > 180) delta -= 360;
|
||||
if (delta < -180) delta += 360;
|
||||
return delta;
|
||||
}
|
||||
|
||||
const AngleHelper = {
|
||||
degToRad,
|
||||
radToDeg,
|
||||
vectorDegToRad,
|
||||
vectorRadToDeg,
|
||||
normalizeDegAngle,
|
||||
normalizeDegVector
|
||||
normalizeDegVector,
|
||||
normalizeAngleDelta
|
||||
}
|
||||
|
||||
export default AngleHelper;
|
||||
|
||||
@ -14,7 +14,8 @@ const Constants = {
|
||||
platformMaxTilt: 0.5,
|
||||
showDebugTrackIds: false,
|
||||
mapZoomTrackDetailsThreshold: 1 / 1.44,
|
||||
mapZoomObjectDetailsThreshold: 0.5
|
||||
mapZoomObjectDetailsThreshold: 0.5,
|
||||
distanceMeterMaxMovePx: 10
|
||||
},
|
||||
parser: {
|
||||
logSceneryAfterFinished: readEnvBool('VITE_DO_LOG_SCENERY') ?? false,
|
||||
|
||||
@ -2,6 +2,11 @@ import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
const hoveredTracksStack = [];
|
||||
export const hoveredTrack$ = new BehaviorSubject(null);
|
||||
export const touchClickedTrack$ = new BehaviorSubject(null);
|
||||
|
||||
export function setTouchClickedTrack(track) {
|
||||
touchClickedTrack$.next(track);
|
||||
}
|
||||
|
||||
export function setHoveredTrack(track) {
|
||||
hoveredTracksStack.push(track);
|
||||
@ -16,4 +21,5 @@ export function unsetHoveredTrack(track) {
|
||||
export function resetHoveredTracksStack() {
|
||||
hoveredTracksStack.length = 0;
|
||||
hoveredTrack$.next(null);
|
||||
touchClickedTrack$.next(null);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user