feat(ImagePreview): fixes & add zoom

This commit is contained in:
Creation's 2024-10-10 20:03:18 -04:00 committed by GitHub
parent 6107090950
commit a849b0503e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 174 additions and 78 deletions

View file

@ -25,6 +25,8 @@ const mimeTypes = {
mov: "video/quicktime",
};
const formatDimension = (value) => value % 1 === 0 ? value : value.toFixed(2);
function getMimeType(extension: string | undefined): [boolean, string] {
if (!extension) return [false, ""];
@ -33,26 +35,22 @@ function getMimeType(extension: string | undefined): [boolean, string] {
}
function addHoverEffect(element: HTMLElement, type: string) {
let hoverElementActual;
let isManualResizing: boolean = false;
let isOutlining: boolean = true;
if (settings.store.hoverOutline) {
if (type === "messageImages") {
hoverElementActual = element.closest('[id^="message-accessories-"]')?.querySelector("div")?.querySelector("div") || element;
if (!(hoverElementActual instanceof HTMLDivElement)) {
hoverElementActual = element;
}
element.style.border = `${settings.store.hoverOutlineSize} dotted ${settings.store.hoverOutlineColor}`;
isOutlining = false;
} else {
hoverElementActual = element.querySelector("img") || element;
element.style.outline = `${settings.store.hoverOutlineSize} dotted ${settings.store.hoverOutlineColor}`;
}
hoverElementActual.style.outline = `${settings.store.hoverOutlineSize} dotted ${settings.store.hoverOutlineColor}`;
}
const url = element.getAttribute("data-safe-src") || element.getAttribute("src") || element.getAttribute("href") || element.textContent;
if (!url) {
hoverElementActual.style.outline = "";
isOutlining ? element.style.outline = "" : element.style.border = "";
return;
}
@ -61,7 +59,7 @@ function addHoverEffect(element: HTMLElement, type: string) {
const [allowed, mimeType] = getMimeType(fileName.split(".").pop());
if (!allowed) {
hoverElementActual.style.outline = "";
isOutlining ? element.style.outline = "" : element.style.border = "";
return;
}
@ -107,16 +105,68 @@ function addHoverEffect(element: HTMLElement, type: string) {
const dimensionsOriginal = document.createElement("span");
dimensionsOriginal.classList.add("dimensions-original");
mediaElement.onload = mediaElement.onloadstart = () => {
dimensionsDiv.appendChild(dimensionsDisplaying);
dimensionsDiv.appendChild(dimensionsOriginal);
previewHeader.appendChild(dimensionsDiv);
previewDiv.appendChild(previewHeader);
previewDiv.appendChild(mediaElement);
document.body.appendChild(previewDiv);
previewDiv.style.display = "none";
const hoverDelay = settings.store.hoverDelay * 1000;
let timeout;
let isMediaLoaded = false;
const startLoading = () => {
if (isImage) {
dimensionsDisplaying.textContent = `Displaying: ${mediaElement.width}x${mediaElement.height}`;
mediaElement.onload = () => {
isMediaLoaded = true;
setMediaDimensions();
};
} else if (isVideo) {
mediaElement.onloadeddata = () => {
isMediaLoaded = true;
setMediaDimensions();
};
}
};
const setMediaDimensions = () => {
if (isManualResizing) {
return;
}
if (isImage) {
const maxHeight = window.innerHeight * 0.9;
if (mediaElement.naturalHeight > maxHeight) {
const aspectRatio = mediaElement.naturalWidth / mediaElement.naturalHeight;
mediaElement.height = maxHeight;
mediaElement.width = maxHeight * aspectRatio;
} else {
mediaElement.width = mediaElement.naturalWidth;
mediaElement.height = mediaElement.naturalHeight;
}
dimensionsDisplaying.textContent = `Displaying: ${formatDimension(mediaElement.width)}x${formatDimension(mediaElement.height)}`;
dimensionsOriginal.textContent = `Original: ${mediaElement.naturalWidth}x${mediaElement.naturalHeight}`;
} else if (isVideo) {
dimensionsDisplaying.textContent = `Displaying: ${mediaElement.videoWidth}x${mediaElement.videoHeight}`;
const maxHeight = window.innerHeight * 0.9;
if (mediaElement.videoHeight > maxHeight) {
const aspectRatio = mediaElement.videoWidth / mediaElement.videoHeight;
mediaElement.height = maxHeight;
mediaElement.width = maxHeight * aspectRatio;
} else {
mediaElement.width = mediaElement.videoWidth;
mediaElement.height = mediaElement.videoHeight;
}
dimensionsDisplaying.textContent = `Displaying: ${formatDimension(mediaElement.width)}x${formatDimension(mediaElement.height)}`;
dimensionsOriginal.textContent = `Original: ${mediaElement.videoWidth}x${mediaElement.videoHeight}`;
}
if (mediaElement.width < 200) {
const width: number = isImage ? mediaElement.width : mediaElement.videoWidth;
if (width < 200) {
previewHeader.style.flexDirection = "column";
previewHeader.style.alignItems = "center";
previewHeader.style.justifyContent = "center";
@ -127,59 +177,108 @@ function addHoverEffect(element: HTMLElement, type: string) {
}
};
dimensionsDiv.appendChild(dimensionsDisplaying);
dimensionsDiv.appendChild(dimensionsOriginal);
previewHeader.appendChild(dimensionsDiv);
previewDiv.appendChild(previewHeader);
previewDiv.appendChild(mediaElement);
document.body.appendChild(previewDiv);
const hoverDelay = settings.store.hoverDelay * 1000;
let timeout;
const showPreview = () => {
timeout = setTimeout(() => {
previewDiv.style.display = "block";
hoverElementActual.style.outline = `${settings.store.hoverOutlineSize} dotted ${settings.store.hoverOutlineColor}`;
positionPreviewDiv(previewDiv, null);
}, hoverDelay);
};
if (isMediaLoaded) {
previewDiv.style.display = "block";
positionPreviewDiv(previewDiv);
}
if (element) isOutlining ? element.style.outline = "" : element.style.border = "";
const movePreviewListener: (e: MouseEvent) => void = e => {
positionPreviewDiv(previewDiv, e);
}, hoverDelay);
};
const removePreview = () => {
clearTimeout(timeout);
timeout = null;
isMediaLoaded = false;
previewDiv.remove();
document.removeEventListener("mousemove", movePreviewListener);
if (hoverElementActual) {
hoverElementActual.style.outline = "";
if (element) {
isOutlining ? element.style.outline = "" : element.style.border = "";
}
};
element.addEventListener("mouseenter", showPreview);
const adjustPreviewSize = (e: WheelEvent) => {
if (e.ctrlKey) {
e.preventDefault();
isManualResizing = true;
const delta = e.deltaY > 0 ? -20 : 20;
let newWidth: number;
let aspectRatio: number;
if (isImage) {
newWidth = Math.max(100, mediaElement.width + delta);
aspectRatio = mediaElement.naturalWidth / mediaElement.naturalHeight;
mediaElement.width = newWidth;
mediaElement.height = newWidth / aspectRatio;
dimensionsDisplaying.textContent = `Displaying: ${formatDimension(mediaElement.width)}x${formatDimension(mediaElement.height)}`;
} else if (isVideo) {
newWidth = Math.max(100, mediaElement.clientWidth + delta);
aspectRatio = mediaElement.videoWidth / mediaElement.videoHeight;
mediaElement.style.width = `${newWidth}px`;
mediaElement.style.height = `${newWidth / aspectRatio}px`;
dimensionsDisplaying.textContent = `Displaying: ${formatDimension(newWidth)}x${formatDimension(newWidth / aspectRatio)}`;
}
positionPreviewDiv(previewDiv);
}
};
element.addEventListener("mouseenter", () => {
startLoading();
isManualResizing = false;
showPreview();
});
element.addEventListener("mouseleave", removePreview);
document.addEventListener("mousemove", movePreviewListener);
element.addEventListener("wheel", adjustPreviewSize);
eventListeners.push({ element, handler: showPreview });
eventListeners.push({ element, handler: removePreview });
eventListeners.push({ element: previewDiv, handler: movePreviewListener });
eventListeners.push({ element, handler: adjustPreviewSize });
function positionPreviewDiv(previewDiv: HTMLElement, e: MouseEvent | null) {
function positionPreviewDiv(previewDiv) {
const previewWidth = previewDiv.offsetWidth;
const previewHeight = previewDiv.offsetHeight;
const pageWidth = window.innerWidth;
const pageHeight = window.innerHeight;
const mouseX = e ? e.pageX : window.innerWidth / 2;
const mouseY = e ? e.pageY : window.innerHeight / 2;
let left = 0;
let top = 0;
let left = mouseX + 10;
let top = mouseY + 10;
switch (settings.store.previewPosition) {
case "top-left":
left = 10;
top = 10;
break;
case "top-right":
left = pageWidth - previewWidth - 10;
top = 10;
break;
case "bottom-left":
left = 10;
top = pageHeight - previewHeight - 10;
break;
case "bottom-right":
left = pageWidth - previewWidth - 10;
top = pageHeight - previewHeight - 10;
break;
case "center":
default:
left = (pageWidth - previewWidth) / 2;
top = (pageHeight - previewHeight) / 2;
break;
}
if (left < 10) {
left = 10;
}
if (top < 10) {
top = 10;
}
if (left + previewWidth > pageWidth) {
left = pageWidth - previewWidth - 10;
}
@ -189,30 +288,6 @@ function addHoverEffect(element: HTMLElement, type: string) {
previewDiv.style.left = `${left}px`;
previewDiv.style.top = `${top}px`;
const maxImageWidth = pageWidth - 20;
const maxImageHeight = pageHeight - 20;
if (isImage) {
if (mediaElement.naturalWidth > maxImageWidth || mediaElement.naturalHeight > maxImageHeight) {
const aspectRatio = mediaElement.naturalWidth / mediaElement.naturalHeight;
if (mediaElement.naturalWidth > maxImageWidth) {
mediaElement.width = maxImageWidth;
mediaElement.height = maxImageWidth / aspectRatio;
}
if (mediaElement.height > maxImageHeight) {
mediaElement.height = maxImageHeight;
mediaElement.width = maxImageHeight * aspectRatio;
}
} else {
mediaElement.width = mediaElement.naturalWidth;
mediaElement.height = mediaElement.naturalHeight;
}
}
dimensionsDisplaying.textContent = isImage ? `Displaying: ${mediaElement.width}x${mediaElement.height}` : `Displaying: ${mediaElement.videoWidth}x${mediaElement.videoHeight}`;
dimensionsOriginal.textContent = isImage ? `Original: ${mediaElement.naturalWidth}x${mediaElement.naturalHeight}` : `Original: ${mediaElement.videoWidth}x${mediaElement.videoHeight}`;
}
}
@ -292,6 +367,18 @@ const settings = definePluginSettings({
markers: [0, 1, 2, 3, 4, 5],
default: 1,
},
previewPosition: {
type: OptionType.SELECT,
description: "Preview Position",
default: "center",
options: [
{ value: "center", label: "Center" },
{ value: "top-left", label: "Top Left" },
{ value: "top-right", label: "Top Right" },
{ value: "bottom-left", label: "Bottom Left" },
{ value: "bottom-right", label: "Bottom Right" },
]
}
});
export default definePlugin({
@ -301,6 +388,9 @@ export default definePlugin({
settings: settings,
start() {
let timeout: number | undefined;
let previewDiv: HTMLDivElement | null = null;
function initialScan() {
const appContainer = document.querySelector('[class*="app-"]');
if (appContainer) {
@ -330,6 +420,11 @@ export default definePlugin({
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === "childList") {
mutation.removedNodes.forEach(removedNode => {
if (removedNode instanceof HTMLElement && lastHoveredElement && removedNode.contains(lastHoveredElement)) {
removePreview();
}
});
mutation.addedNodes.forEach(addedNode => {
if (addedNode instanceof HTMLElement) {
const element = addedNode as HTMLElement;
@ -371,6 +466,12 @@ export default definePlugin({
initialScan();
this.observer = observer;
const removePreview = () => {
if (timeout) clearTimeout(timeout);
if (previewDiv) (previewDiv as HTMLDivElement).remove();
lastHoveredElement = null;
};
},
stop() {

View file

@ -23,18 +23,13 @@
max-width: 180px;
}
.preview-div img {
max-width: 100%;
max-height: 100%;
display: block;
margin: 0 auto;
}
.preview-div video {
.preview-div img,
video {
max-width: 100%;
max-height: 100%;
display: block;
margin: 0 auto;
object-fit: contain;
}
.preview-header {
@ -60,4 +55,4 @@
.dimensions-displaying,
.dimensions-original {
display: block;
}
}