webui: bring back the old per-file diff view as an option

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s46e7d05cb0615b8fk
diff --git a/webui/src/web-components/sketch-diff2-view.ts b/webui/src/web-components/sketch-diff2-view.ts
index e13948e..72be0aa 100644
--- a/webui/src/web-components/sketch-diff2-view.ts
+++ b/webui/src/web-components/sketch-diff2-view.ts
@@ -158,6 +158,12 @@
   @state()
   private error: string | null = null;
 
+  @state()
+  private selectedFile: string = ""; // Empty string means "All files"
+
+  @state()
+  private viewMode: "all" | "single" = "all";
+
   static styles = css`
     :host {
       display: flex;
@@ -189,6 +195,23 @@
       gap: 12px;
     }
 
+    .file-selector {
+      min-width: 200px;
+      padding: 8px 12px;
+      border: 1px solid var(--border-color, #ccc);
+      border-radius: 4px;
+      background-color: var(--background-color, #fff);
+      font-family: var(--font-family, system-ui, sans-serif);
+      font-size: 14px;
+      cursor: pointer;
+    }
+
+    .file-selector:focus {
+      outline: none;
+      border-color: var(--accent-color, #007acc);
+      box-shadow: 0 0 0 2px var(--accent-color-light, rgba(0, 122, 204, 0.2));
+    }
+
     sketch-diff-range-picker {
       flex: 1;
       min-width: 400px; /* Ensure minimum width for range picker */
@@ -385,6 +408,22 @@
       /* Ensure Monaco view takes full container space */
       flex: 1;
     }
+
+    /* Single file view styles */
+    .single-file-view {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      height: 100%;
+      min-height: 0;
+    }
+
+    .single-file-monaco {
+      flex: 1;
+      width: 100%;
+      height: 100%;
+      min-height: 0;
+    }
   `;
 
   @property({ attribute: false, type: Object })
@@ -515,6 +554,7 @@
               .gitService="${this.gitService}"
               @range-change="${this.handleRangeChange}"
             ></sketch-diff-range-picker>
+            ${this.renderFileSelector()}
           </div>
         </div>
       </div>
@@ -525,6 +565,29 @@
     `;
   }
 
+  renderFileSelector() {
+    if (this.files.length === 0) {
+      return html``;
+    }
+
+    return html`
+      <select
+        class="file-selector"
+        .value="${this.selectedFile}"
+        @change="${this.handleFileSelection}"
+      >
+        <option value="">All files (${this.files.length})</option>
+        ${this.files.map(
+          (file) => html`
+            <option value="${file.path}">
+              ${this.getFileDisplayName(file)}
+            </option>
+          `,
+        )}
+      </select>
+    `;
+  }
+
   renderDiffContent() {
     if (this.loading) {
       return html`<div class="loading">Loading diff...</div>`;
@@ -538,6 +601,12 @@
       return html`<sketch-diff-empty-view></sketch-diff-empty-view>`;
     }
 
+    // Render single file view if a specific file is selected
+    if (this.selectedFile && this.viewMode === "single") {
+      return this.renderSingleFileView();
+    }
+
+    // Render multi-file view
     return html`
       <div class="multi-file-diff-container">
         ${this.files.map((file, index) => this.renderFileDiff(file, index))}
@@ -581,6 +650,8 @@
       } else {
         // No files to display - reset the view to initial state
         this.selectedFilePath = "";
+        this.selectedFile = "";
+        this.viewMode = "all";
         this.fileContents.clear();
         this.fileExpandStates.clear();
       }
@@ -591,6 +662,8 @@
       this.files = [];
       // Reset the view to initial state
       this.selectedFilePath = "";
+      this.selectedFile = "";
+      this.viewMode = "all";
       this.fileContents.clear();
       this.fileExpandStates.clear();
     } finally {
@@ -902,6 +975,61 @@
   }
 
   /**
+   * Handle file selection change from the dropdown
+   */
+  handleFileSelection(event: Event) {
+    const selectElement = event.target as HTMLSelectElement;
+    const selectedValue = selectElement.value;
+    
+    this.selectedFile = selectedValue;
+    this.viewMode = selectedValue ? "single" : "all";
+    
+    // Force re-render
+    this.requestUpdate();
+  }
+
+  /**
+   * Get display name for file in the selector
+   */
+  getFileDisplayName(file: GitDiffFile): string {
+    const status = this.getFileStatusText(file.status);
+    const pathInfo = this.getPathInfo(file);
+    return `${status}: ${pathInfo}`;
+  }
+
+  /**
+   * Render single file view with full-screen Monaco editor
+   */
+  renderSingleFileView() {
+    const selectedFileData = this.files.find(f => f.path === this.selectedFile);
+    if (!selectedFileData) {
+      return html`<div class="error">Selected file not found</div>`;
+    }
+
+    const content = this.fileContents.get(this.selectedFile);
+    if (!content) {
+      return html`<div class="loading">Loading ${this.selectedFile}...</div>`;
+    }
+
+    return html`
+      <div class="single-file-view">
+        <sketch-monaco-view
+          class="single-file-monaco"
+          .originalCode="${content.original}"
+          .modifiedCode="${content.modified}"
+          .originalFilename="${selectedFileData.path}"
+          .modifiedFilename="${selectedFileData.path}"
+          ?readOnly="${!content.editable}"
+          ?editable-right="${content.editable}"
+          @monaco-comment="${this.handleMonacoComment}"
+          @monaco-save="${this.handleMonacoSave}"
+          data-file-path="${selectedFileData.path}"
+        ></sketch-monaco-view>
+      </div>
+    `;
+  }
+
+  /**
    * Refresh the diff view by reloading commits and diff data
    *
    * This is called when the Monaco diff tab is activated to ensure: