|
| 1 | +import React from 'react'; |
| 2 | +import { renderDiff } from './file_diff'; |
| 3 | + |
| 4 | +export interface ImageFile { |
| 5 | + width: number; |
| 6 | + height: number; |
| 7 | + num_bytes: number; |
| 8 | +} |
| 9 | + |
| 10 | +// XXX this type is probably imprecise. What's a "thick" vs. "thin" diff? |
| 11 | +export interface FilePair { |
| 12 | + is_image_diff: boolean; |
| 13 | + are_same_pixels: boolean; |
| 14 | + a: string; |
| 15 | + b: string; |
| 16 | + type: 'add' | 'delete' | 'move' | 'change'; // XXX check "change" |
| 17 | + image_a: ImageFile; |
| 18 | + image_b: ImageFile; |
| 19 | + idx: number; |
| 20 | + diffData?: ImageDiffData; |
| 21 | +} |
| 22 | + |
| 23 | +export interface DiffBox { |
| 24 | + width: number; |
| 25 | + height: number; |
| 26 | + left: number; |
| 27 | + top: number; |
| 28 | + bottom: number; |
| 29 | + right: number; |
| 30 | +} |
| 31 | + |
| 32 | +export interface ImageDiffData { |
| 33 | + diffBounds: DiffBox; |
| 34 | +} |
| 35 | + |
| 36 | +// A "no changes" sign which only appears when applicable. |
| 37 | +export function NoChanges(props: {filePair: any}) { |
| 38 | + const {filePair} = props; |
| 39 | + if (filePair.no_changes) { |
| 40 | + return <div className="no-changes">(File content is identical)</div>; |
| 41 | + } else if (filePair.is_image_diff && filePair.are_same_pixels) { |
| 42 | + return ( |
| 43 | + <div className="no-changes"> |
| 44 | + Pixels are the same, though file content differs (perhaps the headers are different?) |
| 45 | + </div> |
| 46 | + ); |
| 47 | + } |
| 48 | + return null; |
| 49 | +} |
| 50 | + |
| 51 | +// A side-by-side diff of source code. |
| 52 | +export function CodeDiff(props: {filePair: FilePair}) { |
| 53 | + const {filePair} = props; |
| 54 | + const codediffRef = React.useRef<HTMLDivElement>(null); |
| 55 | + |
| 56 | + React.useEffect(() => { |
| 57 | + // Either side can be empty (i.e. an add or a delete), in which case |
| 58 | + // getOrNull resolves to null |
| 59 | + var getOrNull = async (side: string, path: string) => { |
| 60 | + if (!path) return null; |
| 61 | + const data = new URLSearchParams(); |
| 62 | + data.set('path', path); |
| 63 | + const response = await fetch(`/${side}/get_contents`, { |
| 64 | + method: 'post', |
| 65 | + body: data, |
| 66 | + }); |
| 67 | + return response.text(); |
| 68 | + } |
| 69 | + |
| 70 | + const {a, b} = filePair; |
| 71 | + // Do XHRs for the contents of both sides in parallel and fill in the diff. |
| 72 | + (async () => { |
| 73 | + const [before, after] = await Promise.all([getOrNull('a', a), getOrNull('b', b)]); |
| 74 | + // Call out to codediff.js to construct the side-by-side diff. |
| 75 | + const codediffEl = codediffRef.current; |
| 76 | + if (codediffEl) { |
| 77 | + codediffEl.innerHTML = ''; |
| 78 | + codediffEl.appendChild(renderDiff(a, b, before, after)); |
| 79 | + } |
| 80 | + })().catch(e => { |
| 81 | + alert("Unable to get diff!") |
| 82 | + }); |
| 83 | + }, [filePair]); |
| 84 | + |
| 85 | + return ( |
| 86 | + <div> |
| 87 | + <NoChanges filePair={filePair} /> |
| 88 | + <div ref={codediffRef} key={filePair.idx}>Loading…</div> |
| 89 | + </div> |
| 90 | + ); |
| 91 | +} |
0 commit comments