image file info toegevoegd bij CMD+ENTER

This commit is contained in:
kodi
2026-03-13 11:37:27 +01:00
parent 05569576a7
commit 018c3dcd94
18 changed files with 726 additions and 0 deletions
+146
View File
@@ -51,6 +51,12 @@ let batchMoveState = {
destinationBase: "",
count: 0,
};
let imageViewerState = {
scale: 1,
fitScale: 1,
path: null,
resizeHandler: null,
};
let settingsState = {
activeTab: "general",
logsLoaded: false,
@@ -203,6 +209,22 @@ function pdfElements() {
};
}
function imageElements() {
return {
overlay: document.getElementById("image-modal"),
title: document.getElementById("image-title"),
fileName: document.getElementById("image-file-name"),
filePath: document.getElementById("image-file-path"),
error: document.getElementById("image-error"),
viewport: document.getElementById("image-viewport"),
image: document.getElementById("image-viewer-img"),
closeButton: document.getElementById("image-close-btn"),
zoomInButton: document.getElementById("image-zoom-in-btn"),
zoomOutButton: document.getElementById("image-zoom-out-btn"),
resetButton: document.getElementById("image-reset-btn"),
};
}
function moveElements() {
return {
overlay: document.getElementById("move-popup"),
@@ -655,6 +677,32 @@ function isPdfSelection(item) {
return (item.name || "").toLowerCase().endsWith(".pdf");
}
function isImageSelection(item) {
if (!item || item.kind !== "file") {
return false;
}
const lower = (item.name || "").toLowerCase();
return [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".avif"].some((suffix) => lower.endsWith(suffix));
}
function currentImageScale() {
return Number.isFinite(imageViewerState.scale) ? imageViewerState.scale : 1;
}
function applyImageScale() {
const image = imageElements().image;
image.style.transform = `scale(${currentImageScale()})`;
}
function resetImageViewerState() {
imageViewerState = {
scale: 1,
fitScale: 1,
path: null,
resizeHandler: null,
};
}
function currentParentPath(path) {
const normalized = (path || "").trim();
if (!normalized) {
@@ -1771,6 +1819,55 @@ function closePdfViewer() {
pdf.frame.removeAttribute("src");
}
function isImageOpen() {
return !imageElements().overlay.classList.contains("hidden");
}
function fitImageToViewport() {
const image = imageElements().image;
const viewport = imageElements().viewport;
if (!image.naturalWidth || !image.naturalHeight) {
return;
}
const widthScale = viewport.clientWidth / image.naturalWidth;
const heightScale = viewport.clientHeight / image.naturalHeight;
imageViewerState.fitScale = Math.min(widthScale, heightScale, 1);
imageViewerState.scale = imageViewerState.fitScale;
applyImageScale();
}
function adjustImageZoom(multiplier) {
if (!isImageOpen()) {
return;
}
const minScale = Math.max(imageViewerState.fitScale * 0.5, 0.1);
const maxScale = Math.max(imageViewerState.fitScale * 6, 1.5);
imageViewerState.scale = Math.min(maxScale, Math.max(minScale, currentImageScale() * multiplier));
applyImageScale();
}
function resetImageZoom() {
if (!isImageOpen()) {
return;
}
imageViewerState.scale = imageViewerState.fitScale;
applyImageScale();
}
function closeImageViewer() {
const image = imageElements();
image.overlay.classList.add("hidden");
image.error.textContent = "";
image.image.removeAttribute("src");
image.image.removeAttribute("alt");
image.image.onload = null;
image.image.onerror = null;
if (imageViewerState.resizeHandler) {
window.removeEventListener("resize", imageViewerState.resizeHandler);
}
resetImageViewerState();
}
function isInfoOpen() {
return !infoElements().overlay.classList.contains("hidden");
}
@@ -1815,6 +1912,8 @@ async function openInfo() {
renderInfoField("Content type", data.content_type);
renderInfoField("Owner", data.owner);
renderInfoField("Group", data.group);
renderInfoField("Width", data.width);
renderInfoField("Height", data.height);
} catch (err) {
elements.error.textContent = err.message;
}
@@ -2116,6 +2215,36 @@ async function openPdfViewer() {
pdf.frame.src = pdfUrl;
}
async function openImageViewer() {
const selectedItems = activePaneState().selectedItems;
if (selectedItems.length !== 1 || !isImageSelection(selectedItems[0])) {
return;
}
const selected = selectedItems[0];
const image = imageElements();
const imageUrl = `/api/files/image?${new URLSearchParams({ path: selected.path }).toString()}`;
closeImageViewer();
image.overlay.classList.remove("hidden");
image.title.textContent = "Image";
image.fileName.textContent = selected.name;
image.filePath.textContent = selected.path;
image.error.textContent = "";
image.image.alt = selected.name;
image.image.onload = () => {
fitImageToViewport();
image.image.onload = null;
};
image.image.onerror = () => {
image.error.textContent = "Image could not be displayed in this browser.";
image.image.onerror = null;
};
imageViewerState.path = selected.path;
imageViewerState.resizeHandler = () => fitImageToViewport();
window.addEventListener("resize", imageViewerState.resizeHandler);
image.image.src = imageUrl;
}
function videoPlaybackMessage(item) {
const lower = (item.name || "").toLowerCase();
if (lower.endsWith(".mkv")) {
@@ -2179,6 +2308,10 @@ function openViewer() {
return;
}
const selected = selectedItems[0];
if (isImageSelection(selected)) {
openImageViewer();
return;
}
if (isVideoSelection(selected)) {
openVideoViewer();
return;
@@ -2369,6 +2502,13 @@ function handleKeyboardShortcuts(event) {
}
return;
}
if (isImageOpen()) {
if (event.key === "Escape") {
event.preventDefault();
closeImageViewer();
}
return;
}
if (isVideoOpen()) {
if (event.key === "Escape") {
event.preventDefault();
@@ -2553,6 +2693,12 @@ function setupEvents() {
}
};
const image = imageElements();
image.closeButton.onclick = closeImageViewer;
image.zoomInButton.onclick = () => adjustImageZoom(1.2);
image.zoomOutButton.onclick = () => adjustImageZoom(1 / 1.2);
image.resetButton.onclick = resetImageZoom;
const search = searchElements();
search.closeButton.onclick = closeSearch;
search.overlay.onclick = (event) => {
+30
View File
@@ -626,6 +626,36 @@ button:disabled {
border: 1px solid var(--color-border);
}
.image-card {
width: min(1100px, calc(100vw - 28px));
}
.image-toolbar {
display: flex;
gap: 8px;
margin: 8px 0 8px 0;
}
.image-viewport {
display: flex;
align-items: center;
justify-content: center;
min-height: 420px;
height: calc(100vh - 240px);
overflow: auto;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--color-surface) 88%, black 12%);
}
.image-viewer-img {
max-width: none;
max-height: none;
transform-origin: center center;
transition: transform 120ms ease;
user-select: none;
}
.search-card {
width: min(680px, calc(100vw - 32px));
}
+18
View File
@@ -249,6 +249,24 @@
</div>
</div>
<div id="image-modal" class="popup-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="image-title">
<div class="popup-card viewer-card image-card">
<button id="image-close-btn" class="viewer-close" type="button" aria-label="Close image">X</button>
<h3 id="image-title">Image</h3>
<div id="image-file-name" class="popup-meta"></div>
<div id="image-file-path" class="popup-meta"></div>
<div class="image-toolbar">
<button id="image-zoom-out-btn" type="button">Zoom out</button>
<button id="image-reset-btn" type="button">Reset</button>
<button id="image-zoom-in-btn" type="button">Zoom in</button>
</div>
<div id="image-error" class="error"></div>
<div id="image-viewport" class="image-viewport">
<img id="image-viewer-img" class="image-viewer-img" alt="">
</div>
</div>
</div>
<div id="editor-modal" class="popup-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="editor-title">
<div class="popup-card viewer-card editor-card">
<button id="editor-close-btn" class="viewer-close" type="button" aria-label="Close editor">X</button>