Spaces:
Running
Running
| window.SD = (() => { | |
| /* | |
| * Painterro is made a field of the SD global object | |
| * To provide convinience when using w() method in css_and_js.py | |
| */ | |
| class PainterroClass { | |
| static isOpen = false; | |
| static async init ({ x, toId }) { | |
| console.log(x) | |
| const originalImage = x[2] === 'Mask' ? x[1]?.image : x[0]; | |
| if (window.Painterro === undefined) { | |
| try { | |
| await this.load(); | |
| } catch (e) { | |
| SDClass.error(e); | |
| return this.fallback(originalImage); | |
| } | |
| } | |
| if (this.isOpen) { | |
| return this.fallback(originalImage); | |
| } | |
| this.isOpen = true; | |
| let resolveResult; | |
| const paintClient = Painterro({ | |
| hiddenTools: ['arrow'], | |
| onHide: () => { | |
| resolveResult?.(null); | |
| }, | |
| saveHandler: (image, done) => { | |
| const data = image.asDataURL(); | |
| // ensures stable performance even | |
| // when the editor is in interactive mode | |
| SD.clearImageInput(SD.el.get(`#${toId}`)); | |
| resolveResult(data); | |
| done(true); | |
| paintClient.hide(); | |
| }, | |
| }); | |
| const result = await new Promise((resolve) => { | |
| resolveResult = resolve; | |
| paintClient.show(originalImage); | |
| }); | |
| this.isOpen = false; | |
| return result ? this.success(result) : this.fallback(originalImage); | |
| } | |
| static success (result) { return [result, { image: result, mask: result }] }; | |
| static fallback (image) { return [image, { image: image, mask: image }] }; | |
| static load () { | |
| return new Promise((resolve, reject) => { | |
| const scriptId = '__painterro-script'; | |
| if (document.getElementById(scriptId)) { | |
| reject(new Error('Tried to load painterro script, but script tag already exists.')); | |
| return; | |
| } | |
| const styleId = '__painterro-css-override'; | |
| if (!document.getElementById(styleId)) { | |
| /* Ensure Painterro window is always on top */ | |
| const style = document.createElement('style'); | |
| style.id = styleId; | |
| style.setAttribute('type', 'text/css'); | |
| style.appendChild(document.createTextNode(` | |
| .ptro-holder-wrapper { | |
| z-index: 100; | |
| } | |
| `)); | |
| document.head.appendChild(style); | |
| } | |
| const script = document.createElement('script'); | |
| script.id = scriptId; | |
| script.src = 'https://unpkg.com/painterro@1.2.78/build/painterro.min.js'; | |
| script.onload = () => resolve(true); | |
| script.onerror = (e) => { | |
| // remove self on error to enable reattempting load | |
| document.head.removeChild(script); | |
| reject(e); | |
| }; | |
| document.head.appendChild(script); | |
| }); | |
| } | |
| } | |
| /* | |
| * Turns out caching elements doesn't actually work in gradio | |
| * As elements in tabs might get recreated | |
| */ | |
| class ElementCache { | |
| #el; | |
| constructor () { | |
| this.root = document.querySelector('gradio-app').shadowRoot; | |
| } | |
| get (selector) { | |
| return this.root.querySelector(selector); | |
| } | |
| } | |
| /* | |
| * The main helper class to incapsulate functions | |
| * that change gradio ui functionality | |
| */ | |
| class SDClass { | |
| el = new ElementCache(); | |
| Painterro = PainterroClass; | |
| moveImageFromGallery ({ x, fromId, toId }) { | |
| x = x[0]; | |
| if (!Array.isArray(x) || x.length === 0) return; | |
| this.clearImageInput(this.el.get(`#${toId}`)); | |
| const i = this.#getGallerySelectedIndex(this.el.get(`#${fromId}`)); | |
| return [x[i].replace('data:;','data:image/png;')]; | |
| } | |
| async copyImageFromGalleryToClipboard ({ x, fromId }) { | |
| x = x[0]; | |
| if (!Array.isArray(x) || x.length === 0) return; | |
| const i = this.#getGallerySelectedIndex(this.el.get(`#${fromId}`)); | |
| const data = x[i]; | |
| const blob = await (await fetch(data.replace('data:;','data:image/png;'))).blob(); | |
| const item = new ClipboardItem({'image/png': blob}); | |
| await this.copyToClipboard([item]); | |
| } | |
| clickFirstVisibleButton({ rowId }) { | |
| const generateButtons = this.el.get(`#${rowId}`).querySelectorAll('.gr-button-primary'); | |
| if (!generateButtons) return; | |
| for (let i = 0, arr = [...generateButtons]; i < arr.length; i++) { | |
| const cs = window.getComputedStyle(arr[i]); | |
| if (cs.display !== 'none' && cs.visibility !== 'hidden') { | |
| console.log(arr[i]); | |
| arr[i].click(); | |
| break; | |
| } | |
| } | |
| } | |
| async gradioInputToClipboard ({ x }) { return this.copyToClipboard(x[0]); } | |
| async copyToClipboard (value) { | |
| if (!value || typeof value === 'boolean') return; | |
| try { | |
| if (Array.isArray(value) && | |
| value.length && | |
| value[0] instanceof ClipboardItem) { | |
| await navigator.clipboard.write(value); | |
| } else { | |
| await navigator.clipboard.writeText(value); | |
| } | |
| } catch (e) { | |
| SDClass.error(e); | |
| } | |
| } | |
| static error (e) { | |
| console.error(e); | |
| if (typeof e === 'string') { | |
| alert(e); | |
| } else if(typeof e === 'object' && Object.hasOwn(e, 'message')) { | |
| alert(e.message); | |
| } | |
| } | |
| clearImageInput (imageEditor) { | |
| imageEditor?.querySelector('.modify-upload button:last-child')?.click(); | |
| } | |
| #getGallerySelectedIndex (gallery) { | |
| const selected = gallery.querySelector(`.\\!ring-2`); | |
| return selected ? [...selected.parentNode.children].indexOf(selected) : 0; | |
| } | |
| } | |
| return new SDClass(); | |
| })(); | |