feat: file viewer added
This commit is contained in:
@@ -58,6 +58,18 @@ function showActionSummary(action, successes, failures, firstError) {
|
||||
setStatus(base);
|
||||
}
|
||||
|
||||
function viewerElements() {
|
||||
return {
|
||||
overlay: document.getElementById("viewer-modal"),
|
||||
title: document.getElementById("viewer-title"),
|
||||
fileName: document.getElementById("viewer-file-name"),
|
||||
filePath: document.getElementById("viewer-file-path"),
|
||||
error: document.getElementById("viewer-error"),
|
||||
content: document.getElementById("viewer-content"),
|
||||
closeButton: document.getElementById("viewer-close-btn"),
|
||||
};
|
||||
}
|
||||
|
||||
async function apiRequest(method, url, body) {
|
||||
const options = { method, headers: {} };
|
||||
if (body !== undefined) {
|
||||
@@ -145,6 +157,7 @@ function updateActionButtons() {
|
||||
const hasSelection = count > 0;
|
||||
const exactlyOne = count === 1;
|
||||
const allFiles = hasSelection && selectedItems.every((item) => item.kind === "file");
|
||||
document.getElementById("view-btn").disabled = !exactlyOne || !allFiles;
|
||||
document.getElementById("rename-btn").disabled = !exactlyOne;
|
||||
document.getElementById("delete-btn").disabled = !hasSelection;
|
||||
document.getElementById("copy-btn").disabled = !allFiles;
|
||||
@@ -633,6 +646,10 @@ function isWildcardPopupOpen() {
|
||||
return !wildcardPopupElements().overlay.classList.contains("hidden");
|
||||
}
|
||||
|
||||
function isViewerOpen() {
|
||||
return !viewerElements().overlay.classList.contains("hidden");
|
||||
}
|
||||
|
||||
function escapeRegExp(text) {
|
||||
return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
||||
}
|
||||
@@ -715,6 +732,40 @@ function openWildcardPopup(mode) {
|
||||
elements.input.focus();
|
||||
}
|
||||
|
||||
function closeViewer() {
|
||||
const viewer = viewerElements();
|
||||
viewer.overlay.classList.add("hidden");
|
||||
viewer.error.textContent = "";
|
||||
viewer.content.textContent = "";
|
||||
}
|
||||
|
||||
async function openViewer() {
|
||||
const selectedItems = activePaneState().selectedItems;
|
||||
if (selectedItems.length !== 1 || selectedItems[0].kind !== "file") {
|
||||
return;
|
||||
}
|
||||
const selected = selectedItems[0];
|
||||
const viewer = viewerElements();
|
||||
viewer.overlay.classList.remove("hidden");
|
||||
viewer.title.textContent = "View";
|
||||
viewer.fileName.textContent = selected.name;
|
||||
viewer.filePath.textContent = selected.path;
|
||||
viewer.error.textContent = "";
|
||||
viewer.content.textContent = "Loading...";
|
||||
try {
|
||||
const data = await apiRequest("GET", `/api/files/view?${new URLSearchParams({ path: selected.path }).toString()}`);
|
||||
viewer.fileName.textContent = data.name;
|
||||
viewer.filePath.textContent = data.path;
|
||||
viewer.content.textContent = data.content;
|
||||
if (data.truncated) {
|
||||
viewer.error.textContent = "Preview truncated for safety";
|
||||
}
|
||||
} catch (err) {
|
||||
viewer.content.textContent = "";
|
||||
viewer.error.textContent = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
function moveCurrentRow(delta) {
|
||||
const pane = state.activePane;
|
||||
const model = paneState(pane);
|
||||
@@ -770,6 +821,13 @@ function clearSelectionForActivePane() {
|
||||
}
|
||||
|
||||
function handleKeyboardShortcuts(event) {
|
||||
if (isViewerOpen()) {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
closeViewer();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isWildcardPopupOpen()) {
|
||||
return;
|
||||
}
|
||||
@@ -851,6 +909,7 @@ function setupEvents() {
|
||||
setupPaneEvents("left");
|
||||
setupPaneEvents("right");
|
||||
document.addEventListener("keydown", handleKeyboardShortcuts);
|
||||
document.getElementById("view-btn").onclick = openViewer;
|
||||
document.getElementById("rename-btn").onclick = renameSelected;
|
||||
document.getElementById("delete-btn").onclick = deleteSelected;
|
||||
document.getElementById("copy-btn").onclick = startCopySelected;
|
||||
@@ -876,6 +935,14 @@ function setupEvents() {
|
||||
closeWildcardPopup();
|
||||
}
|
||||
};
|
||||
|
||||
const viewer = viewerElements();
|
||||
viewer.closeButton.onclick = closeViewer;
|
||||
viewer.overlay.onclick = (event) => {
|
||||
if (event.target === viewer.overlay) {
|
||||
closeViewer();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function init() {
|
||||
|
||||
@@ -88,6 +88,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="viewer-modal" class="popup-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="viewer-title">
|
||||
<div class="popup-card viewer-card">
|
||||
<button id="viewer-close-btn" class="viewer-close" type="button" aria-label="Close viewer">X</button>
|
||||
<h3 id="viewer-title">View</h3>
|
||||
<div id="viewer-file-name" class="popup-meta"></div>
|
||||
<div id="viewer-file-path" class="popup-meta"></div>
|
||||
<div id="viewer-error" class="error"></div>
|
||||
<pre id="viewer-content" class="viewer-content"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/ui/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -366,6 +366,37 @@ button:disabled {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.viewer-card {
|
||||
position: relative;
|
||||
width: min(960px, calc(100vw - 32px));
|
||||
max-height: calc(100vh - 32px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.viewer-close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
min-width: 32px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.viewer-content {
|
||||
margin: 6px 0 0 0;
|
||||
padding: 10px;
|
||||
min-height: 240px;
|
||||
max-height: calc(100vh - 180px);
|
||||
overflow: auto;
|
||||
border: 1px solid var(--border);
|
||||
background: #f8fafc;
|
||||
color: var(--text);
|
||||
font: 12px/1.45 "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.workspace {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
Reference in New Issue
Block a user