image file info toegevoegd bij CMD+ENTER
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user