From a34e9618369c4bdc1e4dbd1ea3f6757fde917b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Utku=20Aky=C3=BCz?= Date: Thu, 7 Aug 2025 12:45:43 +0300 Subject: [PATCH 1/3] feat: minimap width is parameterized --- .../DiffViewer/components/DiffMinimap.tsx | 71 +++++++++++++++---- .../components/VirtualizedDiffViewer.tsx | 2 + src/components/DiffViewer/types/index.ts | 2 + 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/components/DiffViewer/components/DiffMinimap.tsx b/src/components/DiffViewer/components/DiffMinimap.tsx index 3afebed..93daa5e 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"; @@ -15,6 +14,7 @@ export const DiffMinimap: React.FC = ({ rightDiff, height, onScroll, + miniMapWidth = 20, currentScrollTop, searchResults = [], currentMatchIndex = -1, @@ -73,33 +73,37 @@ export const DiffMinimap: React.FC = ({ 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); + ctx.fillRect(0, y, miniMapWidth, lineHeight); } const totalContentHeight = totalLines * ROW_HEIGHT; const viewportTop = (currentScrollTop / totalContentHeight) * height; - ctx.strokeStyle = "#2196F3"; + + ctx.strokeStyle = "rgba(33, 150, 243, 0.5)"; // #2196F3 + ctx.fillStyle = "rgba(33, 150, 243, 0.5)"; + ctx.fillRect(0, viewportTop, miniMapWidth, viewportHeight); ctx.lineWidth = 2; - ctx.strokeRect(0, viewportTop, MINIMAP_WIDTH, viewportHeight); + + ctx.strokeRect(0, viewportTop, miniMapWidth, viewportHeight); }, [leftDiff, rightDiff, height, currentScrollTop, searchResults, currentMatchIndex, drawLine, viewportHeight]); useEffect(() => { @@ -128,15 +132,37 @@ 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; + + if (canvas) { + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + if (isHovering) { + ctx.strokeStyle = "rgba(33, 150, 243, 0.5)"; // #2196F3 + ctx.fillStyle = "rgba(33, 150, 243, 0.5)"; + ctx.fillRect(0, scrollSquareTop, miniMapWidth, viewportHeight); + ctx.lineWidth = 2; + } else { + ctx.fillStyle = "rgba(33, 150, 243, 0.5)"; + ctx.fillRect(0, scrollSquareTop, miniMapWidth, viewportHeight); + } + } + + if (!isDragging.current) return; + + if (height <= 0 || totalLines <= 0) return; if (isNaN(scrollTop) || !isFinite(scrollTop)) return; @@ -160,6 +186,20 @@ 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; + + const totalContentHeight = totalLines * ROW_HEIGHT; + const viewportTop = (currentScrollTop / totalContentHeight) * height; + + ctx.fillStyle = "rgba(33, 150, 243, 0.5)"; + ctx.fillRect(0, viewportTop, miniMapWidth, viewportHeight); + }; + useEffect(() => { window.addEventListener("mouseup", handleMouseUp); return () => window.removeEventListener("mouseup", handleMouseUp); @@ -169,17 +209,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/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; } From 1ee90a1802ea39999a8b2b46565fa37394c48fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Utku=20Aky=C3=BCz?= Date: Thu, 7 Aug 2025 12:45:55 +0300 Subject: [PATCH 2/3] feat: minimap scrol box improvements and effect added on hover --- .../DiffViewer/components/DiffMinimap.tsx | 86 +++++++++++-------- 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/src/components/DiffViewer/components/DiffMinimap.tsx b/src/components/DiffViewer/components/DiffMinimap.tsx index 93daa5e..5280b6a 100644 --- a/src/components/DiffViewer/components/DiffMinimap.tsx +++ b/src/components/DiffViewer/components/DiffMinimap.tsx @@ -9,6 +9,12 @@ 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, @@ -59,18 +65,17 @@ 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, miniMapWidth / 2); @@ -87,23 +92,33 @@ export const DiffMinimap: React.FC = ({ ctx.fillStyle = SEARCH_HIGHLIGHT_COLOR; 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, miniMapWidth, 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 = "rgba(33, 150, 243, 0.5)"; // #2196F3 - ctx.fillStyle = "rgba(33, 150, 243, 0.5)"; + drawDifferencesInMinimap(ctx); + + ctx.fillStyle = color; ctx.fillRect(0, viewportTop, miniMapWidth, viewportHeight); - ctx.lineWidth = 2; + }; + + const drawMinimap = useCallback(() => { + const canvas = canvasRef.current; + if (!canvas) return; - ctx.strokeRect(0, viewportTop, miniMapWidth, viewportHeight); + 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(() => { @@ -120,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; @@ -145,18 +167,14 @@ export const DiffMinimap: React.FC = ({ const canvas = canvasRef.current; + drawMinimap(); + if (canvas) { const ctx = canvas.getContext("2d"); if (!ctx) return; - if (isHovering) { - ctx.strokeStyle = "rgba(33, 150, 243, 0.5)"; // #2196F3 - ctx.fillStyle = "rgba(33, 150, 243, 0.5)"; - ctx.fillRect(0, scrollSquareTop, miniMapWidth, viewportHeight); - ctx.lineWidth = 2; - } else { - ctx.fillStyle = "rgba(33, 150, 243, 0.5)"; - ctx.fillRect(0, scrollSquareTop, miniMapWidth, viewportHeight); + // This is active when box is hovered + drawScrollBox(ctx, MINIMAP_HOVER_SCROLL_COLOR); } } @@ -189,15 +207,13 @@ export const DiffMinimap: React.FC = ({ const handleMouseLeave = () => { const canvas = canvasRef.current; if (!canvas) return; - const ctx = canvas.getContext("2d"); if (!ctx) return; - const totalContentHeight = totalLines * ROW_HEIGHT; - const viewportTop = (currentScrollTop / totalContentHeight) * height; - - ctx.fillStyle = "rgba(33, 150, 243, 0.5)"; - ctx.fillRect(0, viewportTop, miniMapWidth, viewportHeight); + if (isDragging.current) { + isDragging.current = false; + } + drawMinimap(); // resets full state }; useEffect(() => { From 9c1e60e090cb6f2391dbf5c13c32cf164478b70b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Utku=20Aky=C3=BCz?= Date: Thu, 7 Aug 2025 12:46:59 +0300 Subject: [PATCH 3/3] style: generic scroll bar improvements --- .../DiffViewer/styles/JsonDiffCustomTheme.css | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) 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 {