Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 89 additions & 32 deletions src/components/DiffViewer/components/DiffMinimap.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<DiffMinimapProps> = ({
leftDiff,
rightDiff,
height,
onScroll,
miniMapWidth = 20,
currentScrollTop,
searchResults = [],
currentMatchIndex = -1,
Expand Down Expand Up @@ -59,47 +65,60 @@ export const DiffMinimap: React.FC<DiffMinimapProps> = ({
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(() => {
Expand All @@ -116,6 +135,13 @@ export const DiffMinimap: React.FC<DiffMinimapProps> = ({

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;

Expand All @@ -128,15 +154,33 @@ export const DiffMinimap: React.FC<DiffMinimapProps> = ({

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;

Expand All @@ -160,6 +204,18 @@ export const DiffMinimap: React.FC<DiffMinimapProps> = ({
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);
Expand All @@ -169,17 +225,18 @@ export const DiffMinimap: React.FC<DiffMinimapProps> = ({
<div
ref={containerRef}
style={{
width: MINIMAP_WIDTH,
width: miniMapWidth,
height,
position: "relative",
cursor: "pointer",
}}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
<canvas
ref={canvasRef}
width={MINIMAP_WIDTH}
width={miniMapWidth}
height={height}
style={{
width: "100%",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const VirtualizedDiffViewer: React.FC<VirtualizedDiffViewerProps> = ({
onSearchMatch,
differOptions,
className,
miniMapWidth,
}) => {
const listRef = useRef<List>(null);
const searchTimeoutRef = useRef<NodeJS.Timeout>();
Expand Down Expand Up @@ -310,6 +311,7 @@ export const VirtualizedDiffViewer: React.FC<VirtualizedDiffViewerProps> = ({
listRef.current?.scrollTo(scrollTop);
}}
currentScrollTop={scrollTop}
miniMapWidth={miniMapWidth}
searchResults={searchState.results}
currentMatchIndex={searchState.currentIndex}
/>
Expand Down
28 changes: 28 additions & 0 deletions src/components/DiffViewer/styles/JsonDiffCustomTheme.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions src/components/DiffViewer/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface VirtualizedDiffViewerProps {
onSearchMatch?: (index: number) => void;
differOptions?: DifferOptions;
className?: string;
miniMapWidth?: number;
}

export interface DiffMinimapProps {
Expand All @@ -53,4 +54,5 @@ export interface DiffMinimapProps {
currentScrollTop: number;
searchResults?: number[];
currentMatchIndex?: number;
miniMapWidth?: number;
}