0% found this document useful (0 votes)
23 views5 pages

Turn2 TSX

The document is a React component that implements a version history visualization tool, allowing users to view, compare, and merge different versions of content. It includes functionalities for zooming in and out, selecting versions, and displaying differences between selected versions. The component also supports dragging and dropping versions to create forks in the version history.

Uploaded by

Shruti Agnihotri
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views5 pages

Turn2 TSX

The document is a React component that implements a version history visualization tool, allowing users to view, compare, and merge different versions of content. It includes functionalities for zooming in and out, selecting versions, and displaying differences between selected versions. The component also supports dragging and dropping versions to create forks in the version history.

Uploaded by

Shruti Agnihotri
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

import React, { useState, useRef, useEffect } from 'react';

interface Version {
id: string;
title: string;
timestamp: string;
branch: string;
tags: string[];
content: string;
parentId?: string;
}

const initialVersions: Version[] = [


{ id: 'v1', title: 'Initial Draft', timestamp: '2023-01-01', branch: 'main',
tags: ['initial'], content: 'Initial content\nLine two\nLine three' },
{ id: 'v2', title: 'First Edit', timestamp: '2023-01-02', branch: 'main', tags:
['edit'], content: 'Edited content\nLine two\nNew line three', parentId: 'v1' },
{ id: 'v3', title: 'Feature Branch', timestamp: '2023-01-03', branch: 'feature',
tags: ['feature'], content: 'Feature content\nLine two\nLine three', parentId: 'v1'
},
{ id: 'v4', title: 'Second Edit', timestamp: '2023-01-04', branch: 'main', tags:
['edit'], content: 'More edits\nLine two\nNew line three', parentId: 'v2' },
{ id: 'v5', title: 'Feature Complete', timestamp: '2023-01-05', branch:
'feature', tags: ['feature'], content: 'Feature complete\nLine two\nFeature three',
parentId: 'v3' },
{ id: 'v6', title: 'Merge', timestamp: '2023-01-06', branch: 'main', tags:
['merge'], content: 'Merged content\nLine two\nNew line three', parentId: 'v4' },
];

const tagColors: Record<string, string> = {


initial: '#4CAF50',
edit: '#2196F3',
feature: '#9C27B0',
merge: '#FF9800',
fork: '#795548'
};

const VersionHistory: React.FC = () => {


const [zoom, setZoom] = useState(1);
const [versions, setVersions] = useState(initialVersions);
const [selectedVersions, setSelectedVersions] = useState<Version[]>([]);
const [draggedVersion, setDraggedVersion] = useState<Version | null>(null);
const svgRef = useRef<SVGSVGElement>(null);
const containerRef = useRef<HTMLDivElement>(null);

const nodeSpacing = 150;


const branchSpacing = 100;

const handleZoom = (direction: 'in' | 'out') => {


setZoom(prev => Math.max(0.5, Math.min(2, prev + (direction === 'in' ? 0.2 : -
0.2))));
};

const getBranchY = (branch: string) => {


const branches = [...new Set(versions.map(v => v.branch))];
return (branches.indexOf(branch) * branchSpacing) + 50;
};

const getNodeX = (timestamp: string) => {


const baseDate = new Date(versions[0].timestamp).getTime();
const currentDate = new Date(timestamp).getTime();
return ((currentDate - baseDate) / (1000 * 60 * 60 * 24)) * nodeSpacing + 50;
};

const handleVersionSelect = (version: Version) => {


setSelectedVersions(prev => {
if (prev.length === 2) return [version];
if (prev.find(v => v.id === version.id)) return prev.filter(v => v.id !==
version.id);
return [...prev, version];
});
};

const computeDiff = (text1: string, text2: string) => {


const lines1 = text1.split('\n');
const lines2 = text2.split('\n');
const diff: {line: string; type: 'added' | 'removed' | 'unchanged' |
'modified'}[] = [];

lines1.forEach((line, i) => {
if (!lines2[i]) {
diff.push({ line, type: 'removed' });
} else if (line !== lines2[i]) {
diff.push({ line, type: 'removed' });
diff.push({ line: lines2[i], type: 'added' });
} else {
diff.push({ line, type: 'unchanged' });
}
});

lines2.slice(lines1.length).forEach(line => {
diff.push({ line, type: 'added' });
});

return diff;
};

const DiffView: React.FC = () => {


if (selectedVersions.length !== 2) return null;

const diff = computeDiff(selectedVersions[0].content,


selectedVersions[1].content);

return (
<div className="fixed right-0 top-0 h-full w-2/3 bg-white p-4 shadow-lg
overflow-y-auto">
<h2 className="text-xl font-bold mb-2">Version Comparison</h2>
<div className="flex mb-4">
<div className="w-1/2 pr-2">
<h3>{selectedVersions[0].title}</h3>
<p className="text-gray-600">{selectedVersions[0].timestamp}</p>
</div>
<div className="w-1/2 pl-2">
<h3>{selectedVersions[1].title}</h3>
<p className="text-gray-600">{selectedVersions[1].timestamp}</p>
</div>
</div>
<div className="bg-gray-100 p-2 rounded font-mono text-sm">
{diff.map((item, i) => (
<div
key={i}
className={`py-1 ${
item.type === 'added' ? 'bg-green-100' :
item.type === 'removed' ? 'bg-red-100' :
item.type === 'modified' ? 'bg-yellow-100' :
'bg-transparent'
}`}
>
{item.type === 'added' && '+'}
{item.type === 'removed' && '-'}
{' '}{item.line}
</div>
))}
</div>
<button
className="mt-4 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-
600"
onClick={() => {
const newVersion: Version = {
id: `v${versions.length + 1}`,
title: `Merged: ${selectedVersions[0].title} + $
{selectedVersions[1].title}`,
timestamp: new Date().toISOString().split('T')[0],
branch: `${selectedVersions[1].branch}-merged`,
tags: ['merge'],
content: selectedVersions[1].content,
parentId: selectedVersions[1].id
};
setVersions(prev => [...prev, newVersion]);
setSelectedVersions([]);
}}
>
Merge Versions
</button>
</div>
);
};

const VersionNode: React.FC<{ version: Version }> = ({ version }) => {


const x = getNodeX(version.timestamp);
const y = getBranchY(version.branch);
const isSelected = selectedVersions.some(v => v.id === version.id);

const handleDragStart = (e: React.DragEvent) => {


setDraggedVersion(version);
e.dataTransfer.setData('versionId', version.id);
};

const handleDrop = (e: React.DragEvent) => {


e.preventDefault();
if (draggedVersion && draggedVersion.id !== version.id) {
const newVersion: Version = {
id: `v${versions.length + 1}`,
title: `Forked from ${version.title}`,
timestamp: new Date().toISOString().split('T')[0],
branch: `${version.branch}-fork-${Date.now()}`,
tags: ['fork'],
content: draggedVersion.content,
parentId: version.id
};
setVersions(prev => [...prev, newVersion]);
setDraggedVersion(null);
}
};

const handleDragOver = (e: React.DragEvent) => {


e.preventDefault();
};

return (
<g
draggable
onDragStart={handleDragStart}
onDrop={handleDrop}
onDragOver={handleDragOver}
onClick={() => handleVersionSelect(version)}
style={{ cursor: 'pointer' }}
>
<circle
cx={x}
cy={y}
r={15}
fill={tagColors[version.tags[0]] || '#666'}
stroke={isSelected ? '#000' : 'none'}
strokeWidth={2}
opacity={draggedVersion?.id === version.id ? 0.5 : 1}
/>
<text x={x} y={y + 25} textAnchor="middle"
fontSize="12">{version.title}</text>
{version.parentId && (
<line
x1={getNodeX(versions.find(v => v.id === version.parentId)!.timestamp)}
y1={getBranchY(versions.find(v => v.id === version.parentId)!.branch)}
x2={x}
y2={y}
stroke="#666"
strokeWidth="2"
/>
)}
</g>
);
};

return (
<div ref={containerRef} className="h-screen w-full relative overflow-hidden">
<div className="absolute top-4 left-4 z-10">
<button
onClick={() => handleZoom('in')}
className="bg-blue-500 text-white px-4 py-2 rounded mr-2 hover:bg-blue-
600"
>
Zoom In
</button>
<button
onClick={() => handleZoom('out')}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Zoom Out
</button>
</div>

<div className="w-full h-full overflow-x-auto overflow-y-hidden">


<svg
ref={svgRef}
width={versions.length * nodeSpacing * zoom + 100}
height={Math.max(...[...new Set(versions.map(v =>
v.branch))].map(getBranchY)) + 100}
style={{ transform: `scale(${zoom})`, transformOrigin: '0 0' }}
>
{versions.map(version => (
<VersionNode key={version.id} version={version} />
))}
</svg>
</div>

<DiffView />

<style jsx>{`
@media (max-width: 768px) {
.fixed {
position: absolute;
width: 100%;
height: 50%;
top: auto;
bottom: 0;
right: 0;
}
}
`}</style>
</div>
);
};

export default VersionHistory;

You might also like