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
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ public static HtmlTag build(DirectoryComparator comparator) {
span("" + comparator.getDeletedFiles().size()).withClasses("badge", "badge-secondary"))
).withClasses("card-title", "mb-0")
).withClasses("card-header", "bg-danger"),
iff(comparator.getDeletedFiles().size() > 0, AddedOrDeletedFiles.build(comparator.getDeletedFiles(), comparator.getSrc()))
iff(comparator.getDeletedFiles().size() > 0,
DeletedFiles.build(comparator.getDeletedFiles(), comparator.getSrc()))
).withClass("card")
).withClass("col"),
div(
Expand All @@ -75,7 +76,8 @@ public static HtmlTag build(DirectoryComparator comparator) {
span("" + comparator.getAddedFiles().size()).withClasses("badge", "badge-secondary"))
).withClasses("card-title", "mb-0")
).withClasses("card-header", "bg-success"),
iff(comparator.getAddedFiles().size() > 0, AddedOrDeletedFiles.build(comparator.getAddedFiles(), comparator.getSrc()))
iff(comparator.getAddedFiles().size() > 0,
AddedFiles.build(comparator.getAddedFiles(), comparator.getDst()))
).withClass("card")
).withClass("col")
).withClasses("row", "mb-3")
Expand All @@ -88,34 +90,75 @@ private class ModifiedFiles {
public static Tag build(List<Pair<File, File>> files, DirectoryComparator comparator) {
return table(
tbody(
each(files, (id, file) -> tr(
td(comparator.getSrc().toAbsolutePath().relativize(file.first.toPath().toAbsolutePath()).toString()),
td(
div(
each(files, (id, file) -> {
String srcRelPath = comparator.getSrc().toAbsolutePath()
.relativize(file.first.toPath().toAbsolutePath()).toString();
String dstRelPath = comparator.getDst().toAbsolutePath()
.relativize(file.second.toPath().toAbsolutePath()).toString();
boolean isRenamed = !srcRelPath.equals(dstRelPath);
return tr(
td(
isRenamed
? join(text(srcRelPath), rawHtml(" &rarr; "), text(dstRelPath))
: text(srcRelPath)
),
td(
div(
iff(TreeGenerators.getInstance().hasGeneratorForFile(file.first.getAbsolutePath()), join(
a("monaco").withHref("/monaco-diff/" + id).withClasses("btn", "btn-primary", "btn-sm"),
a("classic").withHref("/vanilla-diff/" + id).withClasses("btn", "btn-primary", "btn-sm")
)
),
a("monaco-native").withHref("/monaco-native-diff/" + id).withClasses("btn", "btn-primary", "btn-sm"),
a("mergely").withHref("/mergely-diff/" + id).withClasses("btn", "btn-primary", "btn-sm"),
a("raw").withHref("/raw-diff/" + id).withClasses("btn", "btn-primary", "btn-sm")
).withClass("btn-group")
).withClasses("btn-toolbar", "justify-content-end")
)
))
div(
iff(TreeGenerators.getInstance().hasGeneratorForFile(file.first.getAbsolutePath()), join(
a("monaco").withHref("/monaco-diff/" + id).withClasses("btn", "btn-primary", "btn-sm"),
a("classic").withHref("/vanilla-diff/" + id).withClasses("btn", "btn-primary", "btn-sm")
)
),
a("monaco-native").withHref("/monaco-native-diff/" + id).withClasses("btn", "btn-primary", "btn-sm"),
a("mergely").withHref("/mergely-diff/" + id).withClasses("btn", "btn-primary", "btn-sm"),
a("raw").withHref("/raw-diff/" + id).withClasses("btn", "btn-primary", "btn-sm"),
iff(isRenamed,
button("unpair").withClasses("btn", "btn-warning", "btn-sm", "ms-2")
.attr("onclick", "unpairFiles(" + id + ")"))
).withClass("btn-group")
).withClasses("btn-toolbar", "justify-content-end")
)
);
})
)
).withClasses("table", "card-table", "table-striped", "table-condensed", "mb-0");
}
}

private static class DeletedFiles {

public static Tag build(Set<File> files, Path root) {
return table(
tbody(
each(files, file -> {
String relPath = root.relativize(file.toPath()).toString();
return tr(
td(relPath)
).attr("draggable", "true")
.attr("data-path", relPath)
.attr("data-side", "deleted")
.withClass("draggable-file");
})
)
).withClasses("table", "card-table", "table-striped", "table-condensed", "mb-0");
}
}

private static class AddedOrDeletedFiles {
private static class AddedFiles {

public static Tag build(Set<File> files, Path root) {
return table(
tbody(
each(files, file -> tr(td(root.relativize(file.toPath()).toString())))
each(files, file -> {
String relPath = root.relativize(file.toPath()).toString();
return tr(
td(relPath)
).attr("draggable", "true")
.attr("data-path", relPath)
.attr("data-side", "added")
.withClass("draggable-file");
})
)
).withClasses("table", "card-table", "table-striped", "table-condensed", "mb-0");
}
Expand All @@ -130,7 +173,12 @@ public static Tag build() {
title("GumTree"),
link().withRel("stylesheet").withType("text/css").withHref(WebDiff.BOOTSTRAP_CSS_URL),
script().withType("text/javascript").withSrc(WebDiff.BOOTSTRAP_JS_URL),
script().withType("text/javascript").withSrc("/dist/shortcuts.js")
script().withType("text/javascript").withSrc("/dist/shortcuts.js"),
script().withType("text/javascript").withSrc("/dist/dragdrop.js").attr("defer", "defer"),
rawHtml("<style>"
+ "tr.draggable-file { transition: background-color 0.15s; }"
+ "tr.drag-over { background-color: #ffc107 !important; }"
+ "</style>")
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,21 @@ public void configureSpark(final DirectoryComparator comparator, int port) {
Pair<File, File> pair = comparator.getModifiedFiles().get(id);
return readFile(pair.second.getAbsolutePath(), Charset.defaultCharset());
});
post("/pair-files", (request, response) -> {
String srcPath = request.queryParams("src");
String dstPath = request.queryParams("dst");
File srcFile = new File(comparator.getSrc().toFile(), srcPath);
File dstFile = new File(comparator.getDst().toFile(), dstPath);
comparator.pairFiles(srcFile, dstFile);
response.redirect("/list");
return "";
});
post("/unpair-files", (request, response) -> {
int id = Integer.parseInt(request.queryParams("id"));
comparator.unpairFiles(id);
response.redirect("/list");
return "";
});
get("/quit", (request, response) -> {
System.exit(0);
return "";
Expand Down
81 changes: 81 additions & 0 deletions client.diff/src/main/resources/web/dist/dragdrop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
document.addEventListener("DOMContentLoaded", function () {
var dragSource = null;
var rows = document.querySelectorAll("tr.draggable-file");

rows.forEach(function (row) {
row.style.cursor = "grab";

row.addEventListener("dragstart", function (e) {
dragSource = {
path: row.getAttribute("data-path"),
side: row.getAttribute("data-side")
};
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/plain", "");
row.style.opacity = "0.5";
});

row.addEventListener("dragend", function () {
dragSource = null;
row.style.opacity = "";
document.querySelectorAll("tr.drag-over").forEach(function (el) {
el.classList.remove("drag-over");
});
});

row.addEventListener("dragover", function (e) {
if (!dragSource || dragSource.side === row.getAttribute("data-side")) return;
e.preventDefault();
e.dataTransfer.dropEffect = "move";
});

row.addEventListener("dragenter", function (e) {
if (!dragSource || dragSource.side === row.getAttribute("data-side")) return;
e.preventDefault();
row.classList.add("drag-over");
});

row.addEventListener("dragleave", function (e) {
if (e.relatedTarget && row.contains(e.relatedTarget)) return;
row.classList.remove("drag-over");
});

row.addEventListener("drop", function (e) {
e.preventDefault();
e.stopPropagation();
row.classList.remove("drag-over");

if (!dragSource) return;

var dropSide = row.getAttribute("data-side");
var dropPath = row.getAttribute("data-path");

if (dragSource.side === dropSide) return;

var srcPath, dstPath;
if (dragSource.side === "deleted") {
srcPath = dragSource.path;
dstPath = dropPath;
} else {
srcPath = dropPath;
dstPath = dragSource.path;
}

dragSource = null;

var form = document.createElement("form");
form.method = "POST";
form.action = "/pair-files?src=" + encodeURIComponent(srcPath) + "&dst=" + encodeURIComponent(dstPath);
document.body.appendChild(form);
form.submit();
});
});
});

function unpairFiles(id) {
var form = document.createElement("form");
form.method = "POST";
form.action = "/unpair-files?id=" + id;
document.body.appendChild(form);
form.submit();
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,22 @@ public Set<File> getAddedFiles() {
return addedFiles;
}

public void pairFiles(File srcFile, File dstFile) {
if (!deletedFiles.remove(srcFile))
throw new IllegalArgumentException("File " + srcFile + " is not in the deleted files set.");
if (!addedFiles.remove(dstFile))
throw new IllegalArgumentException("File " + dstFile + " is not in the added files set.");
modifiedFiles.add(new Pair<>(srcFile, dstFile));
}

public void unpairFiles(int id) {
if (id < 0 || id >= modifiedFiles.size())
throw new IllegalArgumentException("Invalid pair id: " + id);
Pair<File, File> pair = modifiedFiles.remove(id);
deletedFiles.add(pair.first);
addedFiles.add(pair.second);
}

private File toSrcFile(String s) {
return new File(src.toFile(), s);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import com.github.gumtreediff.io.DirectoryComparator;
import org.junit.jupiter.api.Test;

import java.io.File;

import static org.junit.jupiter.api.Assertions.*;

public class TestDirectoryComparator {
Expand Down Expand Up @@ -70,4 +72,50 @@ public void testDirectoryComparatorOnFileAndFolder() {
"src/test/resources");
});
}

@Test
public void testPairAndUnpairFiles() {
DirectoryComparator cmp = new DirectoryComparator("src/test/resources/diff/left",
"src/test/resources/diff/right");
cmp.compare();
assertEquals(1, cmp.getModifiedFiles().size());
assertEquals(2, cmp.getDeletedFiles().size());
assertEquals(2, cmp.getAddedFiles().size());

File srcFile = cmp.getDeletedFiles().stream()
.filter(f -> f.getName().equals("renamedLeft")).findFirst().get();
File dstFile = cmp.getAddedFiles().stream()
.filter(f -> f.getName().equals("renamedRight")).findFirst().get();

cmp.pairFiles(srcFile, dstFile);
assertEquals(2, cmp.getModifiedFiles().size());
assertEquals(1, cmp.getDeletedFiles().size());
assertEquals(1, cmp.getAddedFiles().size());
assertEquals("renamedLeft", cmp.getModifiedFiles().get(1).first.getName());
assertEquals("renamedRight", cmp.getModifiedFiles().get(1).second.getName());

cmp.unpairFiles(1);
assertEquals(1, cmp.getModifiedFiles().size());
assertEquals(2, cmp.getDeletedFiles().size());
assertEquals(2, cmp.getAddedFiles().size());
}

@Test
public void testPairFilesInvalidArguments() {
DirectoryComparator cmp = new DirectoryComparator("src/test/resources/diff/left",
"src/test/resources/diff/right");
cmp.compare();

File validDeleted = cmp.getDeletedFiles().iterator().next();
File validAdded = cmp.getAddedFiles().iterator().next();

assertThrows(IllegalArgumentException.class, () ->
cmp.pairFiles(new File("nonexistent"), validAdded));
assertThrows(IllegalArgumentException.class, () ->
cmp.pairFiles(validDeleted, new File("nonexistent")));
assertThrows(IllegalArgumentException.class, () ->
cmp.unpairFiles(-1));
assertThrows(IllegalArgumentException.class, () ->
cmp.unpairFiles(999));
}
}