Added drag and drop image loading to the RS Image-Text node
This commit is contained in:
+295
-257
@@ -6,26 +6,26 @@ app.registerExtension({
|
||||
if (nodeData.name !== "LoadImageWithText") return;
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
const node = this;
|
||||
|
||||
|
||||
node.data = {
|
||||
mode: "read",
|
||||
prefix: "Civitai/prompt",
|
||||
text: "",
|
||||
selected_image: ""
|
||||
};
|
||||
|
||||
|
||||
const MIN_WIDTH = 360;
|
||||
const MIN_HEIGHT = 520;
|
||||
const PREVIEW_FIXED_HEIGHT = 250;
|
||||
|
||||
|
||||
node.MIN_WIDTH = MIN_WIDTH;
|
||||
node.MIN_HEIGHT = MIN_HEIGHT;
|
||||
node.setSize([MIN_WIDTH, MIN_HEIGHT]);
|
||||
|
||||
|
||||
const w_mode = node.widgets?.find(w => w.name === "mode");
|
||||
const w_prefix = node.widgets?.find(w => w.name === "filename_prefix");
|
||||
const w_text = node.widgets?.find(w => w.name === "text_input");
|
||||
@@ -44,13 +44,13 @@ app.registerExtension({
|
||||
if (w.name === "filename_prefix" && w.value) node.data.prefix = w.value;
|
||||
if (w.name === "text_input" && w.value) node.data.text = w.value;
|
||||
if (w.name === "image" && w.value) node.data.selected_image = w.value;
|
||||
|
||||
|
||||
w.hidden = true;
|
||||
w.type = "hidden";
|
||||
w.computeSize = () => [0, 0];
|
||||
w.computedHeight = 0;
|
||||
w.draw = function() {};
|
||||
|
||||
w.draw = function () {};
|
||||
|
||||
if (w.element && w.element.parentNode) {
|
||||
w.element.parentNode.removeChild(w.element);
|
||||
}
|
||||
@@ -65,169 +65,17 @@ app.registerExtension({
|
||||
});
|
||||
}
|
||||
|
||||
node.syncData = function() {
|
||||
node.syncData = function () {
|
||||
if (node.graph) {
|
||||
node.graph.setDirtyCanvas(true, true);
|
||||
node.graph.changeTracker?.dispatchEvent(new Event("change"));
|
||||
}
|
||||
};
|
||||
|
||||
node.image = new Image();
|
||||
node.imageLoaded = false;
|
||||
node.imageLoading = false;
|
||||
node.dragOver = false;
|
||||
|
||||
node.loadImage = function(imagePath) {
|
||||
if (!imagePath || node.imageLoading) return;
|
||||
|
||||
node.imageLoading = true;
|
||||
node.data.selected_image = imagePath;
|
||||
syncWidget(w_image, 'selected_image');
|
||||
|
||||
let filename = imagePath;
|
||||
let subfolder = "";
|
||||
if (imagePath.includes("/")) {
|
||||
const parts = imagePath.split("/");
|
||||
subfolder = parts[0];
|
||||
filename = parts.slice(1).join("/");
|
||||
}
|
||||
|
||||
let imgUrl = `/view?filename=${encodeURIComponent(filename)}&type=input`;
|
||||
if (subfolder) imgUrl += `&subfolder=${encodeURIComponent(subfolder)}`;
|
||||
imgUrl += `&t=${Date.now()}`;
|
||||
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
node.image = img;
|
||||
node.imageLoaded = true;
|
||||
node.imageLoading = false;
|
||||
node.syncData();
|
||||
};
|
||||
img.onerror = () => {
|
||||
node.imageLoaded = false;
|
||||
node.imageLoading = false;
|
||||
node.syncData();
|
||||
};
|
||||
img.src = imgUrl;
|
||||
};
|
||||
|
||||
const uploadFileAndLoad = async (file) => {
|
||||
if (!file || !file.type.startsWith('image/')) return false;
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
formData.append('subfolder', '');
|
||||
formData.append('type', 'input');
|
||||
|
||||
try {
|
||||
const response = await fetch('/upload/image', { method: 'POST', body: formData });
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
const imageName = result.name || result.filename;
|
||||
const subfolder = result.subfolder || '';
|
||||
const finalName = subfolder ? `${subfolder}/${imageName}` : imageName;
|
||||
node.loadImage(finalName);
|
||||
return true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[RS Image-Text] Upload error:", err);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
node.triggerFileUpload = function() {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = 'image/png, image/jpeg, image/webp, image/bmp';
|
||||
fileInput.onchange = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
await uploadFileAndLoad(file);
|
||||
fileInput.remove();
|
||||
};
|
||||
fileInput.click();
|
||||
};
|
||||
|
||||
const handleCanvasDrop = (e) => {
|
||||
if (!e.dataTransfer || !e.dataTransfer.files.length) return;
|
||||
const rect = app.canvas.canvas.getBoundingClientRect();
|
||||
const ds = app.canvas.ds;
|
||||
const nodeX = rect.left + ((node.pos[0] + ds.offset[0]) * ds.scale);
|
||||
const nodeY = rect.top + ((node.pos[1] + ds.offset[1]) * ds.scale);
|
||||
const nodeW = node.size[0] * ds.scale;
|
||||
const nodeH = node.size[1] * ds.scale;
|
||||
|
||||
if (e.clientX >= nodeX && e.clientX <= nodeX + nodeW &&
|
||||
e.clientY >= nodeY && e.clientY <= nodeY + nodeH) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const file = e.dataTransfer.files[0];
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
uploadFileAndLoad(file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleCanvasDragOver = (e) => {
|
||||
if (!e.dataTransfer || !e.dataTransfer.files.length) return;
|
||||
const rect = app.canvas.canvas.getBoundingClientRect();
|
||||
const ds = app.canvas.ds;
|
||||
const nodeX = rect.left + ((node.pos[0] + ds.offset[0]) * ds.scale);
|
||||
const nodeY = rect.top + ((node.pos[1] + ds.offset[1]) * ds.scale);
|
||||
const nodeW = node.size[0] * ds.scale;
|
||||
const nodeH = node.size[1] * ds.scale;
|
||||
|
||||
if (e.clientX >= nodeX && e.clientX <= nodeX + nodeW &&
|
||||
e.clientY >= nodeY && e.clientY <= nodeY + nodeH) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
app.canvas.canvas.addEventListener('dragover', handleCanvasDragOver, { capture: true });
|
||||
app.canvas.canvas.addEventListener('drop', handleCanvasDrop, { capture: true });
|
||||
|
||||
const updateUIFromData = () => {
|
||||
if (prefixInput) prefixInput.value = node.data.prefix || "";
|
||||
if (customTextArea) customTextArea.value = node.data.text || "";
|
||||
updateModeVisuals();
|
||||
};
|
||||
|
||||
node.onSerialize = function(o) {
|
||||
syncWidget(w_mode, 'mode');
|
||||
syncWidget(w_prefix, 'prefix');
|
||||
syncWidget(w_text, 'text');
|
||||
syncWidget(w_image, 'selected_image');
|
||||
o.data = node.data;
|
||||
};
|
||||
|
||||
node.onConfigure = function(o) {
|
||||
if (o && o.data) {
|
||||
node.data = { ...node.data, ...o.data };
|
||||
}
|
||||
|
||||
node.imageLoading = false;
|
||||
node.imageLoaded = false;
|
||||
|
||||
if (node.data.selected_image) {
|
||||
node.loadImage(node.data.selected_image);
|
||||
}
|
||||
|
||||
updateUIFromData();
|
||||
|
||||
syncWidget(w_mode, 'mode');
|
||||
syncWidget(w_prefix, 'prefix');
|
||||
syncWidget(w_text, 'text');
|
||||
syncWidget(w_image, 'selected_image');
|
||||
};
|
||||
|
||||
node.visibilityHandler = function() {
|
||||
if (!document.hidden && node.graph) {
|
||||
node.graph.setDirtyCanvas(true, true);
|
||||
node.syncData();
|
||||
}
|
||||
};
|
||||
document.addEventListener("visibilitychange", node.visibilityHandler);
|
||||
|
||||
// --- DOM UI ---
|
||||
const mainContainer = document.createElement("div");
|
||||
mainContainer.style.cssText = `
|
||||
width: 100%;
|
||||
@@ -240,9 +88,10 @@ app.registerExtension({
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
// Mode buttons
|
||||
const modeContainer = document.createElement("div");
|
||||
modeContainer.style.cssText = "display:flex; gap:8px; width:100%; pointer-events: auto;";
|
||||
|
||||
|
||||
const createModeBtn = (label, value) => {
|
||||
const btn = document.createElement("button");
|
||||
btn.textContent = label;
|
||||
@@ -259,7 +108,7 @@ app.registerExtension({
|
||||
const btnWrite = createModeBtn("✏️ WRITE to IMAGE", "write");
|
||||
modeContainer.appendChild(btnRead);
|
||||
modeContainer.appendChild(btnWrite);
|
||||
|
||||
|
||||
const updateModeVisuals = () => {
|
||||
const isRead = node.data.mode === "read";
|
||||
btnRead.style.backgroundColor = isRead ? "#4CAF50" : "#333";
|
||||
@@ -271,35 +120,37 @@ app.registerExtension({
|
||||
};
|
||||
updateModeVisuals();
|
||||
|
||||
// Prefix input
|
||||
const prefixContainer = document.createElement("div");
|
||||
prefixContainer.style.cssText = "display:flex; align-items:center; gap:8px; width:100%; pointer-events: auto;";
|
||||
|
||||
|
||||
const prefixLabel = document.createElement("label");
|
||||
prefixLabel.textContent = "PREFIX:";
|
||||
prefixLabel.style.cssText = "color:#aaa; font-size:14px; font-weight:normal; white-space:nowrap; pointer-events: auto; cursor:default;";
|
||||
|
||||
|
||||
const prefixInput = document.createElement("input");
|
||||
prefixInput.type = "text";
|
||||
prefixInput.value = node.data.prefix;
|
||||
prefixInput.style.cssText = "flex:1; height:28px; padding:0 12px; background:#222; color:#eee; border:1px solid #444; border-radius:4px; font-size:14px; box-sizing:border-box; pointer-events: auto;";
|
||||
|
||||
|
||||
prefixInput.oninput = () => {
|
||||
node.data.prefix = prefixInput.value;
|
||||
syncWidget(w_prefix, 'prefix');
|
||||
node.syncData();
|
||||
};
|
||||
|
||||
|
||||
prefixContainer.appendChild(prefixLabel);
|
||||
prefixContainer.appendChild(prefixInput);
|
||||
|
||||
// Text area
|
||||
const textContainer = document.createElement("div");
|
||||
textContainer.style.cssText = "width:100%; pointer-events: auto;";
|
||||
|
||||
|
||||
const customTextArea = document.createElement("textarea");
|
||||
customTextArea.value = node.data.text;
|
||||
customTextArea.placeholder = "Enter text here...";
|
||||
customTextArea.style.cssText = "width:100%; min-height:50px; padding:6px; background:#222; color:#eee; border:1px solid #444; border-radius:4px; resize: none; font-family:monospace; font-size:12px; box-sizing:border-box; pointer-events: auto;";
|
||||
|
||||
|
||||
customTextArea.oninput = () => {
|
||||
node.data.text = customTextArea.value;
|
||||
syncWidget(w_text, 'text');
|
||||
@@ -307,16 +158,17 @@ app.registerExtension({
|
||||
};
|
||||
textContainer.appendChild(customTextArea);
|
||||
|
||||
// Buttons
|
||||
const buttonsContainer = document.createElement("div");
|
||||
buttonsContainer.style.cssText = "display:flex; gap:8px; width:100%; pointer-events: auto;";
|
||||
|
||||
|
||||
const btnUpload = document.createElement("button");
|
||||
btnUpload.textContent = "📂 UPLOAD IMAGE";
|
||||
btnUpload.style.cssText = "flex:1; height:28px; background:#2a2a2a; color:#4CAF50; border:2px solid #4CAF50; border-radius:4px; cursor:pointer; font-weight:normal; font-size:12px; transition:all 0.2s; pointer-events: auto;";
|
||||
btnUpload.onmouseover = () => { btnUpload.style.backgroundColor = "#3a3a3a"; };
|
||||
btnUpload.onmouseout = () => { btnUpload.style.backgroundColor = "#2a2a2a"; };
|
||||
btnUpload.onclick = () => node.triggerFileUpload();
|
||||
|
||||
|
||||
const btnClear = document.createElement("button");
|
||||
btnClear.textContent = "🗑️ CLEAR TEXT";
|
||||
btnClear.style.cssText = "flex:1; height:28px; background:#2a2a2a; color:#dc3545; border:2px solid #dc3545; border-radius:4px; cursor:pointer; font-weight:normal; font-size:12px; transition:all 0.2s; pointer-events: auto;";
|
||||
@@ -328,52 +180,297 @@ app.registerExtension({
|
||||
syncWidget(w_text, 'text');
|
||||
node.syncData();
|
||||
};
|
||||
|
||||
|
||||
buttonsContainer.appendChild(btnUpload);
|
||||
buttonsContainer.appendChild(btnClear);
|
||||
|
||||
const previewWrap = document.createElement("div");
|
||||
previewWrap.style.cssText = `
|
||||
width: 100%;
|
||||
height: ${PREVIEW_FIXED_HEIGHT}px;
|
||||
background: #353535;
|
||||
border: 1px dashed #555;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
margin-top: 28px;
|
||||
`;
|
||||
|
||||
const placeholder = document.createElement("div");
|
||||
placeholder.textContent = "📁 Drop Image Here or Click to Upload";
|
||||
placeholder.style.cssText = "color:#888; font-size:14px; font-family:Arial,sans-serif; text-align:center; pointer-events:none; user-select:none;";
|
||||
previewWrap.appendChild(placeholder);
|
||||
|
||||
const previewImg = document.createElement("img");
|
||||
previewImg.style.cssText = "display:none; width:100%; height:100%; object-fit:contain; pointer-events:none; user-select:none;";
|
||||
previewImg.alt = "Image preview";
|
||||
previewWrap.appendChild(previewImg);
|
||||
|
||||
const imgInfo = document.createElement("div");
|
||||
imgInfo.style.cssText = "width:100%; text-align:center; color:#888; font-size:13px; font-family:sans-serif; pointer-events:none; user-select:none; display:none; margin-top:-1px; margin-bottom:-28px;";
|
||||
|
||||
mainContainer.appendChild(modeContainer);
|
||||
mainContainer.appendChild(prefixContainer);
|
||||
mainContainer.appendChild(textContainer);
|
||||
mainContainer.appendChild(buttonsContainer);
|
||||
|
||||
node.addDOMWidget("custom_widgets", "customtext", mainContainer);
|
||||
mainContainer.appendChild(imgInfo);
|
||||
mainContainer.appendChild(previewWrap);
|
||||
|
||||
node.addDOMWidget("custom_widgets", "customtext", mainContainer);
|
||||
node.customTextArea = customTextArea;
|
||||
|
||||
// --- LOAD / UPLOAD ---
|
||||
const buildViewUrl = (imagePath) => {
|
||||
let filename = imagePath;
|
||||
let subfolder = "";
|
||||
if (imagePath.includes("/")) {
|
||||
const parts = imagePath.split("/");
|
||||
subfolder = parts[0];
|
||||
filename = parts.slice(1).join("/");
|
||||
}
|
||||
let url = `/view?filename=${encodeURIComponent(filename)}&type=input`;
|
||||
if (subfolder) url += `&subfolder=${encodeURIComponent(subfolder)}`;
|
||||
return url + `&t=${Date.now()}`;
|
||||
};
|
||||
|
||||
// Запрос текста с бэкенда для режима Read
|
||||
let promptRequestId = 0;
|
||||
const fetchTextForRead = async (imagePath) => {
|
||||
if (node.data.mode !== "read" || !imagePath) return;
|
||||
|
||||
const requestId = ++promptRequestId;
|
||||
|
||||
customTextArea.placeholder = "⏳ Reading text from image...";
|
||||
|
||||
try {
|
||||
const response = await fetch(`/rayko/get_prompt?filename=${encodeURIComponent(imagePath)}`);
|
||||
if (requestId !== promptRequestId) return;
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
|
||||
const result = await response.json();
|
||||
if (requestId !== promptRequestId) return;
|
||||
|
||||
const text = result.prompt || "";
|
||||
node.data.text = text;
|
||||
customTextArea.value = text;
|
||||
customTextArea.placeholder = text ? "" : "⚠ No text found in this image";
|
||||
syncWidget(w_text, 'text');
|
||||
node.syncData();
|
||||
} catch (err) {
|
||||
if (requestId !== promptRequestId) return;
|
||||
customTextArea.placeholder = "⚠ Failed to read text from image";
|
||||
console.error("[RS Image-Text] Fetch text error:", err);
|
||||
}
|
||||
};
|
||||
|
||||
node.loadImage = function (imagePath) {
|
||||
if (!imagePath || node.imageLoading) return;
|
||||
|
||||
node.imageLoading = true;
|
||||
node.data.selected_image = imagePath;
|
||||
syncWidget(w_image, 'selected_image');
|
||||
|
||||
previewImg.onload = () => {
|
||||
node.imageLoading = false;
|
||||
node.imageLoaded = true;
|
||||
previewImg.style.display = "block";
|
||||
placeholder.style.display = "none";
|
||||
imgInfo.style.display = "block";
|
||||
imgInfo.textContent = `${previewImg.naturalWidth} × ${previewImg.naturalHeight}`;
|
||||
node.syncData();
|
||||
|
||||
// В режиме Read автоматически извлекаем текст
|
||||
fetchTextForRead(imagePath);
|
||||
};
|
||||
previewImg.onerror = () => {
|
||||
node.imageLoading = false;
|
||||
node.imageLoaded = false;
|
||||
previewImg.style.display = "none";
|
||||
placeholder.style.display = "block";
|
||||
imgInfo.style.display = "none";
|
||||
node.syncData();
|
||||
};
|
||||
previewImg.src = buildViewUrl(imagePath);
|
||||
};
|
||||
|
||||
const uploadFileAndLoad = async (file) => {
|
||||
if (!file || !file.type.startsWith('image/')) return false;
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
formData.append('subfolder', '');
|
||||
formData.append('type', 'input');
|
||||
|
||||
try {
|
||||
const response = await fetch('/upload/image', { method: 'POST', body: formData });
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
const imageName = result.name || result.filename;
|
||||
const subfolder = result.subfolder || '';
|
||||
const finalName = subfolder ? `${subfolder}/${imageName}` : imageName;
|
||||
node.loadImage(finalName);
|
||||
return true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[RS Image-Text] Upload error:", err);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
node.triggerFileUpload = function () {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = 'image/png, image/jpeg, image/webp, image/bmp';
|
||||
fileInput.onchange = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
await uploadFileAndLoad(file);
|
||||
fileInput.remove();
|
||||
};
|
||||
fileInput.click();
|
||||
};
|
||||
|
||||
// --- DRAG & DROP (из примера) ---
|
||||
const onDocumentDragOver = (e) => {
|
||||
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
const onDocumentDrop = (e) => {
|
||||
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
document.addEventListener('dragover', onDocumentDragOver);
|
||||
document.addEventListener('drop', onDocumentDrop);
|
||||
node._docDragOver = onDocumentDragOver;
|
||||
node._docDrop = onDocumentDrop;
|
||||
|
||||
previewWrap.addEventListener('dragover', (e) => {
|
||||
if (!e.dataTransfer || !e.dataTransfer.types.includes('Files')) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
previewWrap.style.borderColor = '#4CAF50';
|
||||
previewWrap.style.backgroundColor = '#2a2a2a';
|
||||
});
|
||||
|
||||
previewWrap.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
previewWrap.style.borderColor = '#555';
|
||||
previewWrap.style.backgroundColor = '#353535';
|
||||
});
|
||||
|
||||
previewWrap.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
previewWrap.style.borderColor = '#555';
|
||||
previewWrap.style.backgroundColor = '#353535';
|
||||
|
||||
const file = e.dataTransfer.files[0];
|
||||
if (file) {
|
||||
uploadFileAndLoad(file);
|
||||
}
|
||||
});
|
||||
|
||||
previewWrap.addEventListener('click', () => {
|
||||
node.triggerFileUpload();
|
||||
});
|
||||
// --------------------------------------
|
||||
|
||||
// Serialize / Configure
|
||||
node.onSerialize = function (o) {
|
||||
syncWidget(w_mode, 'mode');
|
||||
syncWidget(w_prefix, 'prefix');
|
||||
syncWidget(w_text, 'text');
|
||||
syncWidget(w_image, 'selected_image');
|
||||
o.data = node.data;
|
||||
};
|
||||
|
||||
node.onConfigure = function (o) {
|
||||
if (o && o.data) {
|
||||
node.data = { ...node.data, ...o.data };
|
||||
}
|
||||
|
||||
node.imageLoading = false;
|
||||
node.imageLoaded = false;
|
||||
|
||||
if (node.data.selected_image) {
|
||||
node.loadImage(node.data.selected_image);
|
||||
}
|
||||
|
||||
if (prefixInput) prefixInput.value = node.data.prefix || "";
|
||||
if (customTextArea) customTextArea.value = node.data.text || "";
|
||||
updateModeVisuals();
|
||||
|
||||
syncWidget(w_mode, 'mode');
|
||||
syncWidget(w_prefix, 'prefix');
|
||||
syncWidget(w_text, 'text');
|
||||
syncWidget(w_image, 'selected_image');
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (node.size) node.onResize([...node.size]);
|
||||
});
|
||||
};
|
||||
|
||||
node.visibilityHandler = function () {
|
||||
if (!document.hidden && node.graph) {
|
||||
node.graph.setDirtyCanvas(true, true);
|
||||
node.syncData();
|
||||
if (node.size) node.onResize([...node.size]);
|
||||
}
|
||||
};
|
||||
document.addEventListener("visibilitychange", node.visibilityHandler);
|
||||
|
||||
// Resize
|
||||
const originalOnResize = node.onResize;
|
||||
node.onResize = function(size) {
|
||||
node.onResize = function (size) {
|
||||
if (size[0] < MIN_WIDTH) size[0] = MIN_WIDTH;
|
||||
if (size[1] < MIN_HEIGHT) size[1] = MIN_HEIGHT;
|
||||
|
||||
|
||||
const titleBarHeight = 30;
|
||||
const topPadding = 18;
|
||||
const gap = 8;
|
||||
const bottomPadding = 20;
|
||||
const previewSpacing = 35;
|
||||
|
||||
const fixedElements = titleBarHeight + topPadding +
|
||||
28 + gap + 28 + gap +
|
||||
gap + 28 +
|
||||
previewSpacing +
|
||||
PREVIEW_FIXED_HEIGHT +
|
||||
bottomPadding;
|
||||
|
||||
|
||||
const fixedElements = titleBarHeight + topPadding +
|
||||
28 + gap + 28 + gap +
|
||||
gap + 28 +
|
||||
previewSpacing +
|
||||
PREVIEW_FIXED_HEIGHT +
|
||||
bottomPadding;
|
||||
|
||||
const textareaHeight = Math.max(50, size[1] - fixedElements);
|
||||
|
||||
|
||||
if (customTextArea) {
|
||||
customTextArea.style.height = textareaHeight + 'px';
|
||||
}
|
||||
|
||||
|
||||
if (originalOnResize) originalOnResize.apply(this, arguments);
|
||||
node.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
node.onDrawBackground = function(ctx) {
|
||||
|
||||
// Cleanup
|
||||
const originalOnRemoved = node.onRemoved;
|
||||
node.onRemoved = function () {
|
||||
document.removeEventListener("visibilitychange", node.visibilityHandler);
|
||||
if (node._docDragOver) document.removeEventListener('dragover', node._docDragOver);
|
||||
if (node._docDrop) document.removeEventListener('drop', node._docDrop);
|
||||
if (originalOnRemoved) originalOnRemoved.apply(this, arguments);
|
||||
};
|
||||
|
||||
// Background only (preview is now DOM)
|
||||
node.onDrawBackground = function (ctx) {
|
||||
const w = this.size[0];
|
||||
const h = this.size[1];
|
||||
const radius = 8;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(radius, 0);
|
||||
ctx.lineTo(w - radius, 0);
|
||||
@@ -385,81 +482,22 @@ app.registerExtension({
|
||||
ctx.lineTo(0, radius);
|
||||
ctx.quadraticCurveTo(0, 0, radius, 0);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.fillStyle = "#353535";
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
node.onDrawForeground = function(ctx) {
|
||||
if (this.flags.collapsed) return;
|
||||
const [w, h] = this.size;
|
||||
|
||||
const leftRightPadding = 10;
|
||||
const bottomPadding = 20;
|
||||
const previewSpacing = 35;
|
||||
|
||||
const previewY = h - bottomPadding - PREVIEW_FIXED_HEIGHT;
|
||||
const previewWidth = w - (leftRightPadding * 2);
|
||||
|
||||
if (this.imageLoaded && this.image) {
|
||||
const imgRatio = this.image.width / this.image.height;
|
||||
let drawW, drawH;
|
||||
|
||||
if (previewWidth / PREVIEW_FIXED_HEIGHT > imgRatio) {
|
||||
drawH = PREVIEW_FIXED_HEIGHT;
|
||||
drawW = drawH * imgRatio;
|
||||
} else {
|
||||
drawW = previewWidth;
|
||||
drawH = drawW / imgRatio;
|
||||
}
|
||||
|
||||
const drawX = leftRightPadding + (previewWidth - drawW) / 2;
|
||||
const drawY = previewY + (PREVIEW_FIXED_HEIGHT - drawH) / 2;
|
||||
|
||||
ctx.drawImage(this.image, drawX, drawY, drawW, drawH);
|
||||
ctx.strokeStyle = this.dragOver ? "#4CAF50" : "#444";
|
||||
ctx.lineWidth = this.dragOver ? 2 : 1;
|
||||
ctx.strokeRect(drawX, drawY, drawW, drawH);
|
||||
|
||||
ctx.fillStyle = "#888";
|
||||
ctx.font = "15px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "bottom";
|
||||
ctx.fillText(`${this.image.width} × ${this.image.height}`, w / 2, previewY - 5);
|
||||
} else {
|
||||
ctx.fillStyle = "#1a1a1a";
|
||||
ctx.fillRect(leftRightPadding, previewY, previewWidth, PREVIEW_FIXED_HEIGHT);
|
||||
ctx.strokeStyle = this.dragOver ? "#4CAF50" : "#333";
|
||||
ctx.lineWidth = this.dragOver ? 2 : 1;
|
||||
ctx.strokeRect(leftRightPadding, previewY, previewWidth, PREVIEW_FIXED_HEIGHT);
|
||||
|
||||
ctx.fillStyle = "#555";
|
||||
ctx.font = "14px Arial";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText("Drop Image Here", w / 2, previewY + PREVIEW_FIXED_HEIGHT / 2);
|
||||
}
|
||||
};
|
||||
|
||||
node.onMouseMove = function() {};
|
||||
node.onMouseDown = function() { return false; };
|
||||
|
||||
const originalOnRemoved = node.onRemoved;
|
||||
node.onRemoved = function() {
|
||||
document.removeEventListener("visibilitychange", node.visibilityHandler);
|
||||
app.canvas.canvas.removeEventListener('dragover', handleCanvasDragOver, { capture: true });
|
||||
app.canvas.canvas.removeEventListener('drop', handleCanvasDrop, { capture: true });
|
||||
if (originalOnRemoved) originalOnRemoved.apply(this, arguments);
|
||||
};
|
||||
|
||||
|
||||
node.onDrawForeground = function () {};
|
||||
node.onMouseMove = function () {};
|
||||
node.onMouseDown = function () { return false; };
|
||||
|
||||
if (node.data.selected_image) {
|
||||
node.loadImage(node.data.selected_image);
|
||||
}
|
||||
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
node.onResize([MIN_WIDTH, MIN_HEIGHT]);
|
||||
});
|
||||
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user