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