1import { useEffect, useState, PropsWithChildren } from 'react';
2import { parseDiff, Diff, Hunk } from 'react-diff-view';
3
4import { Snippet } from '../Snippet';
5import { SnippetContent } from '../SnippetContent';
6import { SnippetHeader } from '../SnippetHeader';
7
8const randomCommitHash = () => Math.random().toString(36).slice(2, 9);
9
10// These types come from `react-diff-view` library
11type RenderLine = {
12  oldRevision: string;
13  newRevision: string;
14  type: 'unified' | 'split';
15  hunks: object[];
16  newPath: string;
17  oldPath: string;
18};
19
20type Props = PropsWithChildren<{
21  source?: string;
22  raw?: string;
23}>;
24
25export const DiffBlock = ({ source, raw }: Props) => {
26  const [diff, setDiff] = useState<RenderLine[] | null>(raw ? parseDiff(raw) : null);
27  useEffect(() => {
28    if (source) {
29      const fetchDiffAsync = async () => {
30        const response = await fetch(source);
31        const result = await response.text();
32        setDiff(parseDiff(result));
33      };
34
35      fetchDiffAsync();
36    }
37  }, [source]);
38
39  if (!diff) {
40    return null;
41  }
42
43  const renderFile = ({
44    oldRevision = randomCommitHash(),
45    newRevision = randomCommitHash(),
46    type,
47    hunks,
48    newPath,
49  }: RenderLine) => (
50    <Snippet key={oldRevision + '-' + newRevision}>
51      <SnippetHeader title={newPath} />
52      <SnippetContent skipPadding hideOverflow>
53        <Diff viewType="unified" diffType={type} hunks={hunks}>
54          {(hunks: any[]) => hunks.map(hunk => <Hunk key={hunk.content} hunk={hunk} />)}
55        </Diff>
56      </SnippetContent>
57    </Snippet>
58  );
59
60  return <>{diff.map(renderFile)}</>;
61};
62