diff --git a/src/components/DiffViewer/components/DiffMinimap.tsx b/src/components/DiffViewer/components/DiffMinimap.tsx index 3afebed..5280b6a 100644 --- a/src/components/DiffViewer/components/DiffMinimap.tsx +++ b/src/components/DiffViewer/components/DiffMinimap.tsx @@ -1,7 +1,6 @@ import React, { useRef, useEffect, useCallback, useMemo } from "react"; import { DiffMinimapProps, DiffRowOrCollapsed } from "../types"; -const MINIMAP_WIDTH = 100; const ROW_HEIGHT = 20; const SEARCH_HIGHLIGHT_COLOR = "#ffd700"; const CURRENT_MATCH_COLOR = "#ff4500"; @@ -10,11 +9,18 @@ const ADD_LINE_COLOR = "#4CAF50"; const REMOVE_LINE_COLOR = "#F44336"; const MODIFY_LINE_COLOR = "#FFC107"; +// const MINIMAP_HOVER_SCROLL_COLOR = "#2196f3cc"; +// const MINIMAP_SCROLL_COLOR = "#2196f380"; + +const MINIMAP_HOVER_SCROLL_COLOR = "#7B7B7Bcc"; +const MINIMAP_SCROLL_COLOR = "#7B7B7B80"; + export const DiffMinimap: React.FC = ({ leftDiff, rightDiff, height, onScroll, + miniMapWidth = 20, currentScrollTop, searchResults = [], currentMatchIndex = -1, @@ -59,47 +65,60 @@ export const DiffMinimap: React.FC = ({ ctx.fillRect(x, y, width, ROW_HEIGHT); }, []); - const drawMinimap = useCallback(() => { - const canvas = canvasRef.current; - if (!canvas) return; - - const ctx = canvas.getContext("2d"); - if (!ctx) return; - - ctx.clearRect(0, 0, canvas.width, canvas.height); - - const totalLines = Math.max(leftDiff.length, rightDiff.length); + // Draw the differences -> This will be called in drawScrollBox method + const drawDifferencesInMinimap = (ctx: CanvasRenderingContext2D) => { const scale = height / totalLines; + if (currentMatchIndex >= 0 && searchResults[currentMatchIndex] !== undefined) { + const y = searchResults[currentMatchIndex] * scale; + const lineHeight = Math.max(1, scale); + ctx.fillStyle = CURRENT_MATCH_COLOR; + ctx.fillRect(0, y, miniMapWidth, lineHeight); + } + leftDiff.forEach((line, index) => { const y = index * scale; - drawLine(ctx, line, y, 0, MINIMAP_WIDTH / 2); + drawLine(ctx, line, y, 0, miniMapWidth / 2); }); rightDiff.forEach((line, index) => { const y = index * scale; - drawLine(ctx, line, y, MINIMAP_WIDTH / 2, MINIMAP_WIDTH / 2); + drawLine(ctx, line, y, miniMapWidth / 2, miniMapWidth / 2); }); searchResults.forEach((index) => { const y = index * scale; const lineHeight = Math.max(1, scale); ctx.fillStyle = SEARCH_HIGHLIGHT_COLOR; - ctx.fillRect(0, y, MINIMAP_WIDTH, lineHeight); + ctx.fillRect(0, y, miniMapWidth, lineHeight); }); + }; - if (currentMatchIndex >= 0 && searchResults[currentMatchIndex] !== undefined) { - const y = searchResults[currentMatchIndex] * scale; - const lineHeight = Math.max(1, scale); - ctx.fillStyle = CURRENT_MATCH_COLOR; - ctx.fillRect(0, y, MINIMAP_WIDTH, lineHeight); - } - + // Draw the scroll box and also differences in minimapo + const drawScrollBox = (ctx: CanvasRenderingContext2D, color: string) => { const totalContentHeight = totalLines * ROW_HEIGHT; const viewportTop = (currentScrollTop / totalContentHeight) * height; - ctx.strokeStyle = "#2196F3"; - ctx.lineWidth = 2; - ctx.strokeRect(0, viewportTop, MINIMAP_WIDTH, viewportHeight); + + drawDifferencesInMinimap(ctx); + + ctx.fillStyle = color; + ctx.fillRect(0, viewportTop, miniMapWidth, viewportHeight); + }; + + const drawMinimap = useCallback(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + if (!isDragging.current) { + drawScrollBox(ctx, MINIMAP_SCROLL_COLOR); + } else { + drawScrollBox(ctx, MINIMAP_HOVER_SCROLL_COLOR); + } }, [leftDiff, rightDiff, height, currentScrollTop, searchResults, currentMatchIndex, drawLine, viewportHeight]); useEffect(() => { @@ -116,6 +135,13 @@ export const DiffMinimap: React.FC = ({ if (height <= 0 || totalLines <= 0) return; + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + drawScrollBox(ctx, "rgba(33, 150, 243, 0.8)"); + const viewportCenter = relativeY - viewportHeight / 2; const scrollTop = (viewportCenter / height) * totalLines * ROW_HEIGHT; @@ -128,15 +154,33 @@ export const DiffMinimap: React.FC = ({ const handleMouseMove = useCallback( (e: React.MouseEvent) => { - if (!isDragging.current || !containerRef.current) return; - + if (!containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); - const relativeY = e.clientY - rect.top; + const relativeY = e.clientY - rect.top; // e.client y - rect top (120) + const viewportCenter = relativeY - viewportHeight / 2; // 0 when mouse is at the center of the drag square + const scrollTop = (viewportCenter / height) * totalLines * ROW_HEIGHT; - if (height <= 0 || totalLines <= 0) return; + const totalContentHeight = totalLines * ROW_HEIGHT; + const scrollSquareTop = (currentScrollTop / totalContentHeight) * height; - const viewportCenter = relativeY - viewportHeight / 2; - const scrollTop = (viewportCenter / height) * totalLines * ROW_HEIGHT; + const isHovering = relativeY > scrollSquareTop && relativeY < scrollSquareTop + viewportHeight; + + const canvas = canvasRef.current; + + drawMinimap(); + + if (canvas) { + const ctx = canvas.getContext("2d"); + if (!ctx) return; + if (isHovering) { + // This is active when box is hovered + drawScrollBox(ctx, MINIMAP_HOVER_SCROLL_COLOR); + } + } + + if (!isDragging.current) return; + + if (height <= 0 || totalLines <= 0) return; if (isNaN(scrollTop) || !isFinite(scrollTop)) return; @@ -160,6 +204,18 @@ export const DiffMinimap: React.FC = ({ isDragging.current = false; }, []); + const handleMouseLeave = () => { + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + if (isDragging.current) { + isDragging.current = false; + } + drawMinimap(); // resets full state + }; + useEffect(() => { window.addEventListener("mouseup", handleMouseUp); return () => window.removeEventListener("mouseup", handleMouseUp); @@ -169,17 +225,18 @@ export const DiffMinimap: React.FC = ({
= ({ onSearchMatch, differOptions, className, + miniMapWidth, }) => { const listRef = useRef(null); const searchTimeoutRef = useRef(); @@ -310,6 +311,7 @@ export const VirtualizedDiffViewer: React.FC = ({ listRef.current?.scrollTo(scrollTop); }} currentScrollTop={scrollTop} + miniMapWidth={miniMapWidth} searchResults={searchState.results} currentMatchIndex={searchState.currentIndex} /> diff --git a/src/components/DiffViewer/styles/JsonDiffCustomTheme.css b/src/components/DiffViewer/styles/JsonDiffCustomTheme.css index 6aba40d..891449b 100644 --- a/src/components/DiffViewer/styles/JsonDiffCustomTheme.css +++ b/src/components/DiffViewer/styles/JsonDiffCustomTheme.css @@ -5,6 +5,34 @@ padding-bottom: 12px; } +.virtual-json-diff-list-container { + --sb-track-color: #232E33; + --sb-thumb-color: #4560f8; + --sb-size: 6px; +} + +.virtual-json-diff-list-container::-webkit-scrollbar { + width: var(--sb-size) +} + +.virtual-json-diff-list-container::-webkit-scrollbar-track { + background: var(--sb-track-color); + border-radius: 3px; +} + +.virtual-json-diff-list-container::-webkit-scrollbar-thumb { + background: var(--sb-thumb-color); + border-radius: 3px; + +} + +@supports not selector(::-webkit-scrollbar) { + .virtual-json-diff-list-container { + scrollbar-color: var(--sb-thumb-color) + var(--sb-track-color); + } +} + .diff-viewer-container { button { diff --git a/src/components/DiffViewer/types/index.ts b/src/components/DiffViewer/types/index.ts index e9e6bb2..95eff8c 100644 --- a/src/components/DiffViewer/types/index.ts +++ b/src/components/DiffViewer/types/index.ts @@ -43,6 +43,7 @@ export interface VirtualizedDiffViewerProps { onSearchMatch?: (index: number) => void; differOptions?: DifferOptions; className?: string; + miniMapWidth?: number; } export interface DiffMinimapProps { @@ -53,4 +54,5 @@ export interface DiffMinimapProps { currentScrollTop: number; searchResults?: number[]; currentMatchIndex?: number; + miniMapWidth?: number; }