Files
KLL535-ComfyUI_PNGInfo_Sidebar/web/ImageUploader.js
T
2025-02-07 21:56:17 +03:00

302 lines
9.8 KiB
JavaScript

import { app } from "../../../scripts/app.js";
import * as pngMetadata from "../../../scripts/metadata/png.js";
import ComfyUI from "./read_prompt_comfy.js";
import ForgeUI from "./read_prompt_forge.js";
export class ImageUploader {
constructor(containerEl, options = {}) {
this.container = containerEl;
this.options = { ...options };
this.currentObjectURL = null;
this.log('ImageUploader constructor');
}
///////////////////////////////////////////
init() {
this.log('ImageUploader init');
this.createElements();
this.setupEventListeners();
this.setupDragAndDrop();
}
///////////////////////////////////////////
log(...args) {
if (this.options.isDebugMode) {
console.log(...args);
}
}
showToast(severity, summary, detail) {
app.extensionManager.toast.add({
severity: severity,
summary: summary,
detail: detail,
life: 3000
});
}
///////////////////////////////////////////
createElements() {
this.log('ImageUploader createElements');
this.innerContainer = document.createElement('div');
this.innerContainer.className = 'image-uploader-container';
this.button = document.createElement('button');
this.button.className = 'image-uploader-button';
this.button.textContent = '+';
this.button.title = 'Click to load new image or drop new image';
this.fileInput = document.createElement('input');
this.fileInput.type = 'file';
//this.fileInput.accept = this.options.accept;
this.fileInput.hidden = true;
this.dropOverlay = document.createElement('div');
this.dropOverlay.className = 'drop-overlay';
//this.dropOverlay.textContent = this.options.dropZoneText;
this.innerContainer.appendChild(this.button);
this.innerContainer.appendChild(this.fileInput);
this.innerContainer.appendChild(this.dropOverlay);
this.container.appendChild(this.innerContainer);
}
///////////////////////////////////////////
setupEventListeners() {
this.log('ImageUploader setupEventListeners');
this.button.addEventListener('click', () => this.fileInput.click());
this.fileInput.addEventListener('change', this.handleFileSelect.bind(this));
}
///////////////////////////////////////////
setupDragAndDrop() {
this.log('ImageUploader setupDragAndDrop');
const preventDefault = (e) => {
e.preventDefault();
e.stopPropagation();
};
this.innerContainer.addEventListener('dragover', (e) => {
preventDefault(e);
this.dropOverlay.classList.add('active');
});
this.innerContainer.addEventListener('dragleave', (e) => {
preventDefault(e);
this.dropOverlay.classList.remove('active');
});
this.innerContainer.addEventListener('drop', (e) => {
preventDefault(e);
this.dropOverlay.classList.remove('active');
const files = e.dataTransfer.files;
if (files.length > 0) {
this.handleFileSelect({ target: { files } });
}
});
}
///////////////////////////////////////////
handleFileSelect(e) {
this.log('ImageUploader handleFileSelect');
const file = e.target.files[0];
if (!file) return;
const allowedExtensions = ['image/png'];
const isImage = allowedExtensions.includes(file.type);
if (isImage) {
this.clearPreviousImage();
this.displayImage(file);
this.addImageClickHandler();
} else {
this.showToast('error', 'PNGInfo Failed', `Unsupported file format`);
this.fileInput.value = '';
}
}
///////////////////////////////////////////
async displayImage(file) {
this.log('ImageUploader displayImage');
try {
this.currentObjectURL = URL.createObjectURL(file);
const img = document.createElement('img');
img.className = 'image-uploader-preview';
img.src = this.currentObjectURL;
img.title = 'Click to load new image or drop new image';
const metadata = await this.readMetadata(file);
const metadataContainer = this.createMetadataContainer(metadata);
this.innerContainer.replaceChildren(
img,
metadataContainer,
this.fileInput,
this.dropOverlay
);
this.addImageClickHandler();
} catch (error) {
const error_text = `Error in displayImage`;
console.error(`${error_text}: `, error);
this.showToast('error', 'PNGInfo Failed', `${error_text}`);
}
}
///////////////////////////////////////////
async readMetadata(file) {
this.log('ImageUploader readMetadata');
const baseMetadata = {
//'File name': file.name,
//'Last change': new Date(file.lastModified).toLocaleString()
};
try {
if (file.type === 'image/png') {
const pngData = await this.readPNGMetadata(file);
return { ...baseMetadata, ...pngData };
}
//not supported
/*if (file.type === 'image/jpeg') {
const exifData = await this.readEXIFMetadata(file);
return { ...baseMetadata, ...exifData };
}*/
} catch (error) {
const error_text = `Error in readMetadata`;
console.error(`${error_text}: `, error);
this.showToast('error', 'PNGInfo Failed', `${error_text}`);
}
return baseMetadata;
}
///////////////////////////////////////////
async readPNGMetadata(file) {
this.log('ImageUploader readPNGMetadata');
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const metadata = pngMetadata.getFromPngBuffer(data);
resolve(this.formatPNGMetadata(metadata));
} catch (error) {
reject(error);
}
};
reader.readAsArrayBuffer(file);
});
}
///////////////////////////////////////////
formatPNGMetadata(metadata) {
this.log('ImageUploader formatPNGMetadata');
this.log(metadata);
let result = {};
try{
if (metadata && metadata.parameters) {
const forge_parcer = new ForgeUI(metadata.parameters, {
isDebugMode: this.options.isDebugMode,
colors: this.options.colors
});
forge_parcer.run();
result = forge_parcer.output;
return result;
}
} catch (error) {
const error_text = "Error in parameters section";
result["Error"] = `${error_text}`;
}
try{
if (metadata && metadata.prompt) {
const comfy_parcer = new ComfyUI(metadata.prompt, {
isDebugMode: this.options.isDebugMode,
colors: this.options.colors
});
comfy_parcer.run();
result = comfy_parcer.output;
return result;
}
} catch (error) {
const error_text = "Error in prompt section";
result["Error"] = `${error_text}`;
}
if (Object.keys(result).length === 0) {
const error_text = "No parameters or prompt sections in metadata";
result["Error"] = `${error_text}`;
}
return result;
}
///////////////////////////////////////////
createMetadataContainer(metadata) {
this.log('ImageUploader createMetadataContainer');
const container = document.createElement('div');
container.className = 'image-metadata';
const metadataHTML = Object.entries(metadata)
.map(([header, value]) =>
header == "Error" ? `${this.options.colors.color_red}${value}` :
`${this.options.colors.color_header}${header}${this.options.colors.color_default}${value}`
).join('<br>');
container.innerHTML = metadataHTML;
return container;
}
///////////////////////////////////////////
addImageClickHandler() {
this.log('ImageUploader addImageClickHandler');
const img = this.innerContainer.querySelector('img');
if (img) {
img.addEventListener('click', () => this.fileInput.click());
}
}
///////////////////////////////////////////
clearPreviousImage() {
this.log('ImageUploader clearPreviousImage');
if (this.currentObjectURL) {
URL.revokeObjectURL(this.currentObjectURL);
this.currentObjectURL = null;
}
}
///////////////////////////////////////////
destroy() {
this.log('ImageUploader destroy');
this.clearPreviousImage();
this.innerContainer.removeEventListener('dragover', preventDefault);
this.innerContainer.removeEventListener('dragleave', preventDefault);
this.innerContainer.removeEventListener('drop', preventDefault);
this.container.innerHTML = '';
}
}