From 53d488011b15828c1ee1198c505a2ed4c85f5f68 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Fri, 21 Aug 2026 23:15:41 +0100 Subject: [PATCH] Add Pan Icon and Pan Filled Icon Components for Template Preview - Introduced new PanIcon and PanFilledIcon components to enhance the TemplatePreview functionality. - Implemented panning feature in TemplatePreview, allowing users to navigate through content more intuitively. - Updated TemplatePreview to toggle panning mode with visual feedback using the new icons. - Enhanced user experience by managing pointer events and cursor styles during panning. --- assets/icons/panfilledicon.svg | 7 ++ assets/icons/panicon.svg | 7 ++ .../Dashboard/common/TemplatePreview.jsx | 104 +++++++++++++++++- src/components/Icons/PanFilledIcon.jsx | 8 ++ src/components/Icons/PanIcon.jsx | 6 + 5 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 assets/icons/panfilledicon.svg create mode 100644 assets/icons/panicon.svg create mode 100644 src/components/Icons/PanFilledIcon.jsx create mode 100644 src/components/Icons/PanIcon.jsx diff --git a/assets/icons/panfilledicon.svg b/assets/icons/panfilledicon.svg new file mode 100644 index 00000000..38617ecd --- /dev/null +++ b/assets/icons/panfilledicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/panicon.svg b/assets/icons/panicon.svg new file mode 100644 index 00000000..ba86dc9e --- /dev/null +++ b/assets/icons/panicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/Dashboard/common/TemplatePreview.jsx b/src/components/Dashboard/common/TemplatePreview.jsx index 73bcced0..9a9ea2ed 100644 --- a/src/components/Dashboard/common/TemplatePreview.jsx +++ b/src/components/Dashboard/common/TemplatePreview.jsx @@ -11,6 +11,8 @@ import { Flex, Button, Input, Select } from 'antd' import PlusIcon from '../../Icons/PlusIcon.jsx' import MinusIcon from '../../Icons/MinusIcon.jsx' import InfoCircleIcon from '../../Icons/InfoCircleIcon.jsx' +import PanIcon from '../../Icons/PanIcon.jsx' +import PanFilledIcon from '../../Icons/PanFilledIcon.jsx' import ObjectProperty from '../common/ObjectProperty.jsx' import ScrollBox from './ScrollBox.jsx' import { ApiServerContext } from '../context/ApiServerContext.jsx' @@ -39,6 +41,9 @@ const TemplatePreview = ({ const [reloadLoading, setReloadLoading] = useState(false) const [previewScale, setPreviewScale] = useState(1) const [previewType, setPreviewType] = useState('HTML') + const [panMode, setPanMode] = useState(false) + const [isPanning, setIsPanning] = useState(false) + const panStartRef = useRef({ x: 0, y: 0, scrollLeft: 0, scrollTop: 0 }) const [iframeSize, setIframeSize] = useState({ width: '100%', height: 'auto' @@ -236,6 +241,74 @@ const TemplatePreview = ({ } }, []) + useEffect(() => { + if (!panMode) { + setIsPanning(false) + } + }, [panMode]) + + useEffect(() => { + const scrollEl = scrollElementRef.current + if (!panMode || !scrollEl || previewType != 'HTML') { + return + } + + const handlePointerDown = (event) => { + if (event.pointerType === 'mouse' && event.button !== 0) { + return + } + event.preventDefault() + panStartRef.current = { + x: event.clientX, + y: event.clientY, + scrollLeft: scrollEl.scrollLeft, + scrollTop: scrollEl.scrollTop + } + setIsPanning(true) + } + + scrollEl.addEventListener('pointerdown', handlePointerDown) + return () => { + scrollEl.removeEventListener('pointerdown', handlePointerDown) + } + }, [panMode, previewType]) + + useEffect(() => { + if (!isPanning) { + return + } + + const previousCursor = document.body.style.cursor + const previousUserSelect = document.body.style.userSelect + document.body.style.cursor = 'grabbing' + document.body.style.userSelect = 'none' + + const handlePointerMove = (event) => { + const scrollEl = scrollElementRef.current + if (!scrollEl) { + return + } + const { x, y, scrollLeft, scrollTop } = panStartRef.current + scrollEl.scrollLeft = scrollLeft - (event.clientX - x) + scrollEl.scrollTop = scrollTop - (event.clientY - y) + } + + const handlePointerUp = () => { + setIsPanning(false) + } + + window.addEventListener('pointermove', handlePointerMove) + window.addEventListener('pointerup', handlePointerUp) + window.addEventListener('pointercancel', handlePointerUp) + return () => { + document.body.style.cursor = previousCursor + document.body.style.userSelect = previousUserSelect + window.removeEventListener('pointermove', handlePointerMove) + window.removeEventListener('pointerup', handlePointerUp) + window.removeEventListener('pointercancel', handlePointerUp) + } + }, [isPanning]) + return ( @@ -266,6 +339,19 @@ const TemplatePreview = ({ ) : null} +