/* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { classNameFactory } from "@api/Styles"; import { FluxDispatcher, React, useRef, useState } from "@webpack/common"; import { ELEMENT_ID } from "../constants"; import { settings } from "../index"; import { waitFor } from "../utils/waitFor"; interface Vec2 { x: number, y: number; } export interface MagnifierProps { zoom: number; size: number, instance: any; } const cl = classNameFactory("vc-imgzoom-"); export const Magnifier: React.FC = ({ instance, size: initialSize, zoom: initalZoom }) => { const [ready, setReady] = useState(false); const [lensPosition, setLensPosition] = useState({ x: 0, y: 0 }); const [imagePosition, setImagePosition] = useState({ x: 0, y: 0 }); const [opacity, setOpacity] = useState(0); const isShiftDown = useRef(false); const zoom = useRef(initalZoom); const size = useRef(initialSize); const element = useRef(null); const currentVideoElementRef = useRef(null); const originalVideoElementRef = useRef(null); const imageRef = useRef(null); // since we accessing document im gonna use useLayoutEffect React.useLayoutEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Shift") { isShiftDown.current = true; } }; const onKeyUp = (e: KeyboardEvent) => { if (e.key === "Shift") { isShiftDown.current = false; } }; const syncVideos = () => { currentVideoElementRef.current!.currentTime = originalVideoElementRef.current!.currentTime; }; const updateMousePosition = (e: MouseEvent) => { if (instance.state.mouseOver && instance.state.mouseDown) { const offset = size.current / 2; const pos = { x: e.pageX, y: e.pageY }; const x = -((pos.x - element.current!.getBoundingClientRect().left) * zoom.current - offset); const y = -((pos.y - element.current!.getBoundingClientRect().top) * zoom.current - offset); setLensPosition({ x: e.x - offset, y: e.y - offset }); setImagePosition({ x, y }); setOpacity(1); } else { setOpacity(0); } }; const onMouseDown = (e: MouseEvent) => { if (instance.state.mouseOver && e.button === 0 /* left click */) { zoom.current = settings.store.zoom; size.current = settings.store.size; // close context menu if open if (document.getElementById("image-context")) { FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" }); } updateMousePosition(e); setOpacity(1); } }; const onMouseUp = () => { setOpacity(0); if (settings.store.saveZoomValues) { settings.store.zoom = zoom.current; settings.store.size = size.current; } }; const onWheel = async (e: WheelEvent) => { if (instance.state.mouseOver && instance.state.mouseDown && !isShiftDown.current) { const val = zoom.current + ((e.deltaY / 100) * (settings.store.invertScroll ? -1 : 1)) * settings.store.zoomSpeed; zoom.current = val <= 1 ? 1 : val; updateMousePosition(e); } if (instance.state.mouseOver && instance.state.mouseDown && isShiftDown.current) { const val = size.current + (e.deltaY * (settings.store.invertScroll ? -1 : 1)) * settings.store.zoomSpeed; size.current = val <= 50 ? 50 : val; updateMousePosition(e); } }; waitFor(() => instance.state.readyState === "READY", () => { const elem = document.getElementById(ELEMENT_ID) as HTMLDivElement; element.current = elem; elem.firstElementChild!.setAttribute("draggable", "false"); if (instance.props.animated) { originalVideoElementRef.current = elem!.querySelector("video")!; originalVideoElementRef.current.addEventListener("timeupdate", syncVideos); setReady(true); } else { setReady(true); } }); document.addEventListener("keydown", onKeyDown); document.addEventListener("keyup", onKeyUp); document.addEventListener("mousemove", updateMousePosition); document.addEventListener("mousedown", onMouseDown); document.addEventListener("mouseup", onMouseUp); document.addEventListener("wheel", onWheel); return () => { document.removeEventListener("keydown", onKeyDown); document.removeEventListener("keyup", onKeyUp); document.removeEventListener("mousemove", updateMousePosition); document.removeEventListener("mousedown", onMouseDown); document.removeEventListener("mouseup", onMouseUp); document.removeEventListener("wheel", onWheel); if (settings.store.saveZoomValues) { settings.store.zoom = zoom.current; settings.store.size = size.current; } }; }, []); if (!ready) return null; const box = element.current!.getBoundingClientRect(); return (
{instance.props.animated ? (
); };