File size: 6,718 Bytes
3f9c56c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
(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 : undefined;
        }

        // 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();
    function loadOpenposeEditor(editorURL) {
        // 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) {
            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 (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 imageRows = gradioApp().querySelectorAll('.cnet-image-row');
        imageRows.forEach(imageRow => {
            if (cnetOpenposeEditorRegisteredElements.has(imageRow)) return;
            cnetOpenposeEditorRegisteredElements.add(imageRow);

            const generatedImageGroup = imageRow.querySelector('.cnet-generated-image-group');
            const editButton = generatedImageGroup.querySelector('.cnet-edit-pose');

            editButton.addEventListener('click', async () => {
                const inputImageGroup = imageRow.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');

                await navigateIframe(modalIframe);
                modalIframe.contentWindow.postMessage({
                    modalId,
                    imageURL: inputImage.src,
                    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();
            });

            window.addEventListener('message', (event) => {
                const message = event.data;
                const downloadLink = generatedImageGroup.querySelector('.cnet-download-pose a');
                const renderButton = generatedImageGroup.querySelector('.cnet-render-pose');
                const poseTextbox = generatedImageGroup.querySelector('.cnet-pose-json textarea');
                const modalId = editButton.id.replace('cnet-modal-open-', '');
                const closeModalButton = generatedImageGroup.querySelector('.cnet-modal .cnet-modal-close');

                if (message.modalId !== modalId) return;
                /* 
                * 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.
                */
                downloadLink.href = message.poseURL;
                poseTextbox.value = message.poseURL;
                updateInput(poseTextbox);
                renderButton.click();
                closeModalButton.click();
            });
        });
    }

    function loadPlaceHolder() {
        const imageRows = gradioApp().querySelectorAll('.cnet-image-row');
        imageRows.forEach(imageRow => {
            if (cnetOpenposeEditorRegisteredElements.has(imageRow)) return;
            cnetOpenposeEditorRegisteredElements.add(imageRow);

            const generatedImageGroup = imageRow.querySelector('.cnet-generated-image-group');
            const editButton = generatedImageGroup.querySelector('.cnet-edit-pose');
            const modalContent = generatedImageGroup.querySelector('.cnet-modal-content');

            modalContent.classList.add('alert');
            modalContent.innerHTML = `
        <div>
            <p>
                OpenPose editor not found. Please make sure you have an OpenPose editor available on <code>/openpose_editor_index</code>. To hide the edit button, check "Disable openpose edit" in Settings.<br>
                <br>
                The following extension(s) provide integration with ControlNet:
            </p>
            <ul>
                <li>
                    <a href="https://github.com/huchenlei/sd-webui-openpose-editor">huchenlei/sd-webui-openpose-editor</a>
                </li>
            </ul>
        </div>
        `;

            editButton.innerHTML = '<del>' + editButton.innerHTML + '</del>';
        });
    }

    checkEditorAvailable().then(editorURL => {
        onUiUpdate(() => {
            if (editorURL)
                loadOpenposeEditor(editorURL);
            else
                loadPlaceHolder();
        });
    });
})();