152 lines
6.9 KiB
JavaScript
152 lines
6.9 KiB
JavaScript
(function () {
|
|
async function checkEditorAvailable() {
|
|
const LOCAL_EDITOR_PATH = '/openpose_editor_index';
|
|
const REMOTE_EDITOR_PATH = 'https://huchenlei.github.io/sd-webui-openpose-editor/';
|
|
|
|
async function testEditorPath(path) {
|
|
const res = await fetch(path);
|
|
return res.status === 200 ? path : null;
|
|
}
|
|
|
|
// Use local editor if the user has the extension installed. Fallback
|
|
// onto remote editor if the local editor is not ready yet.
|
|
// See https://github.com/huchenlei/sd-webui-openpose-editor/issues/53
|
|
// for more details.
|
|
return await testEditorPath(LOCAL_EDITOR_PATH) || await testEditorPath(REMOTE_EDITOR_PATH);
|
|
}
|
|
|
|
const cnetOpenposeEditorRegisteredElements = new Set();
|
|
let editorURL = null;
|
|
function loadOpenposeEditor() {
|
|
// Simulate an `input` DOM event for Gradio Textbox component. Needed after you edit its contents in javascript, otherwise your edits
|
|
// will only visible on web page and not sent to python.
|
|
function updateInput(target) {
|
|
let e = new Event("input", { bubbles: true })
|
|
Object.defineProperty(e, "target", { value: target })
|
|
target.dispatchEvent(e);
|
|
}
|
|
|
|
function navigateIframe(iframe, editorURL) {
|
|
function getPathname(rawURL) {
|
|
try {
|
|
return new URL(rawURL).pathname;
|
|
} catch (e) {
|
|
return rawURL;
|
|
}
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
const darkThemeParam = document.body.classList.contains('dark') ?
|
|
new URLSearchParams({ theme: 'dark' }).toString() :
|
|
'';
|
|
|
|
window.addEventListener('message', (event) => {
|
|
const message = event.data;
|
|
if (message['ready']) resolve();
|
|
}, { once: true });
|
|
|
|
if ((editorURL.startsWith("http") ? iframe.src : getPathname(iframe.src)) !== editorURL) {
|
|
iframe.src = `${editorURL}?${darkThemeParam}`;
|
|
// By default assume 5 second is enough for the openpose editor
|
|
// to load.
|
|
setTimeout(resolve, 5000);
|
|
} else {
|
|
// If no navigation is required, immediately return.
|
|
resolve();
|
|
}
|
|
});
|
|
}
|
|
const tabs = gradioApp().querySelectorAll('#controlnet .input-accordion');
|
|
tabs.forEach(tab => {
|
|
if (cnetOpenposeEditorRegisteredElements.has(tab)) return;
|
|
cnetOpenposeEditorRegisteredElements.add(tab);
|
|
|
|
const generatedImageGroup = tab.querySelector('.cnet-generated-image-group');
|
|
const editButton = generatedImageGroup.querySelector('.cnet-edit-pose');
|
|
|
|
editButton.addEventListener('click', async () => {
|
|
const inputImageGroup = tab.querySelector('.cnet-input-image-group');
|
|
const inputImage = inputImageGroup.querySelector('.cnet-image img');
|
|
const downloadLink = generatedImageGroup.querySelector('.cnet-download-pose a');
|
|
const modalId = editButton.id.replace('cnet-modal-open-', '');
|
|
const modalIframe = generatedImageGroup.querySelector('.cnet-modal iframe');
|
|
|
|
if (!editorURL) {
|
|
editorURL = await checkEditorAvailable();
|
|
if (!editorURL) {
|
|
alert("No openpose editor available.")
|
|
}
|
|
}
|
|
|
|
await navigateIframe(modalIframe, editorURL);
|
|
modalIframe.contentWindow.postMessage({
|
|
modalId,
|
|
imageURL: inputImage ? inputImage.src : undefined,
|
|
poseURL: downloadLink.href,
|
|
}, '*');
|
|
// Focus the iframe so that the focus is no longer on the `Edit` button.
|
|
// Pressing space when the focus is on `Edit` button will trigger
|
|
// the click again to resend the frame message.
|
|
modalIframe.contentWindow.focus();
|
|
});
|
|
/*
|
|
* Writes the pose data URL to an link element on input image group.
|
|
* Click a hidden button to trigger a backend rendering of the pose JSON.
|
|
*
|
|
* The backend should:
|
|
* - Set the rendered pose image as preprocessor generated image.
|
|
*/
|
|
function updatePreviewPose(poseURL) {
|
|
const downloadLink = generatedImageGroup.querySelector('.cnet-download-pose a');
|
|
const renderButton = generatedImageGroup.querySelector('.cnet-render-pose');
|
|
const poseTextbox = generatedImageGroup.querySelector('.cnet-pose-json textarea');
|
|
const allowPreviewCheckbox = tab.querySelector('.cnet-allow-preview input');
|
|
|
|
if (!allowPreviewCheckbox.checked)
|
|
allowPreviewCheckbox.click();
|
|
|
|
// Only set href when download link exists and needs an update. `downloadLink`
|
|
// can be null when user closes preview and click `Upload JSON` button again.
|
|
// https://github.com/Mikubill/sd-webui-controlnet/issues/2308
|
|
if (downloadLink !== null)
|
|
downloadLink.href = poseURL;
|
|
|
|
poseTextbox.value = poseURL;
|
|
updateInput(poseTextbox);
|
|
renderButton.click();
|
|
}
|
|
|
|
// Updates preview image when edit is done.
|
|
window.addEventListener('message', (event) => {
|
|
const message = event.data;
|
|
const modalId = editButton.id.replace('cnet-modal-open-', '');
|
|
if (message.modalId !== modalId) return;
|
|
updatePreviewPose(message.poseURL);
|
|
|
|
const closeModalButton = generatedImageGroup.querySelector('.cnet-modal .cnet-modal-close');
|
|
closeModalButton.click();
|
|
});
|
|
|
|
const inputImageGroup = tab.querySelector('.cnet-input-image-group');
|
|
const uploadButton = inputImageGroup.querySelector('.cnet-upload-pose input');
|
|
// Updates preview image when JSON file is uploaded.
|
|
uploadButton.addEventListener('change', (event) => {
|
|
const file = event.target.files[0];
|
|
if (!file)
|
|
return;
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function (e) {
|
|
const contents = e.target.result;
|
|
const poseURL = `data:application/json;base64,${btoa(contents)}`;
|
|
updatePreviewPose(poseURL);
|
|
};
|
|
reader.readAsText(file);
|
|
// Reset the file input value so that uploading the same file still triggers callback.
|
|
event.target.value = '';
|
|
});
|
|
});
|
|
}
|
|
|
|
onUiUpdate(loadOpenposeEditor);
|
|
})(); |