|
|
import type { SerializedGraph, BadLinksData } from "typings/index.js";
|
|
|
import { fixBadLinks } from "../common/link_fixer.js";
|
|
|
import { getPngMetadata } from "scripts/pnginfo.js";
|
|
|
|
|
|
function wait(ms = 16, value?: any) {
|
|
|
return new Promise((resolve) => {
|
|
|
setTimeout(() => {
|
|
|
resolve(value);
|
|
|
}, ms);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const logger = {
|
|
|
logTo: console as Console | HTMLElement,
|
|
|
log: (...args: any[]) => {
|
|
|
logger.logTo === console
|
|
|
? console.log(...args)
|
|
|
: ((logger.logTo as HTMLElement).innerText += args.join(",") + "\n");
|
|
|
},
|
|
|
};
|
|
|
|
|
|
const findBadLinksLogger = {
|
|
|
log: async (...args: any[]) => {
|
|
|
logger.log(...args);
|
|
|
|
|
|
},
|
|
|
};
|
|
|
|
|
|
export class LinkPage {
|
|
|
private containerEl: HTMLDivElement;
|
|
|
private figcaptionEl: HTMLElement;
|
|
|
private btnFix: HTMLButtonElement;
|
|
|
private outputeMessageEl: HTMLDivElement;
|
|
|
private outputImageEl: HTMLImageElement;
|
|
|
|
|
|
private file?: File | Blob;
|
|
|
private graph?: SerializedGraph;
|
|
|
private graphResults?: BadLinksData;
|
|
|
private graphFinalResults?: BadLinksData;
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
this.containerEl = document.querySelector(".box")!;
|
|
|
this.figcaptionEl = document.querySelector("figcaption")!;
|
|
|
this.outputeMessageEl = document.querySelector(".output")!;
|
|
|
this.outputImageEl = document.querySelector(".output-image")!;
|
|
|
this.btnFix = document.querySelector(".btn-fix")!;
|
|
|
|
|
|
|
|
|
document.addEventListener(
|
|
|
"dragover",
|
|
|
(e) => {
|
|
|
e.preventDefault();
|
|
|
},
|
|
|
false,
|
|
|
);
|
|
|
document.addEventListener("drop", (e) => {
|
|
|
this.onDrop(e);
|
|
|
});
|
|
|
this.btnFix.addEventListener("click", (e) => {
|
|
|
this.onFixClick(e);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
private async onFixClick(e: MouseEvent) {
|
|
|
if (!this.graphResults || !this.graph) {
|
|
|
this.updateUi("⛔ Fix button click without results.");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
let graphFinalResults = fixBadLinks(this.graph, true);
|
|
|
|
|
|
graphFinalResults = fixBadLinks(graphFinalResults.graph, true);
|
|
|
|
|
|
if (graphFinalResults.patched || graphFinalResults.deleted) {
|
|
|
graphFinalResults = fixBadLinks(graphFinalResults.graph, true);
|
|
|
}
|
|
|
this.graphFinalResults = graphFinalResults;
|
|
|
|
|
|
await this.saveFixedWorkflow();
|
|
|
|
|
|
if (graphFinalResults.hasBadLinks) {
|
|
|
this.updateUi(
|
|
|
"⛔ Hmm... Still detecting bad links. Can you file an issue at https://github.com/rgthree/rgthree-comfy/issues with your image/workflow.",
|
|
|
);
|
|
|
} else {
|
|
|
this.updateUi(
|
|
|
"✅ Workflow fixed.<br><br><small>Please load new saved workflow json and double check linking and execution.</small>",
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private async onDrop(event: DragEvent) {
|
|
|
if (!event.dataTransfer) {
|
|
|
return;
|
|
|
}
|
|
|
this.reset();
|
|
|
|
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
|
|
|
|
|
|
|
if (event.dataTransfer.files.length && event.dataTransfer.files?.[0]?.type !== "image/bmp") {
|
|
|
await this.handleFile(event.dataTransfer.files[0]!);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
const validTypes = ["text/uri-list", "text/x-moz-url"];
|
|
|
const match = [...event.dataTransfer.types].find((t) => validTypes.find((v) => t === v));
|
|
|
if (match) {
|
|
|
const uri = event.dataTransfer.getData(match)?.split("\n")?.[0];
|
|
|
if (uri) {
|
|
|
await this.handleFile(await (await fetch(uri)).blob());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
reset() {
|
|
|
this.file = undefined;
|
|
|
this.graph = undefined;
|
|
|
this.graphResults = undefined;
|
|
|
this.graphFinalResults = undefined;
|
|
|
this.updateUi();
|
|
|
}
|
|
|
|
|
|
private updateUi(msg?: string) {
|
|
|
this.outputeMessageEl.innerHTML = "";
|
|
|
if (this.file && !this.containerEl.classList.contains("-has-file")) {
|
|
|
this.containerEl.classList.add("-has-file");
|
|
|
this.figcaptionEl.innerHTML = (this.file as File).name || this.file.type;
|
|
|
if (this.file.type === "application/json") {
|
|
|
this.outputImageEl.src = "icon_file_json.png";
|
|
|
} else {
|
|
|
const reader = new FileReader();
|
|
|
reader.onload = () => (this.outputImageEl.src = reader.result as string);
|
|
|
reader.readAsDataURL(this.file);
|
|
|
}
|
|
|
} else if (!this.file && this.containerEl.classList.contains("-has-file")) {
|
|
|
this.containerEl.classList.remove("-has-file");
|
|
|
this.outputImageEl.src = "";
|
|
|
this.outputImageEl.removeAttribute("src");
|
|
|
}
|
|
|
|
|
|
if (this.graphResults) {
|
|
|
this.containerEl.classList.add("-has-results");
|
|
|
if (!this.graphResults.patched && !this.graphResults.deleted) {
|
|
|
this.outputeMessageEl.innerHTML = "✅ No bad links detected in the workflow.";
|
|
|
} else {
|
|
|
this.containerEl.classList.add("-has-fixable-results");
|
|
|
this.outputeMessageEl.innerHTML = `⚠️ Found ${this.graphResults.patched} links to fix, and ${this.graphResults.deleted} to be removed.`;
|
|
|
}
|
|
|
} else {
|
|
|
this.containerEl.classList.remove("-has-results");
|
|
|
this.containerEl.classList.remove("-has-fixable-results");
|
|
|
}
|
|
|
|
|
|
if (msg) {
|
|
|
this.outputeMessageEl.innerHTML = msg;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private async handleFile(file: File | Blob) {
|
|
|
this.file = file;
|
|
|
this.updateUi();
|
|
|
|
|
|
let workflow: string | undefined | null = null;
|
|
|
if (file.type.startsWith("image/")) {
|
|
|
const pngInfo = await getPngMetadata(file);
|
|
|
workflow = pngInfo?.workflow;
|
|
|
} else if (
|
|
|
file.type === "application/json" ||
|
|
|
(file instanceof File && file.name.endsWith(".json"))
|
|
|
) {
|
|
|
workflow = await new Promise((resolve) => {
|
|
|
const reader = new FileReader();
|
|
|
reader.onload = () => {
|
|
|
resolve(reader.result as string);
|
|
|
};
|
|
|
reader.readAsText(file);
|
|
|
});
|
|
|
}
|
|
|
if (!workflow) {
|
|
|
this.updateUi("⛔ No workflow found in dropped item.");
|
|
|
} else {
|
|
|
try {
|
|
|
this.graph = JSON.parse(workflow);
|
|
|
} catch (e) {
|
|
|
this.graph = undefined;
|
|
|
}
|
|
|
if (!this.graph) {
|
|
|
this.updateUi("⛔ Invalid workflow found in dropped item.");
|
|
|
} else {
|
|
|
this.loadGraphData(this.graph);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private async loadGraphData(graphData: SerializedGraph) {
|
|
|
this.graphResults = await fixBadLinks(graphData);
|
|
|
this.updateUi();
|
|
|
}
|
|
|
|
|
|
private async saveFixedWorkflow() {
|
|
|
if (!this.graphFinalResults) {
|
|
|
this.updateUi("⛔ Save w/o final graph patched.");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
let filename: string | null = (this.file as File).name || "workflow.json";
|
|
|
let filenames = filename.split(".");
|
|
|
filenames.pop();
|
|
|
filename = filenames.join(".");
|
|
|
filename += "_fixed.json";
|
|
|
filename = prompt("Save workflow as:", filename);
|
|
|
if (!filename) return false;
|
|
|
if (!filename.toLowerCase().endsWith(".json")) {
|
|
|
filename += ".json";
|
|
|
}
|
|
|
const json = JSON.stringify(this.graphFinalResults.graph, null, 2);
|
|
|
const blob = new Blob([json], { type: "application/json" });
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
const anchor = document.createElement("a");
|
|
|
anchor.download = filename;
|
|
|
anchor.href = url;
|
|
|
anchor.style.display = "none";
|
|
|
document.body.appendChild(anchor);
|
|
|
await wait();
|
|
|
anchor.click();
|
|
|
await wait();
|
|
|
anchor.remove();
|
|
|
window.URL.revokeObjectURL(url);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|