Toby Chui 12 часов назад
Родитель
Сommit
75870add8a
3 измененных файлов с 143 добавлено и 32 удалено
  1. 7 0
      src/web/GitApp/index.html
  2. 16 0
      src/web/GitApp/script/gitapp.css
  3. 120 32
      src/web/GitApp/script/gitapp.js

+ 7 - 0
src/web/GitApp/index.html

@@ -87,6 +87,13 @@
             <!-- Drag handle: resizes the sidebar, double click restores the default -->
             <!-- Drag handle: resizes the sidebar, double click restores the default -->
             <div class="splitter" id="splitter" title="Drag to resize"></div>
             <div class="splitter" id="splitter" title="Drag to resize"></div>
 
 
+            <!-- Middle column: the files touched by the selected commit (History tab only) -->
+            <div class="commitfilepane hidden" id="commitFilePane">
+                <div class="listhead"><span id="commitFilesLabel">No commit selected</span></div>
+                <div class="filelist" id="commitFileList"></div>
+            </div>
+            <div class="splitter hidden" id="commitFileSplitter" title="Drag to resize"></div>
+
             <div class="content">
             <div class="content">
                 <div class="contenthead">
                 <div class="contenthead">
                     <div class="path" id="diffPath">No file selected</div>
                     <div class="path" id="diffPath">No file selected</div>

+ 16 - 0
src/web/GitApp/script/gitapp.css

@@ -543,6 +543,22 @@ button i.icon {
     text-overflow: ellipsis;
     text-overflow: ellipsis;
 }
 }
 
 
+/*
+    Middle column of the History tab: the files a commit touched.
+
+    They used to be chips along the diff header, which overflowed as soon as a
+    commit touched more than a handful of files; a column of their own scrolls
+    instead. Its width is the second splitter's to control.
+*/
+.commitfilepane {
+    flex: 0 0 260px;
+    display: flex;
+    flex-direction: column;
+    min-width: 0;
+    min-height: 0;
+    background: var(--sidebar-bg);
+}
+
 /* ── Content pane ────────────────────────────────────────────────────── */
 /* ── Content pane ────────────────────────────────────────────────────── */
 
 
 .content {
 .content {

+ 120 - 32
src/web/GitApp/script/gitapp.js

@@ -24,6 +24,8 @@ var state = {
     tab: "changes",
     tab: "changes",
     commits: [],
     commits: [],
     activeCommit: null,
     activeCommit: null,
+    commitFiles: [],     //files touched by the selected commit (History tab)
+    activeCommitFile: null,
     busy: false
     busy: false
 };
 };
 
 
@@ -772,6 +774,7 @@ function renderEmptyState() {
     $("#branchName").text("—");
     $("#branchName").text("—");
     $("#changesCount").text("0");
     $("#changesCount").text("0");
     $("#fileList").empty();
     $("#fileList").empty();
+    $("#commitFileList").empty();
     $("#diffPath").text("No file selected");
     $("#diffPath").text("No file selected");
     $("#diffActions").empty();
     $("#diffActions").empty();
 
 
@@ -807,6 +810,7 @@ function loadHistory() {
         if (state.commits.length > 0) {
         if (state.commits.length > 0) {
             selectCommit(state.commits[0].hash);
             selectCommit(state.commits[0].hash);
         } else {
         } else {
+            renderCommitFiles([]);
             renderDiffMessage("No commits yet", "Make your first commit to see it here.");
             renderDiffMessage("No commits yet", "Make your first commit to see it here.");
         }
         }
     });
     });
@@ -854,34 +858,72 @@ function renderCommitList() {
 
 
 function selectCommit(hash) {
 function selectCommit(hash) {
     state.activeCommit = hash;
     state.activeCommit = hash;
+    state.activeCommitFile = null;
     renderCommitList();
     renderCommitList();
 
 
     call("status.agi", { opr: "commitfiles", repo: state.repo, hash: hash }, function(reply) {
     call("status.agi", { opr: "commitfiles", repo: state.repo, hash: hash }, function(reply) {
         if (!reply.success) {
         if (!reply.success) {
+            renderCommitFiles([]);
             renderDiffMessage("Cannot read this commit", reply.error);
             renderDiffMessage("Cannot read this commit", reply.error);
             return;
             return;
         }
         }
 
 
         if (reply.files.length === 0) {
         if (reply.files.length === 0) {
+            renderCommitFiles([]);
             renderDiffMessage("Empty commit", "This commit does not touch any file.");
             renderDiffMessage("Empty commit", "This commit does not touch any file.");
             return;
             return;
         }
         }
 
 
-        //Show the commit's file list, then open the first file's diff
-        $("#diffPath").text(reply.files.length + " changed file" + (reply.files.length === 1 ? "" : "s"));
-        var actions = $("#diffActions").empty();
-        reply.files.forEach(function(file) {
-            var button = $("<button></button>").text(file.path.split("/").pop()).attr("title", file.path);
-            button.on("click", function() {
-                showCommitDiff(hash, file.path, file.preview);
-            });
-            actions.append(button);
-        });
+        //Fill the middle column with the commit's files, then open the first one
+        renderCommitFiles(reply.files);
+        selectCommitFile(reply.files[0]);
+    });
+}
+
+/*
+    renderCommitFiles draws the middle column of the History tab. The rows reuse
+    the Changes tab's .filerow look, minus the staging checkbox — a committed
+    file has nothing left to stage.
+*/
+function renderCommitFiles(files) {
+    state.commitFiles = files;
+    $("#commitFilesLabel").text(
+        files.length === 0 ? "No changed files" :
+        files.length + " changed file" + (files.length === 1 ? "" : "s"));
+
+    var list = $("#commitFileList").empty();
+    if (files.length === 0) {
+        return;
+    }
 
 
-        showCommitDiff(hash, reply.files[0].path, reply.files[0].preview);
+    files.forEach(function(file) {
+        var row = $("<div class='filerow'></div>");
+        if (file.path === state.activeCommitFile) {
+            row.addClass("active");
+        }
+
+        //Rendered right-to-left so a long folder chain truncates on the left and
+        //the file name stays visible, as in the Changes tab.
+        row.append($("<div class='fname'></div>")
+            .attr("title", file.path)
+            .text("‎" + file.path));
+        row.append(statusMark(file));
+
+        row.on("click", function() {
+            selectCommitFile(file);
+        });
+        list.append(row);
     });
     });
 }
 }
 
 
+function selectCommitFile(file) {
+    state.activeCommitFile = file.path;
+    $("#commitFileList .filerow").removeClass("active");
+    renderCommitFiles(state.commitFiles);
+    $("#diffActions").empty();
+    showCommitDiff(state.activeCommit, file.path, file.preview);
+}
+
 function formatTime(unixSeconds) {
 function formatTime(unixSeconds) {
     var date = new Date(unixSeconds * 1000);
     var date = new Date(unixSeconds * 1000);
     return date.toLocaleDateString() + " " + date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
     return date.toLocaleDateString() + " " + date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
@@ -1939,39 +1981,60 @@ function togglePopover(selector, anchorSelector, beforeOpen) {
     }).addClass("open");
     }).addClass("open");
 }
 }
 
 
-/* ── Sidebar splitter ─────────────────────────────────────────────────── */
+/* ── Pane splitters ───────────────────────────────────────────────────── */
 
 
 var SIDEBAR_MIN_WIDTH = 220;
 var SIDEBAR_MIN_WIDTH = 220;
 var SIDEBAR_DEFAULT_WIDTH = 300;
 var SIDEBAR_DEFAULT_WIDTH = 300;
 var SIDEBAR_WIDTH_KEY = "gitapp-sidebar-width";
 var SIDEBAR_WIDTH_KEY = "gitapp-sidebar-width";
 
 
-//clampSidebarWidth keeps the sidebar usable and always leaves room for the diff.
-function clampSidebarWidth(width) {
-    var maximum = Math.max(SIDEBAR_MIN_WIDTH, Math.round($(window).width() * 0.7));
-    return Math.min(Math.max(Math.round(width), SIDEBAR_MIN_WIDTH), maximum);
+var COMMITFILES_MIN_WIDTH = 160;
+var COMMITFILES_DEFAULT_WIDTH = 260;
+var COMMITFILES_WIDTH_KEY = "gitapp-commitfiles-width";
+
+//clampPaneWidth keeps a pane usable and always leaves room for the diff.
+function clampPaneWidth(width, minimumWidth) {
+    var maximum = Math.max(minimumWidth, Math.round($(window).width() * 0.7));
+    return Math.min(Math.max(Math.round(width), minimumWidth), maximum);
 }
 }
 
 
 function applySidebarWidth(width) {
 function applySidebarWidth(width) {
-    $(".sidebar").css("flex-basis", clampSidebarWidth(width) + "px");
+    $(".sidebar").css("flex-basis", clampPaneWidth(width, SIDEBAR_MIN_WIDTH) + "px");
 }
 }
 
 
-function setupSplitter() {
-    var stored = parseInt(localStorage.getItem(SIDEBAR_WIDTH_KEY), 10);
-    applySidebarWidth(isNaN(stored) ? SIDEBAR_DEFAULT_WIDTH : stored);
+function applyCommitFilesWidth(width) {
+    $("#commitFilePane").css("flex-basis", clampPaneWidth(width, COMMITFILES_MIN_WIDTH) + "px");
+}
+
+/*
+    setupPaneSplitter wires one drag handle to the pane on its left.
+
+    Both the sidebar and the History tab's committed file column are resized the
+    same way, so the behaviour — drag, double click to restore, remember the
+    width — lives here once and is instantiated per handle.
+*/
+function setupPaneSplitter(options) {
+    var handle = $(options.handle);
+    var pane = $(options.pane);
+
+    var stored = parseInt(localStorage.getItem(options.storageKey), 10);
+    options.apply(isNaN(stored) ? options.defaultWidth : stored);
 
 
     var dragging = false;
     var dragging = false;
 
 
     function moveTo(clientX) {
     function moveTo(clientX) {
-        //The workspace may not start at x=0 once the window is scrolled or
-        //embedded, so measure against its own left edge.
-        var origin = $(".workspace").offset().left;
-        applySidebarWidth(clientX - origin);
+        //The pane may not start at x=0 once the window is scrolled or embedded,
+        //so measure against its own left edge.
+        options.apply(clientX - pane.offset().left);
     }
     }
 
 
-    $("#splitter").on("mousedown", function(event) {
+    function currentWidth() {
+        return parseInt(pane.css("flex-basis"), 10);
+    }
+
+    handle.on("mousedown", function(event) {
         event.preventDefault();
         event.preventDefault();
         dragging = true;
         dragging = true;
-        $("#splitter").addClass("dragging");
+        handle.addClass("dragging");
         $("body").addClass("resizing");
         $("body").addClass("resizing");
         closePopovers();
         closePopovers();
         closeContextMenu();
         closeContextMenu();
@@ -1990,20 +2053,38 @@ function setupSplitter() {
             return;
             return;
         }
         }
         dragging = false;
         dragging = false;
-        $("#splitter").removeClass("dragging");
+        handle.removeClass("dragging");
         $("body").removeClass("resizing");
         $("body").removeClass("resizing");
-        localStorage.setItem(SIDEBAR_WIDTH_KEY, parseInt($(".sidebar").css("flex-basis"), 10));
+        localStorage.setItem(options.storageKey, currentWidth());
     });
     });
 
 
     //Double click restores the default, matching the usual splitter convention
     //Double click restores the default, matching the usual splitter convention
-    $("#splitter").on("dblclick", function() {
-        applySidebarWidth(SIDEBAR_DEFAULT_WIDTH);
-        localStorage.setItem(SIDEBAR_WIDTH_KEY, SIDEBAR_DEFAULT_WIDTH);
+    handle.on("dblclick", function() {
+        options.apply(options.defaultWidth);
+        localStorage.setItem(options.storageKey, options.defaultWidth);
     });
     });
 
 
     //A window that shrank below the stored width must not hide the diff pane
     //A window that shrank below the stored width must not hide the diff pane
     $(window).on("resize", function() {
     $(window).on("resize", function() {
-        applySidebarWidth(parseInt($(".sidebar").css("flex-basis"), 10));
+        options.apply(currentWidth());
+    });
+}
+
+function setupSplitter() {
+    setupPaneSplitter({
+        handle: "#splitter",
+        pane: ".sidebar",
+        storageKey: SIDEBAR_WIDTH_KEY,
+        defaultWidth: SIDEBAR_DEFAULT_WIDTH,
+        apply: applySidebarWidth
+    });
+
+    setupPaneSplitter({
+        handle: "#commitFileSplitter",
+        pane: "#commitFilePane",
+        storageKey: COMMITFILES_WIDTH_KEY,
+        defaultWidth: COMMITFILES_DEFAULT_WIDTH,
+        apply: applyCommitFilesWidth
     });
     });
 }
 }
 
 
@@ -2015,10 +2096,17 @@ function switchTab(tab) {
     $("#changesPane").toggleClass("hidden", tab !== "changes");
     $("#changesPane").toggleClass("hidden", tab !== "changes");
     $("#historyPane").toggleClass("hidden", tab !== "history");
     $("#historyPane").toggleClass("hidden", tab !== "history");
 
 
+    //The committed file column belongs to the History tab only; the Changes tab
+    //already lists its files in the sidebar.
+    $("#commitFilePane").toggleClass("hidden", tab !== "history");
+    $("#commitFileSplitter").toggleClass("hidden", tab !== "history");
+
     if (tab === "history") {
     if (tab === "history") {
         loadHistory();
         loadHistory();
     } else {
     } else {
         state.activeCommit = null;
         state.activeCommit = null;
+        state.activeCommitFile = null;
+        renderCommitFiles([]);
         renderStatus();
         renderStatus();
     }
     }
 }
 }