From 20d51dfeb688f40c8566ec1c2b52c2eedeb19dc7 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 15 Aug 2024 13:25:17 -0400 Subject: [PATCH] Swap frontend uncompressed --- web/extensions/core/clipspace.js | 166 - web/extensions/core/colorPalette.js | 785 - web/extensions/core/contextMenuFilter.js | 148 - web/extensions/core/dynamicPrompts.js | 48 - web/extensions/core/editAttention.js | 144 - web/extensions/core/groupNode.js | 1281 -- web/extensions/core/groupNodeManage.css | 149 - web/extensions/core/groupNodeManage.js | 422 - web/extensions/core/groupOptions.js | 259 - web/extensions/core/invertMenuScrolling.js | 36 - web/extensions/core/keybinds.js | 69 - web/extensions/core/linkRenderMode.js | 25 - web/extensions/core/maskeditor.js | 967 -- web/extensions/core/nodeTemplates.js | 412 - web/extensions/core/noteNode.js | 41 - web/extensions/core/rerouteNode.js | 274 - web/extensions/core/saveImageExtraOutput.js | 35 - web/extensions/core/simpleTouchSupport.js | 102 - web/extensions/core/slotDefaults.js | 91 - web/extensions/core/snapToGrid.js | 171 - web/extensions/core/tooltips.js | 122 - web/extensions/core/uploadAudio.js | 186 - web/extensions/core/uploadImage.js | 12 - web/extensions/core/webcamCapture.js | 126 - web/extensions/core/widgetInputs.js | 800 - web/extensions/logging.js.example | 55 - web/fonts/materialdesignicons-webfont.woff2 | Bin 403216 -> 0 bytes web/index.html | 49 - web/jsconfig.json | 12 - web/lib/litegraph.core.js | 14424 ------------------ web/lib/litegraph.css | 693 - web/lib/litegraph.extensions.js | 21 - web/lib/materialdesignicons.min.css | 3 - web/scripts/api.js | 482 - web/scripts/app.js | 2460 --- web/scripts/changeTracker.js | 255 - web/scripts/defaultGraph.js | 119 - web/scripts/domWidget.js | 335 - web/scripts/logging.js | 370 - web/scripts/pnginfo.js | 507 - web/scripts/ui.js | 664 - web/scripts/ui/components/asyncDialog.js | 64 - web/scripts/ui/components/button.js | 163 - web/scripts/ui/components/buttonGroup.js | 45 - web/scripts/ui/components/popup.js | 128 - web/scripts/ui/components/splitButton.js | 43 - web/scripts/ui/dialog.js | 38 - web/scripts/ui/draggableList.js | 287 - web/scripts/ui/imagePreview.js | 97 - web/scripts/ui/menu/index.js | 302 - web/scripts/ui/menu/interruptButton.js | 23 - web/scripts/ui/menu/menu.css | 710 - web/scripts/ui/menu/queueButton.js | 93 - web/scripts/ui/menu/queueOptions.js | 77 - web/scripts/ui/menu/viewHistory.js | 27 - web/scripts/ui/menu/viewList.js | 203 - web/scripts/ui/menu/viewQueue.js | 55 - web/scripts/ui/menu/workflows.js | 770 - web/scripts/ui/settings.js | 333 - web/scripts/ui/spinner.css | 34 - web/scripts/ui/spinner.js | 9 - web/scripts/ui/toggleSwitch.js | 60 - web/scripts/ui/userSelection.css | 135 - web/scripts/ui/userSelection.js | 114 - web/scripts/ui/utils.js | 56 - web/scripts/utils.js | 156 - web/scripts/widgets.js | 533 - web/scripts/workflows.js | 450 - web/style.css | 664 - web/types/comfy.d.ts | 76 - web/types/litegraph.d.ts | 1506 -- web/user.css | 1 - 72 files changed, 34572 deletions(-) delete mode 100644 web/extensions/core/clipspace.js delete mode 100644 web/extensions/core/colorPalette.js delete mode 100644 web/extensions/core/contextMenuFilter.js delete mode 100644 web/extensions/core/dynamicPrompts.js delete mode 100644 web/extensions/core/editAttention.js delete mode 100644 web/extensions/core/groupNode.js delete mode 100644 web/extensions/core/groupNodeManage.css delete mode 100644 web/extensions/core/groupNodeManage.js delete mode 100644 web/extensions/core/groupOptions.js delete mode 100644 web/extensions/core/invertMenuScrolling.js delete mode 100644 web/extensions/core/keybinds.js delete mode 100644 web/extensions/core/linkRenderMode.js delete mode 100644 web/extensions/core/maskeditor.js delete mode 100644 web/extensions/core/nodeTemplates.js delete mode 100644 web/extensions/core/noteNode.js delete mode 100644 web/extensions/core/rerouteNode.js delete mode 100644 web/extensions/core/saveImageExtraOutput.js delete mode 100644 web/extensions/core/simpleTouchSupport.js delete mode 100644 web/extensions/core/slotDefaults.js delete mode 100644 web/extensions/core/snapToGrid.js delete mode 100644 web/extensions/core/tooltips.js delete mode 100644 web/extensions/core/uploadAudio.js delete mode 100644 web/extensions/core/uploadImage.js delete mode 100644 web/extensions/core/webcamCapture.js delete mode 100644 web/extensions/core/widgetInputs.js delete mode 100644 web/extensions/logging.js.example delete mode 100644 web/fonts/materialdesignicons-webfont.woff2 delete mode 100644 web/index.html delete mode 100644 web/jsconfig.json delete mode 100644 web/lib/litegraph.core.js delete mode 100644 web/lib/litegraph.css delete mode 100644 web/lib/litegraph.extensions.js delete mode 100644 web/lib/materialdesignicons.min.css delete mode 100644 web/scripts/api.js delete mode 100644 web/scripts/app.js delete mode 100644 web/scripts/changeTracker.js delete mode 100644 web/scripts/defaultGraph.js delete mode 100644 web/scripts/domWidget.js delete mode 100644 web/scripts/logging.js delete mode 100644 web/scripts/pnginfo.js delete mode 100644 web/scripts/ui.js delete mode 100644 web/scripts/ui/components/asyncDialog.js delete mode 100644 web/scripts/ui/components/button.js delete mode 100644 web/scripts/ui/components/buttonGroup.js delete mode 100644 web/scripts/ui/components/popup.js delete mode 100644 web/scripts/ui/components/splitButton.js delete mode 100644 web/scripts/ui/dialog.js delete mode 100644 web/scripts/ui/draggableList.js delete mode 100644 web/scripts/ui/imagePreview.js delete mode 100644 web/scripts/ui/menu/index.js delete mode 100644 web/scripts/ui/menu/interruptButton.js delete mode 100644 web/scripts/ui/menu/menu.css delete mode 100644 web/scripts/ui/menu/queueButton.js delete mode 100644 web/scripts/ui/menu/queueOptions.js delete mode 100644 web/scripts/ui/menu/viewHistory.js delete mode 100644 web/scripts/ui/menu/viewList.js delete mode 100644 web/scripts/ui/menu/viewQueue.js delete mode 100644 web/scripts/ui/menu/workflows.js delete mode 100644 web/scripts/ui/settings.js delete mode 100644 web/scripts/ui/spinner.css delete mode 100644 web/scripts/ui/spinner.js delete mode 100644 web/scripts/ui/toggleSwitch.js delete mode 100644 web/scripts/ui/userSelection.css delete mode 100644 web/scripts/ui/userSelection.js delete mode 100644 web/scripts/ui/utils.js delete mode 100644 web/scripts/utils.js delete mode 100644 web/scripts/widgets.js delete mode 100644 web/scripts/workflows.js delete mode 100644 web/style.css delete mode 100644 web/types/comfy.d.ts delete mode 100644 web/types/litegraph.d.ts delete mode 100644 web/user.css diff --git a/web/extensions/core/clipspace.js b/web/extensions/core/clipspace.js deleted file mode 100644 index e376a02f7..000000000 --- a/web/extensions/core/clipspace.js +++ /dev/null @@ -1,166 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { ComfyDialog, $el } from "../../scripts/ui.js"; -import { ComfyApp } from "../../scripts/app.js"; - -export class ClipspaceDialog extends ComfyDialog { - static items = []; - static instance = null; - - static registerButton(name, contextPredicate, callback) { - const item = - $el("button", { - type: "button", - textContent: name, - contextPredicate: contextPredicate, - onclick: callback - }) - - ClipspaceDialog.items.push(item); - } - - static invalidatePreview() { - if(ComfyApp.clipspace && ComfyApp.clipspace.imgs && ComfyApp.clipspace.imgs.length > 0) { - const img_preview = document.getElementById("clipspace_preview"); - if(img_preview) { - img_preview.src = ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src; - img_preview.style.maxHeight = "100%"; - img_preview.style.maxWidth = "100%"; - } - } - } - - static invalidate() { - if(ClipspaceDialog.instance) { - const self = ClipspaceDialog.instance; - // allow reconstruct controls when copying from non-image to image content. - const children = $el("div.comfy-modal-content", [ self.createImgSettings(), ...self.createButtons() ]); - - if(self.element) { - // update - self.element.removeChild(self.element.firstChild); - self.element.appendChild(children); - } - else { - // new - self.element = $el("div.comfy-modal", { parent: document.body }, [children,]); - } - - if(self.element.children[0].children.length <= 1) { - self.element.children[0].appendChild($el("p", {}, ["Unable to find the features to edit content of a format stored in the current Clipspace."])); - } - - ClipspaceDialog.invalidatePreview(); - } - } - - constructor() { - super(); - } - - createButtons(self) { - const buttons = []; - - for(let idx in ClipspaceDialog.items) { - const item = ClipspaceDialog.items[idx]; - if(!item.contextPredicate || item.contextPredicate()) - buttons.push(ClipspaceDialog.items[idx]); - } - - buttons.push( - $el("button", { - type: "button", - textContent: "Close", - onclick: () => { this.close(); } - }) - ); - - return buttons; - } - - createImgSettings() { - if(ComfyApp.clipspace.imgs) { - const combo_items = []; - const imgs = ComfyApp.clipspace.imgs; - - for(let i=0; i < imgs.length; i++) { - combo_items.push($el("option", {value:i}, [`${i}`])); - } - - const combo1 = $el("select", - {id:"clipspace_img_selector", onchange:(event) => { - ComfyApp.clipspace['selectedIndex'] = event.target.selectedIndex; - ClipspaceDialog.invalidatePreview(); - } }, combo_items); - - const row1 = - $el("tr", {}, - [ - $el("td", {}, [$el("font", {color:"white"}, ["Select Image"])]), - $el("td", {}, [combo1]) - ]); - - - const combo2 = $el("select", - {id:"clipspace_img_paste_mode", onchange:(event) => { - ComfyApp.clipspace['img_paste_mode'] = event.target.value; - } }, - [ - $el("option", {value:'selected'}, 'selected'), - $el("option", {value:'all'}, 'all') - ]); - combo2.value = ComfyApp.clipspace['img_paste_mode']; - - const row2 = - $el("tr", {}, - [ - $el("td", {}, [$el("font", {color:"white"}, ["Paste Mode"])]), - $el("td", {}, [combo2]) - ]); - - const td = $el("td", {align:'center', width:'100px', height:'100px', colSpan:'2'}, - [ $el("img",{id:"clipspace_preview", ondragstart:() => false},[]) ]); - - const row3 = - $el("tr", {}, [td]); - - return $el("table", {}, [row1, row2, row3]); - } - else { - return []; - } - } - - createImgPreview() { - if(ComfyApp.clipspace.imgs) { - return $el("img",{id:"clipspace_preview", ondragstart:() => false}); - } - else - return []; - } - - show() { - const img_preview = document.getElementById("clipspace_preview"); - ClipspaceDialog.invalidate(); - - this.element.style.display = "block"; - } -} - -app.registerExtension({ - name: "Comfy.Clipspace", - init(app) { - app.openClipspace = - function () { - if(!ClipspaceDialog.instance) { - ClipspaceDialog.instance = new ClipspaceDialog(app); - ComfyApp.clipspace_invalidate_handler = ClipspaceDialog.invalidate; - } - - if(ComfyApp.clipspace) { - ClipspaceDialog.instance.show(); - } - else - app.ui.dialog.show("Clipspace is Empty!"); - }; - } -}); \ No newline at end of file diff --git a/web/extensions/core/colorPalette.js b/web/extensions/core/colorPalette.js deleted file mode 100644 index 245f3b93e..000000000 --- a/web/extensions/core/colorPalette.js +++ /dev/null @@ -1,785 +0,0 @@ -import {app} from "../../scripts/app.js"; -import {$el} from "../../scripts/ui.js"; - -// Manage color palettes - -const colorPalettes = { - "dark": { - "id": "dark", - "name": "Dark (Default)", - "colors": { - "node_slot": { - "CLIP": "#FFD500", // bright yellow - "CLIP_VISION": "#A8DADC", // light blue-gray - "CLIP_VISION_OUTPUT": "#ad7452", // rusty brown-orange - "CONDITIONING": "#FFA931", // vibrant orange-yellow - "CONTROL_NET": "#6EE7B7", // soft mint green - "IMAGE": "#64B5F6", // bright sky blue - "LATENT": "#FF9CF9", // light pink-purple - "MASK": "#81C784", // muted green - "MODEL": "#B39DDB", // light lavender-purple - "STYLE_MODEL": "#C2FFAE", // light green-yellow - "VAE": "#FF6E6E", // bright red - "NOISE": "#B0B0B0", // gray - "GUIDER": "#66FFFF", // cyan - "SAMPLER": "#ECB4B4", // very soft red - "SIGMAS": "#CDFFCD", // soft lime green - "TAESD": "#DCC274", // cheesecake - }, - "litegraph_base": { - "BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=", - "CLEAR_BACKGROUND_COLOR": "#222", - "NODE_TITLE_COLOR": "#999", - "NODE_SELECTED_TITLE_COLOR": "#FFF", - "NODE_TEXT_SIZE": 14, - "NODE_TEXT_COLOR": "#AAA", - "NODE_SUBTEXT_SIZE": 12, - "NODE_DEFAULT_COLOR": "#333", - "NODE_DEFAULT_BGCOLOR": "#353535", - "NODE_DEFAULT_BOXCOLOR": "#666", - "NODE_DEFAULT_SHAPE": "box", - "NODE_BOX_OUTLINE_COLOR": "#FFF", - "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)", - "DEFAULT_GROUP_FONT": 24, - - "WIDGET_BGCOLOR": "#222", - "WIDGET_OUTLINE_COLOR": "#666", - "WIDGET_TEXT_COLOR": "#DDD", - "WIDGET_SECONDARY_TEXT_COLOR": "#999", - - "LINK_COLOR": "#9A9", - "EVENT_LINK_COLOR": "#A86", - "CONNECTING_LINK_COLOR": "#AFA", - }, - "comfy_base": { - "fg-color": "#fff", - "bg-color": "#202020", - "comfy-menu-bg": "#353535", - "comfy-input-bg": "#222", - "input-text": "#ddd", - "descrip-text": "#999", - "drag-text": "#ccc", - "error-text": "#ff4444", - "border-color": "#4e4e4e", - "tr-even-bg-color": "#222", - "tr-odd-bg-color": "#353535", - "content-bg": "#4e4e4e", - "content-fg": "#fff", - "content-hover-bg": "#222", - "content-hover-fg": "#fff" - } - }, - }, - "light": { - "id": "light", - "name": "Light", - "colors": { - "node_slot": { - "CLIP": "#FFA726", // orange - "CLIP_VISION": "#5C6BC0", // indigo - "CLIP_VISION_OUTPUT": "#8D6E63", // brown - "CONDITIONING": "#EF5350", // red - "CONTROL_NET": "#66BB6A", // green - "IMAGE": "#42A5F5", // blue - "LATENT": "#AB47BC", // purple - "MASK": "#9CCC65", // light green - "MODEL": "#7E57C2", // deep purple - "STYLE_MODEL": "#D4E157", // lime - "VAE": "#FF7043", // deep orange - }, - "litegraph_base": { - "BACKGROUND_IMAGE": "data:image/gif;base64,R0lGODlhZABkALMAAAAAAP///+vr6+rq6ujo6Ofn5+bm5uXl5d3d3f///wAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAkALAAAAABkAGQAAAT/UMhJq7046827HkcoHkYxjgZhnGG6si5LqnIM0/fL4qwwIMAg0CAsEovBIxKhRDaNy2GUOX0KfVFrssrNdpdaqTeKBX+dZ+jYvEaTf+y4W66mC8PUdrE879f9d2mBeoNLfH+IhYBbhIx2jkiHiomQlGKPl4uZe3CaeZifnnijgkESBqipqqusra6vsLGys62SlZO4t7qbuby7CLa+wqGWxL3Gv3jByMOkjc2lw8vOoNSi0czAncXW3Njdx9Pf48/Z4Kbbx+fQ5evZ4u3k1fKR6cn03vHlp7T9/v8A/8Gbp4+gwXoFryXMB2qgwoMMHyKEqA5fxX322FG8tzBcRnMW/zlulPbRncmQGidKjMjyYsOSKEF2FBlJQMCbOHP6c9iSZs+UnGYCdbnSo1CZI5F64kn0p1KnTH02nSoV3dGTV7FFHVqVq1dtWcMmVQZTbNGu72zqXMuW7danVL+6e4t1bEy6MeueBYLXrNO5Ze36jQtWsOG97wIj1vt3St/DjTEORss4nNq2mDP3e7w4r1bFkSET5hy6s2TRlD2/mSxXtSHQhCunXo26NevCpmvD/UU6tuullzULH76q92zdZG/Ltv1a+W+osI/nRmyc+fRi1Xdbh+68+0vv10dH3+77KD/i6IdnX669/frn5Zsjh4/2PXju8+8bzc9/6fj27LFnX11/+IUnXWl7BJfegm79FyB9JOl3oHgSklefgxAC+FmFGpqHIYcCfkhgfCohSKKJVo044YUMttggiBkmp6KFXw1oII24oYhjiDByaKOOHcp3Y5BD/njikSkO+eBREQAAOw==", - "CLEAR_BACKGROUND_COLOR": "lightgray", - "NODE_TITLE_COLOR": "#222", - "NODE_SELECTED_TITLE_COLOR": "#000", - "NODE_TEXT_SIZE": 14, - "NODE_TEXT_COLOR": "#444", - "NODE_SUBTEXT_SIZE": 12, - "NODE_DEFAULT_COLOR": "#F7F7F7", - "NODE_DEFAULT_BGCOLOR": "#F5F5F5", - "NODE_DEFAULT_BOXCOLOR": "#CCC", - "NODE_DEFAULT_SHAPE": "box", - "NODE_BOX_OUTLINE_COLOR": "#000", - "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.1)", - "DEFAULT_GROUP_FONT": 24, - - "WIDGET_BGCOLOR": "#D4D4D4", - "WIDGET_OUTLINE_COLOR": "#999", - "WIDGET_TEXT_COLOR": "#222", - "WIDGET_SECONDARY_TEXT_COLOR": "#555", - - "LINK_COLOR": "#4CAF50", - "EVENT_LINK_COLOR": "#FF9800", - "CONNECTING_LINK_COLOR": "#2196F3", - }, - "comfy_base": { - "fg-color": "#222", - "bg-color": "#DDD", - "comfy-menu-bg": "#F5F5F5", - "comfy-input-bg": "#C9C9C9", - "input-text": "#222", - "descrip-text": "#444", - "drag-text": "#555", - "error-text": "#F44336", - "border-color": "#888", - "tr-even-bg-color": "#f9f9f9", - "tr-odd-bg-color": "#fff", - "content-bg": "#e0e0e0", - "content-fg": "#222", - "content-hover-bg": "#adadad", - "content-hover-fg": "#222" - } - }, - }, - "solarized": { - "id": "solarized", - "name": "Solarized", - "colors": { - "node_slot": { - "CLIP": "#2AB7CA", // light blue - "CLIP_VISION": "#6c71c4", // blue violet - "CLIP_VISION_OUTPUT": "#859900", // olive green - "CONDITIONING": "#d33682", // magenta - "CONTROL_NET": "#d1ffd7", // light mint green - "IMAGE": "#5940bb", // deep blue violet - "LATENT": "#268bd2", // blue - "MASK": "#CCC9E7", // light purple-gray - "MODEL": "#dc322f", // red - "STYLE_MODEL": "#1a998a", // teal - "UPSCALE_MODEL": "#054A29", // dark green - "VAE": "#facfad", // light pink-orange - }, - "litegraph_base": { - "NODE_TITLE_COLOR": "#fdf6e3", // Base3 - "NODE_SELECTED_TITLE_COLOR": "#A9D400", - "NODE_TEXT_SIZE": 14, - "NODE_TEXT_COLOR": "#657b83", // Base00 - "NODE_SUBTEXT_SIZE": 12, - "NODE_DEFAULT_COLOR": "#094656", - "NODE_DEFAULT_BGCOLOR": "#073642", // Base02 - "NODE_DEFAULT_BOXCOLOR": "#839496", // Base0 - "NODE_DEFAULT_SHAPE": "box", - "NODE_BOX_OUTLINE_COLOR": "#fdf6e3", // Base3 - "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)", - "DEFAULT_GROUP_FONT": 24, - - "WIDGET_BGCOLOR": "#002b36", // Base03 - "WIDGET_OUTLINE_COLOR": "#839496", // Base0 - "WIDGET_TEXT_COLOR": "#fdf6e3", // Base3 - "WIDGET_SECONDARY_TEXT_COLOR": "#93a1a1", // Base1 - - "LINK_COLOR": "#2aa198", // Solarized Cyan - "EVENT_LINK_COLOR": "#268bd2", // Solarized Blue - "CONNECTING_LINK_COLOR": "#859900", // Solarized Green - }, - "comfy_base": { - "fg-color": "#fdf6e3", // Base3 - "bg-color": "#002b36", // Base03 - "comfy-menu-bg": "#073642", // Base02 - "comfy-input-bg": "#002b36", // Base03 - "input-text": "#93a1a1", // Base1 - "descrip-text": "#586e75", // Base01 - "drag-text": "#839496", // Base0 - "error-text": "#dc322f", // Solarized Red - "border-color": "#657b83", // Base00 - "tr-even-bg-color": "#002b36", - "tr-odd-bg-color": "#073642", - "content-bg": "#657b83", - "content-fg": "#fdf6e3", - "content-hover-bg": "#002b36", - "content-hover-fg": "#fdf6e3" - } - }, - }, - "arc": { - "id": "arc", - "name": "Arc", - "colors": { - "node_slot": { - "BOOLEAN": "", - "CLIP": "#eacb8b", - "CLIP_VISION": "#A8DADC", - "CLIP_VISION_OUTPUT": "#ad7452", - "CONDITIONING": "#cf876f", - "CONTROL_NET": "#00d78d", - "CONTROL_NET_WEIGHTS": "", - "FLOAT": "", - "GLIGEN": "", - "IMAGE": "#80a1c0", - "IMAGEUPLOAD": "", - "INT": "", - "LATENT": "#b38ead", - "LATENT_KEYFRAME": "", - "MASK": "#a3bd8d", - "MODEL": "#8978a7", - "SAMPLER": "", - "SIGMAS": "", - "STRING": "", - "STYLE_MODEL": "#C2FFAE", - "T2I_ADAPTER_WEIGHTS": "", - "TAESD": "#DCC274", - "TIMESTEP_KEYFRAME": "", - "UPSCALE_MODEL": "", - "VAE": "#be616b" - }, - "litegraph_base": { - "BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABcklEQVR4nO3YMUoDARgF4RfxBqZI6/0vZqFn0MYtrLIQMFN8U6V4LAtD+Jm9XG/v30OGl2e/AP7yevz4+vx45nvgF/+QGITEICQGITEIiUFIjNNC3q43u3/YnRJyPOzeQ+0e220nhRzReC8e7R7bbdvl+Jal1Bs46jEIiUFIDEJiEBKDkBhKPbZT6qHdptRTu02p53DUYxASg5AYhMQgJAYhMZR6bKfUQ7tNqad2m1LP4ajHICQGITEIiUFIDEJiKPXYTqmHdptST+02pZ7DUY9BSAxCYhASg5AYhMRQ6rGdUg/tNqWe2m1KPYejHoOQGITEICQGITEIiaHUYzulHtptSj2125R6Dkc9BiExCIlBSAxCYhASQ6nHdko9tNuUemq3KfUcjnoMQmIQEoOQGITEICSGUo/tlHpotyn11G5T6jkc9RiExCAkBiExCIlBSAylHtsp9dBuU+qp3abUczjqMQiJQUgMQmIQEoOQGITE+AHFISNQrFTGuwAAAABJRU5ErkJggg==", - "CLEAR_BACKGROUND_COLOR": "#2b2f38", - "NODE_TITLE_COLOR": "#b2b7bd", - "NODE_SELECTED_TITLE_COLOR": "#FFF", - "NODE_TEXT_SIZE": 14, - "NODE_TEXT_COLOR": "#AAA", - "NODE_SUBTEXT_SIZE": 12, - "NODE_DEFAULT_COLOR": "#2b2f38", - "NODE_DEFAULT_BGCOLOR": "#242730", - "NODE_DEFAULT_BOXCOLOR": "#6e7581", - "NODE_DEFAULT_SHAPE": "box", - "NODE_BOX_OUTLINE_COLOR": "#FFF", - "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)", - "DEFAULT_GROUP_FONT": 22, - "WIDGET_BGCOLOR": "#2b2f38", - "WIDGET_OUTLINE_COLOR": "#6e7581", - "WIDGET_TEXT_COLOR": "#DDD", - "WIDGET_SECONDARY_TEXT_COLOR": "#b2b7bd", - "LINK_COLOR": "#9A9", - "EVENT_LINK_COLOR": "#A86", - "CONNECTING_LINK_COLOR": "#AFA" - }, - "comfy_base": { - "fg-color": "#fff", - "bg-color": "#2b2f38", - "comfy-menu-bg": "#242730", - "comfy-input-bg": "#2b2f38", - "input-text": "#ddd", - "descrip-text": "#b2b7bd", - "drag-text": "#ccc", - "error-text": "#ff4444", - "border-color": "#6e7581", - "tr-even-bg-color": "#2b2f38", - "tr-odd-bg-color": "#242730", - "content-bg": "#6e7581", - "content-fg": "#fff", - "content-hover-bg": "#2b2f38", - "content-hover-fg": "#fff" - } - }, - }, - "nord": { - "id": "nord", - "name": "Nord", - "colors": { - "node_slot": { - "BOOLEAN": "", - "CLIP": "#eacb8b", - "CLIP_VISION": "#A8DADC", - "CLIP_VISION_OUTPUT": "#ad7452", - "CONDITIONING": "#cf876f", - "CONTROL_NET": "#00d78d", - "CONTROL_NET_WEIGHTS": "", - "FLOAT": "", - "GLIGEN": "", - "IMAGE": "#80a1c0", - "IMAGEUPLOAD": "", - "INT": "", - "LATENT": "#b38ead", - "LATENT_KEYFRAME": "", - "MASK": "#a3bd8d", - "MODEL": "#8978a7", - "SAMPLER": "", - "SIGMAS": "", - "STRING": "", - "STYLE_MODEL": "#C2FFAE", - "T2I_ADAPTER_WEIGHTS": "", - "TAESD": "#DCC274", - "TIMESTEP_KEYFRAME": "", - "UPSCALE_MODEL": "", - "VAE": "#be616b" - }, - "litegraph_base": { - "BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFu2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjUwNDFhMmZjLTEzNzQtMTk0ZC1hZWY4LTYxMzM1MTVmNjUwMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyMzFiMTBiMC1iNGZiLTAyNGUtYjEyZS0zMDUzMDNjZDA3YzgiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoyMzFiMTBiMC1iNGZiLTAyNGUtYjEyZS0zMDUzMDNjZDA3YzgiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDQxYTJmYy0xMzc0LTE5NGQtYWVmOC02MTMzNTE1ZjY1MDAiIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz73jWg/AAAAyUlEQVR42u3WKwoAIBRFQRdiMb1idv9Lsxn9gEFw4Dbb8JCTojbbXEJwjJVL2HKwYMGCBQuWLbDmjr+9zrBGjHl1WVcvy2DBggULFizTWQpewSt4HzwsgwULFiwFr7MUvMtS8D54WLBgGSxYCl7BK3iXZbBgwYIFC5bpLAWv4BW8Dx6WwYIFC5aC11kK3mUpeB88LFiwDBYsBa/gFbzLMliwYMGCBct0loJX8AreBw/LYMGCBUvB6ywF77IUvA8eFixYBgsWrNfWAZPltufdad+1AAAAAElFTkSuQmCC", - "CLEAR_BACKGROUND_COLOR": "#212732", - "NODE_TITLE_COLOR": "#999", - "NODE_SELECTED_TITLE_COLOR": "#e5eaf0", - "NODE_TEXT_SIZE": 14, - "NODE_TEXT_COLOR": "#bcc2c8", - "NODE_SUBTEXT_SIZE": 12, - "NODE_DEFAULT_COLOR": "#2e3440", - "NODE_DEFAULT_BGCOLOR": "#161b22", - "NODE_DEFAULT_BOXCOLOR": "#545d70", - "NODE_DEFAULT_SHAPE": "box", - "NODE_BOX_OUTLINE_COLOR": "#e5eaf0", - "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)", - "DEFAULT_GROUP_FONT": 24, - "WIDGET_BGCOLOR": "#2e3440", - "WIDGET_OUTLINE_COLOR": "#545d70", - "WIDGET_TEXT_COLOR": "#bcc2c8", - "WIDGET_SECONDARY_TEXT_COLOR": "#999", - "LINK_COLOR": "#9A9", - "EVENT_LINK_COLOR": "#A86", - "CONNECTING_LINK_COLOR": "#AFA" - }, - "comfy_base": { - "fg-color": "#e5eaf0", - "bg-color": "#2e3440", - "comfy-menu-bg": "#161b22", - "comfy-input-bg": "#2e3440", - "input-text": "#bcc2c8", - "descrip-text": "#999", - "drag-text": "#ccc", - "error-text": "#ff4444", - "border-color": "#545d70", - "tr-even-bg-color": "#2e3440", - "tr-odd-bg-color": "#161b22", - "content-bg": "#545d70", - "content-fg": "#e5eaf0", - "content-hover-bg": "#2e3440", - "content-hover-fg": "#e5eaf0" - } - }, - }, - "github": { - "id": "github", - "name": "Github", - "colors": { - "node_slot": { - "BOOLEAN": "", - "CLIP": "#eacb8b", - "CLIP_VISION": "#A8DADC", - "CLIP_VISION_OUTPUT": "#ad7452", - "CONDITIONING": "#cf876f", - "CONTROL_NET": "#00d78d", - "CONTROL_NET_WEIGHTS": "", - "FLOAT": "", - "GLIGEN": "", - "IMAGE": "#80a1c0", - "IMAGEUPLOAD": "", - "INT": "", - "LATENT": "#b38ead", - "LATENT_KEYFRAME": "", - "MASK": "#a3bd8d", - "MODEL": "#8978a7", - "SAMPLER": "", - "SIGMAS": "", - "STRING": "", - "STYLE_MODEL": "#C2FFAE", - "T2I_ADAPTER_WEIGHTS": "", - "TAESD": "#DCC274", - "TIMESTEP_KEYFRAME": "", - "UPSCALE_MODEL": "", - "VAE": "#be616b" - }, - "litegraph_base": { - "BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmIyYzRhNjA5LWJmYTctYTg0MC1iOGFlLTk3MzE2ZjM1ZGIyNyIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk0ZmNlZGU4LTE1MTctZmQ0MC04ZGU3LWYzOTgxM2E3ODk5ZiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjMxYjEwYjAtYjRmYi0wMjRlLWIxMmUtMzA1MzAzY2QwN2M4IiBzdEV2dDp3aGVuPSIyMDIzLTExLTEzVDAwOjE4OjAyKzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQ4OWY1NzlmLTJkNjUtZWQ0Zi04OTg0LTA4NGE2MGE1ZTMzNSIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xNVQwMjowNDo1OSswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiMmM0YTYwOS1iZmE3LWE4NDAtYjhhZS05NzMxNmYzNWRiMjciIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4OTe6GAAAAx0lEQVR42u3WMQoAIQxFwRzJys77X8vSLiRgITif7bYbgrwYc/mKXyBoY4VVBgsWLFiwYFmOlTv+9jfDOjHmr8u6eVkGCxYsWLBgmc5S8ApewXvgYRksWLBgKXidpeBdloL3wMOCBctgwVLwCl7BuyyDBQsWLFiwTGcpeAWv4D3wsAwWLFiwFLzOUvAuS8F74GHBgmWwYCl4Ba/gXZbBggULFixYprMUvIJX8B54WAYLFixYCl5nKXiXpeA98LBgwTJYsGC9tg1o8f4TTtqzNQAAAABJRU5ErkJggg==", - "CLEAR_BACKGROUND_COLOR": "#040506", - "NODE_TITLE_COLOR": "#999", - "NODE_SELECTED_TITLE_COLOR": "#e5eaf0", - "NODE_TEXT_SIZE": 14, - "NODE_TEXT_COLOR": "#bcc2c8", - "NODE_SUBTEXT_SIZE": 12, - "NODE_DEFAULT_COLOR": "#161b22", - "NODE_DEFAULT_BGCOLOR": "#13171d", - "NODE_DEFAULT_BOXCOLOR": "#30363d", - "NODE_DEFAULT_SHAPE": "box", - "NODE_BOX_OUTLINE_COLOR": "#e5eaf0", - "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)", - "DEFAULT_GROUP_FONT": 24, - "WIDGET_BGCOLOR": "#161b22", - "WIDGET_OUTLINE_COLOR": "#30363d", - "WIDGET_TEXT_COLOR": "#bcc2c8", - "WIDGET_SECONDARY_TEXT_COLOR": "#999", - "LINK_COLOR": "#9A9", - "EVENT_LINK_COLOR": "#A86", - "CONNECTING_LINK_COLOR": "#AFA" - }, - "comfy_base": { - "fg-color": "#e5eaf0", - "bg-color": "#161b22", - "comfy-menu-bg": "#13171d", - "comfy-input-bg": "#161b22", - "input-text": "#bcc2c8", - "descrip-text": "#999", - "drag-text": "#ccc", - "error-text": "#ff4444", - "border-color": "#30363d", - "tr-even-bg-color": "#161b22", - "tr-odd-bg-color": "#13171d", - "content-bg": "#30363d", - "content-fg": "#e5eaf0", - "content-hover-bg": "#161b22", - "content-hover-fg": "#e5eaf0" - } - }, - } -}; - -const id = "Comfy.ColorPalette"; -const idCustomColorPalettes = "Comfy.CustomColorPalettes"; -const defaultColorPaletteId = "dark"; -const els = {} -// const ctxMenu = LiteGraph.ContextMenu; -app.registerExtension({ - name: id, - addCustomNodeDefs(node_defs) { - const sortObjectKeys = (unordered) => { - return Object.keys(unordered).sort().reduce((obj, key) => { - obj[key] = unordered[key]; - return obj; - }, {}); - }; - - function getSlotTypes() { - var types = []; - - const defs = node_defs; - for (const nodeId in defs) { - const nodeData = defs[nodeId]; - - var inputs = nodeData["input"]["required"]; - if (nodeData["input"]["optional"] !== undefined) { - inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"]) - } - - for (const inputName in inputs) { - const inputData = inputs[inputName]; - const type = inputData[0]; - - if (!Array.isArray(type)) { - types.push(type); - } - } - - for (const o in nodeData["output"]) { - const output = nodeData["output"][o]; - types.push(output); - } - } - - return types; - } - - function completeColorPalette(colorPalette) { - var types = getSlotTypes(); - - for (const type of types) { - if (!colorPalette.colors.node_slot[type]) { - colorPalette.colors.node_slot[type] = ""; - } - } - - colorPalette.colors.node_slot = sortObjectKeys(colorPalette.colors.node_slot); - - return colorPalette; - } - - const getColorPaletteTemplate = async () => { - let colorPalette = { - "id": "my_color_palette_unique_id", - "name": "My Color Palette", - "colors": { - "node_slot": {}, - "litegraph_base": {}, - "comfy_base": {} - } - }; - - // Copy over missing keys from default color palette - const defaultColorPalette = colorPalettes[defaultColorPaletteId]; - for (const key in defaultColorPalette.colors.litegraph_base) { - if (!colorPalette.colors.litegraph_base[key]) { - colorPalette.colors.litegraph_base[key] = ""; - } - } - for (const key in defaultColorPalette.colors.comfy_base) { - if (!colorPalette.colors.comfy_base[key]) { - colorPalette.colors.comfy_base[key] = ""; - } - } - - return completeColorPalette(colorPalette); - }; - - const getCustomColorPalettes = () => { - return app.ui.settings.getSettingValue(idCustomColorPalettes, {}); - }; - - const setCustomColorPalettes = (customColorPalettes) => { - return app.ui.settings.setSettingValue(idCustomColorPalettes, customColorPalettes); - }; - - const addCustomColorPalette = async (colorPalette) => { - if (typeof (colorPalette) !== "object") { - alert("Invalid color palette."); - return; - } - - if (!colorPalette.id) { - alert("Color palette missing id."); - return; - } - - if (!colorPalette.name) { - alert("Color palette missing name."); - return; - } - - if (!colorPalette.colors) { - alert("Color palette missing colors."); - return; - } - - if (colorPalette.colors.node_slot && typeof (colorPalette.colors.node_slot) !== "object") { - alert("Invalid color palette colors.node_slot."); - return; - } - - const customColorPalettes = getCustomColorPalettes(); - customColorPalettes[colorPalette.id] = colorPalette; - setCustomColorPalettes(customColorPalettes); - - for (const option of els.select.childNodes) { - if (option.value === "custom_" + colorPalette.id) { - els.select.removeChild(option); - } - } - - els.select.append($el("option", { - textContent: colorPalette.name + " (custom)", - value: "custom_" + colorPalette.id, - selected: true - })); - - setColorPalette("custom_" + colorPalette.id); - await loadColorPalette(colorPalette); - }; - - const deleteCustomColorPalette = async (colorPaletteId) => { - const customColorPalettes = getCustomColorPalettes(); - delete customColorPalettes[colorPaletteId]; - setCustomColorPalettes(customColorPalettes); - - for (const option of els.select.childNodes) { - if (option.value === defaultColorPaletteId) { - option.selected = true; - } - - if (option.value === "custom_" + colorPaletteId) { - els.select.removeChild(option); - } - } - - setColorPalette(defaultColorPaletteId); - await loadColorPalette(getColorPalette()); - }; - - const loadColorPalette = async (colorPalette) => { - colorPalette = await completeColorPalette(colorPalette); - if (colorPalette.colors) { - // Sets the colors of node slots and links - if (colorPalette.colors.node_slot) { - Object.assign(app.canvas.default_connection_color_byType, colorPalette.colors.node_slot); - Object.assign(LGraphCanvas.link_type_colors, colorPalette.colors.node_slot); - } - // Sets the colors of the LiteGraph objects - if (colorPalette.colors.litegraph_base) { - // Everything updates correctly in the loop, except the Node Title and Link Color for some reason - app.canvas.node_title_color = colorPalette.colors.litegraph_base.NODE_TITLE_COLOR; - app.canvas.default_link_color = colorPalette.colors.litegraph_base.LINK_COLOR; - - for (const key in colorPalette.colors.litegraph_base) { - if (colorPalette.colors.litegraph_base.hasOwnProperty(key) && LiteGraph.hasOwnProperty(key)) { - LiteGraph[key] = colorPalette.colors.litegraph_base[key]; - } - } - } - // Sets the color of ComfyUI elements - if (colorPalette.colors.comfy_base) { - const rootStyle = document.documentElement.style; - for (const key in colorPalette.colors.comfy_base) { - rootStyle.setProperty('--' + key, colorPalette.colors.comfy_base[key]); - } - } - app.canvas.draw(true, true); - } - }; - - const getColorPalette = (colorPaletteId) => { - if (!colorPaletteId) { - colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId); - } - - if (colorPaletteId.startsWith("custom_")) { - colorPaletteId = colorPaletteId.substr(7); - let customColorPalettes = getCustomColorPalettes(); - if (customColorPalettes[colorPaletteId]) { - return customColorPalettes[colorPaletteId]; - } - } - - return colorPalettes[colorPaletteId]; - }; - - const setColorPalette = (colorPaletteId) => { - app.ui.settings.setSettingValue(id, colorPaletteId); - }; - - const fileInput = $el("input", { - type: "file", - accept: ".json", - style: {display: "none"}, - parent: document.body, - onchange: () => { - const file = fileInput.files[0]; - if (file.type === "application/json" || file.name.endsWith(".json")) { - const reader = new FileReader(); - reader.onload = async () => { - await addCustomColorPalette(JSON.parse(reader.result)); - }; - reader.readAsText(file); - } - }, - }); - - app.ui.settings.addSetting({ - id, - name: "Color Palette", - type: (name, setter, value) => { - const options = [ - ...Object.values(colorPalettes).map(c=> $el("option", { - textContent: c.name, - value: c.id, - selected: c.id === value - })), - ...Object.values(getCustomColorPalettes()).map(c=>$el("option", { - textContent: `${c.name} (custom)`, - value: `custom_${c.id}`, - selected: `custom_${c.id}` === value - })) , - ]; - - els.select = $el("select", { - style: { - marginBottom: "0.15rem", - width: "100%", - }, - onchange: (e) => { - setter(e.target.value); - } - }, options) - - return $el("tr", [ - $el("td", [ - $el("label", { - for: id.replaceAll(".", "-"), - textContent: "Color palette", - }), - ]), - $el("td", [ - els.select, - $el("div", { - style: { - display: "grid", - gap: "4px", - gridAutoFlow: "column", - }, - }, [ - $el("input", { - type: "button", - value: "Export", - onclick: async () => { - const colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId); - const colorPalette = await completeColorPalette(getColorPalette(colorPaletteId)); - const json = JSON.stringify(colorPalette, null, 2); // convert the data to a JSON string - const blob = new Blob([json], {type: "application/json"}); - const url = URL.createObjectURL(blob); - const a = $el("a", { - href: url, - download: colorPaletteId + ".json", - style: {display: "none"}, - parent: document.body, - }); - a.click(); - setTimeout(function () { - a.remove(); - window.URL.revokeObjectURL(url); - }, 0); - }, - }), - $el("input", { - type: "button", - value: "Import", - onclick: () => { - fileInput.click(); - } - }), - $el("input", { - type: "button", - value: "Template", - onclick: async () => { - const colorPalette = await getColorPaletteTemplate(); - const json = JSON.stringify(colorPalette, null, 2); // convert the data to a JSON string - const blob = new Blob([json], {type: "application/json"}); - const url = URL.createObjectURL(blob); - const a = $el("a", { - href: url, - download: "color_palette.json", - style: {display: "none"}, - parent: document.body, - }); - a.click(); - setTimeout(function () { - a.remove(); - window.URL.revokeObjectURL(url); - }, 0); - } - }), - $el("input", { - type: "button", - value: "Delete", - onclick: async () => { - let colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId); - - if (colorPalettes[colorPaletteId]) { - alert("You cannot delete a built-in color palette."); - return; - } - - if (colorPaletteId.startsWith("custom_")) { - colorPaletteId = colorPaletteId.substr(7); - } - - await deleteCustomColorPalette(colorPaletteId); - } - }), - ]), - ]), - ]) - }, - defaultValue: defaultColorPaletteId, - async onChange(value) { - if (!value) { - return; - } - - let palette = colorPalettes[value]; - if (palette) { - await loadColorPalette(palette); - } else if (value.startsWith("custom_")) { - value = value.substr(7); - let customColorPalettes = getCustomColorPalettes(); - if (customColorPalettes[value]) { - palette = customColorPalettes[value]; - await loadColorPalette(customColorPalettes[value]); - } - } - - let {BACKGROUND_IMAGE, CLEAR_BACKGROUND_COLOR} = palette.colors.litegraph_base; - if (BACKGROUND_IMAGE === undefined || CLEAR_BACKGROUND_COLOR === undefined) { - const base = colorPalettes["dark"].colors.litegraph_base; - BACKGROUND_IMAGE = base.BACKGROUND_IMAGE; - CLEAR_BACKGROUND_COLOR = base.CLEAR_BACKGROUND_COLOR; - } - app.canvas.updateBackground(BACKGROUND_IMAGE, CLEAR_BACKGROUND_COLOR); - }, - }); - }, -}); diff --git a/web/extensions/core/contextMenuFilter.js b/web/extensions/core/contextMenuFilter.js deleted file mode 100644 index 0a305391a..000000000 --- a/web/extensions/core/contextMenuFilter.js +++ /dev/null @@ -1,148 +0,0 @@ -import {app} from "../../scripts/app.js"; - -// Adds filtering to combo context menus - -const ext = { - name: "Comfy.ContextMenuFilter", - init() { - const ctxMenu = LiteGraph.ContextMenu; - - LiteGraph.ContextMenu = function (values, options) { - const ctx = ctxMenu.call(this, values, options); - - // If we are a dark menu (only used for combo boxes) then add a filter input - if (options?.className === "dark" && values?.length > 10) { - const filter = document.createElement("input"); - filter.classList.add("comfy-context-menu-filter"); - filter.placeholder = "Filter list"; - this.root.prepend(filter); - - const items = Array.from(this.root.querySelectorAll(".litemenu-entry")); - let displayedItems = [...items]; - let itemCount = displayedItems.length; - - // We must request an animation frame for the current node of the active canvas to update. - requestAnimationFrame(() => { - const currentNode = LGraphCanvas.active_canvas.current_node; - const clickedComboValue = currentNode.widgets - ?.filter(w => w.type === "combo" && w.options.values.length === values.length) - .find(w => w.options.values.every((v, i) => v === values[i])) - ?.value; - - let selectedIndex = clickedComboValue ? values.findIndex(v => v === clickedComboValue) : 0; - if (selectedIndex < 0) { - selectedIndex = 0; - } - let selectedItem = displayedItems[selectedIndex]; - updateSelected(); - - // Apply highlighting to the selected item - function updateSelected() { - selectedItem?.style.setProperty("background-color", ""); - selectedItem?.style.setProperty("color", ""); - selectedItem = displayedItems[selectedIndex]; - selectedItem?.style.setProperty("background-color", "#ccc", "important"); - selectedItem?.style.setProperty("color", "#000", "important"); - } - - const positionList = () => { - const rect = this.root.getBoundingClientRect(); - - // If the top is off-screen then shift the element with scaling applied - if (rect.top < 0) { - const scale = 1 - this.root.getBoundingClientRect().height / this.root.clientHeight; - const shift = (this.root.clientHeight * scale) / 2; - this.root.style.top = -shift + "px"; - } - } - - // Arrow up/down to select items - filter.addEventListener("keydown", (event) => { - switch (event.key) { - case "ArrowUp": - event.preventDefault(); - if (selectedIndex === 0) { - selectedIndex = itemCount - 1; - } else { - selectedIndex--; - } - updateSelected(); - break; - case "ArrowRight": - event.preventDefault(); - selectedIndex = itemCount - 1; - updateSelected(); - break; - case "ArrowDown": - event.preventDefault(); - if (selectedIndex === itemCount - 1) { - selectedIndex = 0; - } else { - selectedIndex++; - } - updateSelected(); - break; - case "ArrowLeft": - event.preventDefault(); - selectedIndex = 0; - updateSelected(); - break; - case "Enter": - selectedItem?.click(); - break; - case "Escape": - this.close(); - break; - } - }); - - filter.addEventListener("input", () => { - // Hide all items that don't match our filter - const term = filter.value.toLocaleLowerCase(); - // When filtering, recompute which items are visible for arrow up/down and maintain selection. - displayedItems = items.filter(item => { - const isVisible = !term || item.textContent.toLocaleLowerCase().includes(term); - item.style.display = isVisible ? "block" : "none"; - return isVisible; - }); - - selectedIndex = 0; - if (displayedItems.includes(selectedItem)) { - selectedIndex = displayedItems.findIndex(d => d === selectedItem); - } - itemCount = displayedItems.length; - - updateSelected(); - - // If we have an event then we can try and position the list under the source - if (options.event) { - let top = options.event.clientY - 10; - - const bodyRect = document.body.getBoundingClientRect(); - const rootRect = this.root.getBoundingClientRect(); - if (bodyRect.height && top > bodyRect.height - rootRect.height - 10) { - top = Math.max(0, bodyRect.height - rootRect.height - 10); - } - - this.root.style.top = top + "px"; - positionList(); - } - }); - - requestAnimationFrame(() => { - // Focus the filter box when opening - filter.focus(); - - positionList(); - }); - }) - } - - return ctx; - }; - - LiteGraph.ContextMenu.prototype = ctxMenu.prototype; - }, -} - -app.registerExtension(ext); diff --git a/web/extensions/core/dynamicPrompts.js b/web/extensions/core/dynamicPrompts.js deleted file mode 100644 index 7417361ba..000000000 --- a/web/extensions/core/dynamicPrompts.js +++ /dev/null @@ -1,48 +0,0 @@ -import { app } from "../../scripts/app.js"; - -// Allows for simple dynamic prompt replacement -// Inputs in the format {a|b} will have a random value of a or b chosen when the prompt is queued. - -/* - * Strips C-style line and block comments from a string - */ -function stripComments(str) { - return str.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g,''); -} - -app.registerExtension({ - name: "Comfy.DynamicPrompts", - nodeCreated(node) { - if (node.widgets) { - // Locate dynamic prompt text widgets - // Include any widgets with dynamicPrompts set to true, and customtext - const widgets = node.widgets.filter( - (n) => n.dynamicPrompts - ); - for (const widget of widgets) { - // Override the serialization of the value to resolve dynamic prompts for all widgets supporting it in this node - widget.serializeValue = (workflowNode, widgetIndex) => { - let prompt = stripComments(widget.value); - while (prompt.replace("\\{", "").includes("{") && prompt.replace("\\}", "").includes("}")) { - const startIndex = prompt.replace("\\{", "00").indexOf("{"); - const endIndex = prompt.replace("\\}", "00").indexOf("}"); - - const optionsString = prompt.substring(startIndex + 1, endIndex); - const options = optionsString.split("|"); - - const randomIndex = Math.floor(Math.random() * options.length); - const randomOption = options[randomIndex]; - - prompt = prompt.substring(0, startIndex) + randomOption + prompt.substring(endIndex + 1); - } - - // Overwrite the value in the serialized workflow pnginfo - if (workflowNode?.widgets_values) - workflowNode.widgets_values[widgetIndex] = prompt; - - return prompt; - }; - } - } - }, -}); diff --git a/web/extensions/core/editAttention.js b/web/extensions/core/editAttention.js deleted file mode 100644 index 6792b2357..000000000 --- a/web/extensions/core/editAttention.js +++ /dev/null @@ -1,144 +0,0 @@ -import { app } from "../../scripts/app.js"; - -// Allows you to edit the attention weight by holding ctrl (or cmd) and using the up/down arrow keys - -app.registerExtension({ - name: "Comfy.EditAttention", - init() { - const editAttentionDelta = app.ui.settings.addSetting({ - id: "Comfy.EditAttention.Delta", - name: "Ctrl+up/down precision", - type: "slider", - attrs: { - min: 0.01, - max: 0.5, - step: 0.01, - }, - defaultValue: 0.05, - }); - - function incrementWeight(weight, delta) { - const floatWeight = parseFloat(weight); - if (isNaN(floatWeight)) return weight; - const newWeight = floatWeight + delta; - if (newWeight < 0) return "0"; - return String(Number(newWeight.toFixed(10))); - } - - function findNearestEnclosure(text, cursorPos) { - let start = cursorPos, end = cursorPos; - let openCount = 0, closeCount = 0; - - // Find opening parenthesis before cursor - while (start >= 0) { - start--; - if (text[start] === "(" && openCount === closeCount) break; - if (text[start] === "(") openCount++; - if (text[start] === ")") closeCount++; - } - if (start < 0) return false; - - openCount = 0; - closeCount = 0; - - // Find closing parenthesis after cursor - while (end < text.length) { - if (text[end] === ")" && openCount === closeCount) break; - if (text[end] === "(") openCount++; - if (text[end] === ")") closeCount++; - end++; - } - if (end === text.length) return false; - - return { start: start + 1, end: end }; - } - - function addWeightToParentheses(text) { - const parenRegex = /^\((.*)\)$/; - const parenMatch = text.match(parenRegex); - - const floatRegex = /:([+-]?(\d*\.)?\d+([eE][+-]?\d+)?)/; - const floatMatch = text.match(floatRegex); - - if (parenMatch && !floatMatch) { - return `(${parenMatch[1]}:1.0)`; - } else { - return text; - } - }; - - function editAttention(event) { - const inputField = event.composedPath()[0]; - const delta = parseFloat(editAttentionDelta.value); - - if (inputField.tagName !== "TEXTAREA") return; - if (!(event.key === "ArrowUp" || event.key === "ArrowDown")) return; - if (!event.ctrlKey && !event.metaKey) return; - - event.preventDefault(); - - let start = inputField.selectionStart; - let end = inputField.selectionEnd; - let selectedText = inputField.value.substring(start, end); - - // If there is no selection, attempt to find the nearest enclosure, or select the current word - if (!selectedText) { - const nearestEnclosure = findNearestEnclosure(inputField.value, start); - if (nearestEnclosure) { - start = nearestEnclosure.start; - end = nearestEnclosure.end; - selectedText = inputField.value.substring(start, end); - } else { - // Select the current word, find the start and end of the word - const delimiters = " .,\\/!?%^*;:{}=-_`~()\r\n\t"; - - while (!delimiters.includes(inputField.value[start - 1]) && start > 0) { - start--; - } - - while (!delimiters.includes(inputField.value[end]) && end < inputField.value.length) { - end++; - } - - selectedText = inputField.value.substring(start, end); - if (!selectedText) return; - } - } - - // If the selection ends with a space, remove it - if (selectedText[selectedText.length - 1] === " ") { - selectedText = selectedText.substring(0, selectedText.length - 1); - end -= 1; - } - - // If there are parentheses left and right of the selection, select them - if (inputField.value[start - 1] === "(" && inputField.value[end] === ")") { - start -= 1; - end += 1; - selectedText = inputField.value.substring(start, end); - } - - // If the selection is not enclosed in parentheses, add them - if (selectedText[0] !== "(" || selectedText[selectedText.length - 1] !== ")") { - selectedText = `(${selectedText})`; - } - - // If the selection does not have a weight, add a weight of 1.0 - selectedText = addWeightToParentheses(selectedText); - - // Increment the weight - const weightDelta = event.key === "ArrowUp" ? delta : -delta; - const updatedText = selectedText.replace(/\((.*):(\d+(?:\.\d+)?)\)/, (match, text, weight) => { - weight = incrementWeight(weight, weightDelta); - if (weight == 1) { - return text; - } else { - return `(${text}:${weight})`; - } - }); - - inputField.setRangeText(updatedText, start, end, "select"); - } - window.addEventListener("keydown", editAttention); - }, -}); diff --git a/web/extensions/core/groupNode.js b/web/extensions/core/groupNode.js deleted file mode 100644 index 163e42b89..000000000 --- a/web/extensions/core/groupNode.js +++ /dev/null @@ -1,1281 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js"; -import { mergeIfValid } from "./widgetInputs.js"; -import { ManageGroupDialog } from "./groupNodeManage.js"; - -const GROUP = Symbol(); - -const Workflow = { - InUse: { - Free: 0, - Registered: 1, - InWorkflow: 2, - }, - isInUseGroupNode(name) { - const id = `workflow/${name}`; - // Check if lready registered/in use in this workflow - if (app.graph.extra?.groupNodes?.[name]) { - if (app.graph._nodes.find((n) => n.type === id)) { - return Workflow.InUse.InWorkflow; - } else { - return Workflow.InUse.Registered; - } - } - return Workflow.InUse.Free; - }, - storeGroupNode(name, data) { - let extra = app.graph.extra; - if (!extra) app.graph.extra = extra = {}; - let groupNodes = extra.groupNodes; - if (!groupNodes) extra.groupNodes = groupNodes = {}; - groupNodes[name] = data; - }, -}; - -class GroupNodeBuilder { - constructor(nodes) { - this.nodes = nodes; - } - - build() { - const name = this.getName(); - if (!name) return; - - // Sort the nodes so they are in execution order - // this allows for widgets to be in the correct order when reconstructing - this.sortNodes(); - - this.nodeData = this.getNodeData(); - Workflow.storeGroupNode(name, this.nodeData); - - return { name, nodeData: this.nodeData }; - } - - getName() { - const name = prompt("Enter group name"); - if (!name) return; - const used = Workflow.isInUseGroupNode(name); - switch (used) { - case Workflow.InUse.InWorkflow: - alert( - "An in use group node with this name already exists embedded in this workflow, please remove any instances or use a new name." - ); - return; - case Workflow.InUse.Registered: - if (!confirm("A group node with this name already exists embedded in this workflow, are you sure you want to overwrite it?")) { - return; - } - break; - } - return name; - } - - sortNodes() { - // Gets the builders nodes in graph execution order - const nodesInOrder = app.graph.computeExecutionOrder(false); - this.nodes = this.nodes - .map((node) => ({ index: nodesInOrder.indexOf(node), node })) - .sort((a, b) => a.index - b.index || a.node.id - b.node.id) - .map(({ node }) => node); - } - - getNodeData() { - const storeLinkTypes = (config) => { - // Store link types for dynamically typed nodes e.g. reroutes - for (const link of config.links) { - const origin = app.graph.getNodeById(link[4]); - const type = origin.outputs[link[1]].type; - link.push(type); - } - }; - - const storeExternalLinks = (config) => { - // Store any external links to the group in the config so when rebuilding we add extra slots - config.external = []; - for (let i = 0; i < this.nodes.length; i++) { - const node = this.nodes[i]; - if (!node.outputs?.length) continue; - for (let slot = 0; slot < node.outputs.length; slot++) { - let hasExternal = false; - const output = node.outputs[slot]; - let type = output.type; - if (!output.links?.length) continue; - for (const l of output.links) { - const link = app.graph.links[l]; - if (!link) continue; - if (type === "*") type = link.type; - - if (!app.canvas.selected_nodes[link.target_id]) { - hasExternal = true; - break; - } - } - if (hasExternal) { - config.external.push([i, slot, type]); - } - } - } - }; - - // Use the built in copyToClipboard function to generate the node data we need - const backup = localStorage.getItem("litegrapheditor_clipboard"); - try { - app.canvas.copyToClipboard(this.nodes); - const config = JSON.parse(localStorage.getItem("litegrapheditor_clipboard")); - - storeLinkTypes(config); - storeExternalLinks(config); - - return config; - } finally { - localStorage.setItem("litegrapheditor_clipboard", backup); - } - } -} - -export class GroupNodeConfig { - constructor(name, nodeData) { - this.name = name; - this.nodeData = nodeData; - this.getLinks(); - - this.inputCount = 0; - this.oldToNewOutputMap = {}; - this.newToOldOutputMap = {}; - this.oldToNewInputMap = {}; - this.oldToNewWidgetMap = {}; - this.newToOldWidgetMap = {}; - this.primitiveDefs = {}; - this.widgetToPrimitive = {}; - this.primitiveToWidget = {}; - this.nodeInputs = {}; - this.outputVisibility = []; - } - - async registerType(source = "workflow") { - this.nodeDef = { - output: [], - output_name: [], - output_is_list: [], - output_is_hidden: [], - name: source + "/" + this.name, - display_name: this.name, - category: "group nodes" + ("/" + source), - input: { required: {} }, - - [GROUP]: this, - }; - - this.inputs = []; - const seenInputs = {}; - const seenOutputs = {}; - for (let i = 0; i < this.nodeData.nodes.length; i++) { - const node = this.nodeData.nodes[i]; - node.index = i; - this.processNode(node, seenInputs, seenOutputs); - } - - for (const p of this.#convertedToProcess) { - p(); - } - this.#convertedToProcess = null; - await app.registerNodeDef("workflow/" + this.name, this.nodeDef); - } - - getLinks() { - this.linksFrom = {}; - this.linksTo = {}; - this.externalFrom = {}; - - // Extract links for easy lookup - for (const l of this.nodeData.links) { - const [sourceNodeId, sourceNodeSlot, targetNodeId, targetNodeSlot] = l; - - // Skip links outside the copy config - if (sourceNodeId == null) continue; - - if (!this.linksFrom[sourceNodeId]) { - this.linksFrom[sourceNodeId] = {}; - } - if (!this.linksFrom[sourceNodeId][sourceNodeSlot]) { - this.linksFrom[sourceNodeId][sourceNodeSlot] = []; - } - this.linksFrom[sourceNodeId][sourceNodeSlot].push(l); - - if (!this.linksTo[targetNodeId]) { - this.linksTo[targetNodeId] = {}; - } - this.linksTo[targetNodeId][targetNodeSlot] = l; - } - - if (this.nodeData.external) { - for (const ext of this.nodeData.external) { - if (!this.externalFrom[ext[0]]) { - this.externalFrom[ext[0]] = { [ext[1]]: ext[2] }; - } else { - this.externalFrom[ext[0]][ext[1]] = ext[2]; - } - } - } - } - - processNode(node, seenInputs, seenOutputs) { - const def = this.getNodeDef(node); - if (!def) return; - - const inputs = { ...def.input?.required, ...def.input?.optional }; - - this.inputs.push(this.processNodeInputs(node, seenInputs, inputs)); - if (def.output?.length) this.processNodeOutputs(node, seenOutputs, def); - } - - getNodeDef(node) { - const def = globalDefs[node.type]; - if (def) return def; - - const linksFrom = this.linksFrom[node.index]; - if (node.type === "PrimitiveNode") { - // Skip as its not linked - if (!linksFrom) return; - - let type = linksFrom["0"][0][5]; - if (type === "COMBO") { - // Use the array items - const source = node.outputs[0].widget.name; - const fromTypeName = this.nodeData.nodes[linksFrom["0"][0][2]].type; - const fromType = globalDefs[fromTypeName]; - const input = fromType.input.required[source] ?? fromType.input.optional[source]; - type = input[0]; - } - - const def = (this.primitiveDefs[node.index] = { - input: { - required: { - value: [type, {}], - }, - }, - output: [type], - output_name: [], - output_is_list: [], - }); - return def; - } else if (node.type === "Reroute") { - const linksTo = this.linksTo[node.index]; - if (linksTo && linksFrom && !this.externalFrom[node.index]?.[0]) { - // Being used internally - return null; - } - - let config = {}; - let rerouteType = "*"; - if (linksFrom) { - for (const [, , id, slot] of linksFrom["0"]) { - const node = this.nodeData.nodes[id]; - const input = node.inputs[slot]; - if (rerouteType === "*") { - rerouteType = input.type; - } - if (input.widget) { - const targetDef = globalDefs[node.type]; - const targetWidget = targetDef.input.required[input.widget.name] ?? targetDef.input.optional[input.widget.name]; - - const widget = [targetWidget[0], config]; - const res = mergeIfValid( - { - widget, - }, - targetWidget, - false, - null, - widget - ); - config = res?.customConfig ?? config; - } - } - } else if (linksTo) { - const [id, slot] = linksTo["0"]; - rerouteType = this.nodeData.nodes[id].outputs[slot].type; - } else { - // Reroute used as a pipe - for (const l of this.nodeData.links) { - if (l[2] === node.index) { - rerouteType = l[5]; - break; - } - } - if (rerouteType === "*") { - // Check for an external link - const t = this.externalFrom[node.index]?.[0]; - if (t) { - rerouteType = t; - } - } - } - - config.forceInput = true; - return { - input: { - required: { - [rerouteType]: [rerouteType, config], - }, - }, - output: [rerouteType], - output_name: [], - output_is_list: [], - }; - } - - console.warn("Skipping virtual node " + node.type + " when building group node " + this.name); - } - - getInputConfig(node, inputName, seenInputs, config, extra) { - const customConfig = this.nodeData.config?.[node.index]?.input?.[inputName]; - let name = customConfig?.name ?? node.inputs?.find((inp) => inp.name === inputName)?.label ?? inputName; - let key = name; - let prefix = ""; - // Special handling for primitive to include the title if it is set rather than just "value" - if ((node.type === "PrimitiveNode" && node.title) || name in seenInputs) { - prefix = `${node.title ?? node.type} `; - key = name = `${prefix}${inputName}`; - if (name in seenInputs) { - name = `${prefix}${seenInputs[name]} ${inputName}`; - } - } - seenInputs[key] = (seenInputs[key] ?? 1) + 1; - - if (inputName === "seed" || inputName === "noise_seed") { - if (!extra) extra = {}; - extra.control_after_generate = `${prefix}control_after_generate`; - } - if (config[0] === "IMAGEUPLOAD") { - if (!extra) extra = {}; - extra.widget = this.oldToNewWidgetMap[node.index]?.[config[1]?.widget ?? "image"] ?? "image"; - } - - if (extra) { - config = [config[0], { ...config[1], ...extra }]; - } - - return { name, config, customConfig }; - } - - processWidgetInputs(inputs, node, inputNames, seenInputs) { - const slots = []; - const converted = new Map(); - const widgetMap = (this.oldToNewWidgetMap[node.index] = {}); - for (const inputName of inputNames) { - let widgetType = app.getWidgetType(inputs[inputName], inputName); - if (widgetType) { - const convertedIndex = node.inputs?.findIndex((inp) => inp.name === inputName && inp.widget?.name === inputName); - if (convertedIndex > -1) { - // This widget has been converted to a widget - // We need to store this in the correct position so link ids line up - converted.set(convertedIndex, inputName); - widgetMap[inputName] = null; - } else { - // Normal widget - const { name, config } = this.getInputConfig(node, inputName, seenInputs, inputs[inputName]); - this.nodeDef.input.required[name] = config; - widgetMap[inputName] = name; - this.newToOldWidgetMap[name] = { node, inputName }; - } - } else { - // Normal input - slots.push(inputName); - } - } - return { converted, slots }; - } - - checkPrimitiveConnection(link, inputName, inputs) { - const sourceNode = this.nodeData.nodes[link[0]]; - if (sourceNode.type === "PrimitiveNode") { - // Merge link configurations - const [sourceNodeId, _, targetNodeId, __] = link; - const primitiveDef = this.primitiveDefs[sourceNodeId]; - const targetWidget = inputs[inputName]; - const primitiveConfig = primitiveDef.input.required.value; - const output = { widget: primitiveConfig }; - const config = mergeIfValid(output, targetWidget, false, null, primitiveConfig); - primitiveConfig[1] = config?.customConfig ?? inputs[inputName][1] ? { ...inputs[inputName][1] } : {}; - - let name = this.oldToNewWidgetMap[sourceNodeId]["value"]; - name = name.substr(0, name.length - 6); - primitiveConfig[1].control_after_generate = true; - primitiveConfig[1].control_prefix = name; - - let toPrimitive = this.widgetToPrimitive[targetNodeId]; - if (!toPrimitive) { - toPrimitive = this.widgetToPrimitive[targetNodeId] = {}; - } - if (toPrimitive[inputName]) { - toPrimitive[inputName].push(sourceNodeId); - } - toPrimitive[inputName] = sourceNodeId; - - let toWidget = this.primitiveToWidget[sourceNodeId]; - if (!toWidget) { - toWidget = this.primitiveToWidget[sourceNodeId] = []; - } - toWidget.push({ nodeId: targetNodeId, inputName }); - } - } - - processInputSlots(inputs, node, slots, linksTo, inputMap, seenInputs) { - this.nodeInputs[node.index] = {}; - for (let i = 0; i < slots.length; i++) { - const inputName = slots[i]; - if (linksTo[i]) { - this.checkPrimitiveConnection(linksTo[i], inputName, inputs); - // This input is linked so we can skip it - continue; - } - - const { name, config, customConfig } = this.getInputConfig(node, inputName, seenInputs, inputs[inputName]); - - this.nodeInputs[node.index][inputName] = name; - if(customConfig?.visible === false) continue; - - this.nodeDef.input.required[name] = config; - inputMap[i] = this.inputCount++; - } - } - - processConvertedWidgets(inputs, node, slots, converted, linksTo, inputMap, seenInputs) { - // Add converted widgets sorted into their index order (ordered as they were converted) so link ids match up - const convertedSlots = [...converted.keys()].sort().map((k) => converted.get(k)); - for (let i = 0; i < convertedSlots.length; i++) { - const inputName = convertedSlots[i]; - if (linksTo[slots.length + i]) { - this.checkPrimitiveConnection(linksTo[slots.length + i], inputName, inputs); - // This input is linked so we can skip it - continue; - } - - const { name, config } = this.getInputConfig(node, inputName, seenInputs, inputs[inputName], { - defaultInput: true, - }); - - this.nodeDef.input.required[name] = config; - this.newToOldWidgetMap[name] = { node, inputName }; - - if (!this.oldToNewWidgetMap[node.index]) { - this.oldToNewWidgetMap[node.index] = {}; - } - this.oldToNewWidgetMap[node.index][inputName] = name; - - inputMap[slots.length + i] = this.inputCount++; - } - } - - #convertedToProcess = []; - processNodeInputs(node, seenInputs, inputs) { - const inputMapping = []; - - const inputNames = Object.keys(inputs); - if (!inputNames.length) return; - - const { converted, slots } = this.processWidgetInputs(inputs, node, inputNames, seenInputs); - const linksTo = this.linksTo[node.index] ?? {}; - const inputMap = (this.oldToNewInputMap[node.index] = {}); - this.processInputSlots(inputs, node, slots, linksTo, inputMap, seenInputs); - - // Converted inputs have to be processed after all other nodes as they'll be at the end of the list - this.#convertedToProcess.push(() => this.processConvertedWidgets(inputs, node, slots, converted, linksTo, inputMap, seenInputs)); - - return inputMapping; - } - - processNodeOutputs(node, seenOutputs, def) { - const oldToNew = (this.oldToNewOutputMap[node.index] = {}); - - // Add outputs - for (let outputId = 0; outputId < def.output.length; outputId++) { - const linksFrom = this.linksFrom[node.index]; - // If this output is linked internally we flag it to hide - const hasLink = linksFrom?.[outputId] && !this.externalFrom[node.index]?.[outputId]; - const customConfig = this.nodeData.config?.[node.index]?.output?.[outputId]; - const visible = customConfig?.visible ?? !hasLink; - this.outputVisibility.push(visible); - if (!visible) { - continue; - } - - oldToNew[outputId] = this.nodeDef.output.length; - this.newToOldOutputMap[this.nodeDef.output.length] = { node, slot: outputId }; - this.nodeDef.output.push(def.output[outputId]); - this.nodeDef.output_is_list.push(def.output_is_list[outputId]); - - let label = customConfig?.name; - if (!label) { - label = def.output_name?.[outputId] ?? def.output[outputId]; - const output = node.outputs.find((o) => o.name === label); - if (output?.label) { - label = output.label; - } - } - - let name = label; - if (name in seenOutputs) { - const prefix = `${node.title ?? node.type} `; - name = `${prefix}${label}`; - if (name in seenOutputs) { - name = `${prefix}${node.index} ${label}`; - } - } - seenOutputs[name] = 1; - - this.nodeDef.output_name.push(name); - } - } - - static async registerFromWorkflow(groupNodes, missingNodeTypes) { - const clean = app.clean; - app.clean = function () { - for (const g in groupNodes) { - try { - LiteGraph.unregisterNodeType("workflow/" + g); - } catch (error) {} - } - app.clean = clean; - }; - - for (const g in groupNodes) { - const groupData = groupNodes[g]; - - let hasMissing = false; - for (const n of groupData.nodes) { - // Find missing node types - if (!(n.type in LiteGraph.registered_node_types)) { - missingNodeTypes.push({ - type: n.type, - hint: ` (In group node 'workflow/${g}')`, - }); - - missingNodeTypes.push({ - type: "workflow/" + g, - action: { - text: "Remove from workflow", - callback: (e) => { - delete groupNodes[g]; - e.target.textContent = "Removed"; - e.target.style.pointerEvents = "none"; - e.target.style.opacity = 0.7; - }, - }, - }); - - hasMissing = true; - } - } - - if (hasMissing) continue; - - const config = new GroupNodeConfig(g, groupData); - await config.registerType(); - } - } -} - -export class GroupNodeHandler { - node; - groupData; - - constructor(node) { - this.node = node; - this.groupData = node.constructor?.nodeData?.[GROUP]; - - this.node.setInnerNodes = (innerNodes) => { - this.innerNodes = innerNodes; - - for (let innerNodeIndex = 0; innerNodeIndex < this.innerNodes.length; innerNodeIndex++) { - const innerNode = this.innerNodes[innerNodeIndex]; - - for (const w of innerNode.widgets ?? []) { - if (w.type === "converted-widget") { - w.serializeValue = w.origSerializeValue; - } - } - - innerNode.index = innerNodeIndex; - innerNode.getInputNode = (slot) => { - // Check if this input is internal or external - const externalSlot = this.groupData.oldToNewInputMap[innerNode.index]?.[slot]; - if (externalSlot != null) { - return this.node.getInputNode(externalSlot); - } - - // Internal link - const innerLink = this.groupData.linksTo[innerNode.index]?.[slot]; - if (!innerLink) return null; - - const inputNode = innerNodes[innerLink[0]]; - // Primitives will already apply their values - if (inputNode.type === "PrimitiveNode") return null; - - return inputNode; - }; - - innerNode.getInputLink = (slot) => { - const externalSlot = this.groupData.oldToNewInputMap[innerNode.index]?.[slot]; - if (externalSlot != null) { - // The inner node is connected via the group node inputs - const linkId = this.node.inputs[externalSlot].link; - let link = app.graph.links[linkId]; - - // Use the outer link, but update the target to the inner node - link = { - ...link, - target_id: innerNode.id, - target_slot: +slot, - }; - return link; - } - - let link = this.groupData.linksTo[innerNode.index]?.[slot]; - if (!link) return null; - // Use the inner link, but update the origin node to be inner node id - link = { - origin_id: innerNodes[link[0]].id, - origin_slot: link[1], - target_id: innerNode.id, - target_slot: +slot, - }; - return link; - }; - } - }; - - this.node.updateLink = (link) => { - // Replace the group node reference with the internal node - link = { ...link }; - const output = this.groupData.newToOldOutputMap[link.origin_slot]; - let innerNode = this.innerNodes[output.node.index]; - let l; - while (innerNode?.type === "Reroute") { - l = innerNode.getInputLink(0); - innerNode = innerNode.getInputNode(0); - } - - if (!innerNode) { - return null; - } - - if (l && GroupNodeHandler.isGroupNode(innerNode)) { - return innerNode.updateLink(l); - } - - link.origin_id = innerNode.id; - link.origin_slot = l?.origin_slot ?? output.slot; - return link; - }; - - this.node.getInnerNodes = () => { - if (!this.innerNodes) { - this.node.setInnerNodes( - this.groupData.nodeData.nodes.map((n, i) => { - const innerNode = LiteGraph.createNode(n.type); - innerNode.configure(n); - innerNode.id = `${this.node.id}:${i}`; - return innerNode; - }) - ); - } - - this.updateInnerWidgets(); - - return this.innerNodes; - }; - - this.node.recreate = async () => { - const id = this.node.id; - const sz = this.node.size; - const nodes = this.node.convertToNodes(); - - const groupNode = LiteGraph.createNode(this.node.type); - groupNode.id = id; - - // Reuse the existing nodes for this instance - groupNode.setInnerNodes(nodes); - groupNode[GROUP].populateWidgets(); - app.graph.add(groupNode); - groupNode.size = [Math.max(groupNode.size[0], sz[0]), Math.max(groupNode.size[1], sz[1])]; - - // Remove all converted nodes and relink them - groupNode[GROUP].replaceNodes(nodes); - return groupNode; - }; - - this.node.convertToNodes = () => { - const addInnerNodes = () => { - const backup = localStorage.getItem("litegrapheditor_clipboard"); - // Clone the node data so we dont mutate it for other nodes - const c = { ...this.groupData.nodeData }; - c.nodes = [...c.nodes]; - const innerNodes = this.node.getInnerNodes(); - let ids = []; - for (let i = 0; i < c.nodes.length; i++) { - let id = innerNodes?.[i]?.id; - // Use existing IDs if they are set on the inner nodes - if (id == null || isNaN(id)) { - id = undefined; - } else { - ids.push(id); - } - c.nodes[i] = { ...c.nodes[i], id }; - } - localStorage.setItem("litegrapheditor_clipboard", JSON.stringify(c)); - app.canvas.pasteFromClipboard(); - localStorage.setItem("litegrapheditor_clipboard", backup); - - const [x, y] = this.node.pos; - let top; - let left; - // Configure nodes with current widget data - const selectedIds = ids.length ? ids : Object.keys(app.canvas.selected_nodes); - const newNodes = []; - for (let i = 0; i < selectedIds.length; i++) { - const id = selectedIds[i]; - const newNode = app.graph.getNodeById(id); - const innerNode = innerNodes[i]; - newNodes.push(newNode); - - if (left == null || newNode.pos[0] < left) { - left = newNode.pos[0]; - } - if (top == null || newNode.pos[1] < top) { - top = newNode.pos[1]; - } - - if (!newNode.widgets) continue; - - const map = this.groupData.oldToNewWidgetMap[innerNode.index]; - if (map) { - const widgets = Object.keys(map); - - for (const oldName of widgets) { - const newName = map[oldName]; - if (!newName) continue; - - const widgetIndex = this.node.widgets.findIndex((w) => w.name === newName); - if (widgetIndex === -1) continue; - - // Populate the main and any linked widgets - if (innerNode.type === "PrimitiveNode") { - for (let i = 0; i < newNode.widgets.length; i++) { - newNode.widgets[i].value = this.node.widgets[widgetIndex + i].value; - } - } else { - const outerWidget = this.node.widgets[widgetIndex]; - const newWidget = newNode.widgets.find((w) => w.name === oldName); - if (!newWidget) continue; - - newWidget.value = outerWidget.value; - for (let w = 0; w < outerWidget.linkedWidgets?.length; w++) { - newWidget.linkedWidgets[w].value = outerWidget.linkedWidgets[w].value; - } - } - } - } - } - - // Shift each node - for (const newNode of newNodes) { - newNode.pos = [newNode.pos[0] - (left - x), newNode.pos[1] - (top - y)]; - } - - return { newNodes, selectedIds }; - }; - - const reconnectInputs = (selectedIds) => { - for (const innerNodeIndex in this.groupData.oldToNewInputMap) { - const id = selectedIds[innerNodeIndex]; - const newNode = app.graph.getNodeById(id); - const map = this.groupData.oldToNewInputMap[innerNodeIndex]; - for (const innerInputId in map) { - const groupSlotId = map[innerInputId]; - if (groupSlotId == null) continue; - const slot = node.inputs[groupSlotId]; - if (slot.link == null) continue; - const link = app.graph.links[slot.link]; - if (!link) continue; - // connect this node output to the input of another node - const originNode = app.graph.getNodeById(link.origin_id); - originNode.connect(link.origin_slot, newNode, +innerInputId); - } - } - }; - - const reconnectOutputs = (selectedIds) => { - for (let groupOutputId = 0; groupOutputId < node.outputs?.length; groupOutputId++) { - const output = node.outputs[groupOutputId]; - if (!output.links) continue; - const links = [...output.links]; - for (const l of links) { - const slot = this.groupData.newToOldOutputMap[groupOutputId]; - const link = app.graph.links[l]; - const targetNode = app.graph.getNodeById(link.target_id); - const newNode = app.graph.getNodeById(selectedIds[slot.node.index]); - newNode.connect(slot.slot, targetNode, link.target_slot); - } - } - }; - - const { newNodes, selectedIds } = addInnerNodes(); - reconnectInputs(selectedIds); - reconnectOutputs(selectedIds); - app.graph.remove(this.node); - - return newNodes; - }; - - const getExtraMenuOptions = this.node.getExtraMenuOptions; - this.node.getExtraMenuOptions = function (_, options) { - getExtraMenuOptions?.apply(this, arguments); - - let optionIndex = options.findIndex((o) => o.content === "Outputs"); - if (optionIndex === -1) optionIndex = options.length; - else optionIndex++; - options.splice( - optionIndex, - 0, - null, - { - content: "Convert to nodes", - callback: () => { - return this.convertToNodes(); - }, - }, - { - content: "Manage Group Node", - callback: () => { - new ManageGroupDialog(app).show(this.type); - }, - } - ); - }; - - // Draw custom collapse icon to identity this as a group - const onDrawTitleBox = this.node.onDrawTitleBox; - this.node.onDrawTitleBox = function (ctx, height, size, scale) { - onDrawTitleBox?.apply(this, arguments); - - const fill = ctx.fillStyle; - ctx.beginPath(); - ctx.rect(11, -height + 11, 2, 2); - ctx.rect(14, -height + 11, 2, 2); - ctx.rect(17, -height + 11, 2, 2); - ctx.rect(11, -height + 14, 2, 2); - ctx.rect(14, -height + 14, 2, 2); - ctx.rect(17, -height + 14, 2, 2); - ctx.rect(11, -height + 17, 2, 2); - ctx.rect(14, -height + 17, 2, 2); - ctx.rect(17, -height + 17, 2, 2); - - ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.fill(); - ctx.fillStyle = fill; - }; - - // Draw progress label - const onDrawForeground = node.onDrawForeground; - const groupData = this.groupData.nodeData; - node.onDrawForeground = function (ctx) { - const r = onDrawForeground?.apply?.(this, arguments); - if (+app.runningNodeId === this.id && this.runningInternalNodeId !== null) { - const n = groupData.nodes[this.runningInternalNodeId]; - if(!n) return; - const message = `Running ${n.title || n.type} (${this.runningInternalNodeId}/${groupData.nodes.length})`; - ctx.save(); - ctx.font = "12px sans-serif"; - const sz = ctx.measureText(message); - ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.beginPath(); - ctx.roundRect(0, -LiteGraph.NODE_TITLE_HEIGHT - 20, sz.width + 12, 20, 5); - ctx.fill(); - - ctx.fillStyle = "#fff"; - ctx.fillText(message, 6, -LiteGraph.NODE_TITLE_HEIGHT - 6); - ctx.restore(); - } - }; - - // Flag this node as needing to be reset - const onExecutionStart = this.node.onExecutionStart; - this.node.onExecutionStart = function () { - this.resetExecution = true; - return onExecutionStart?.apply(this, arguments); - }; - - const self = this; - const onNodeCreated = this.node.onNodeCreated; - this.node.onNodeCreated = function () { - if (!this.widgets) { - return; - } - const config = self.groupData.nodeData.config; - if (config) { - for (const n in config) { - const inputs = config[n]?.input; - for (const w in inputs) { - if (inputs[w].visible !== false) continue; - const widgetName = self.groupData.oldToNewWidgetMap[n][w]; - const widget = this.widgets.find((w) => w.name === widgetName); - if (widget) { - widget.type = "hidden"; - widget.computeSize = () => [0, -4]; - } - } - } - } - - return onNodeCreated?.apply(this, arguments); - }; - - function handleEvent(type, getId, getEvent) { - const handler = ({ detail }) => { - const id = getId(detail); - if (!id) return; - const node = app.graph.getNodeById(id); - if (node) return; - - const innerNodeIndex = this.innerNodes?.findIndex((n) => n.id == id); - if (innerNodeIndex > -1) { - this.node.runningInternalNodeId = innerNodeIndex; - api.dispatchEvent(new CustomEvent(type, { detail: getEvent(detail, this.node.id + "", this.node) })); - } - }; - api.addEventListener(type, handler); - return handler; - } - - const executing = handleEvent.call( - this, - "executing", - (d) => d, - (d, id, node) => id - ); - - const executed = handleEvent.call( - this, - "executed", - (d) => d?.display_node, - (d, id, node) => ({ ...d, node: id, display_node: id, merge: !node.resetExecution }) - ); - - const onRemoved = node.onRemoved; - this.node.onRemoved = function () { - onRemoved?.apply(this, arguments); - api.removeEventListener("executing", executing); - api.removeEventListener("executed", executed); - }; - - this.node.refreshComboInNode = (defs) => { - // Update combo widget options - for (const widgetName in this.groupData.newToOldWidgetMap) { - const widget = this.node.widgets.find((w) => w.name === widgetName); - if (widget?.type === "combo") { - const old = this.groupData.newToOldWidgetMap[widgetName]; - const def = defs[old.node.type]; - const input = def?.input?.required?.[old.inputName] ?? def?.input?.optional?.[old.inputName]; - if (!input) continue; - - widget.options.values = input[0]; - - if (old.inputName !== "image" && !widget.options.values.includes(widget.value)) { - widget.value = widget.options.values[0]; - widget.callback(widget.value); - } - } - } - }; - } - - updateInnerWidgets() { - for (const newWidgetName in this.groupData.newToOldWidgetMap) { - const newWidget = this.node.widgets.find((w) => w.name === newWidgetName); - if (!newWidget) continue; - - const newValue = newWidget.value; - const old = this.groupData.newToOldWidgetMap[newWidgetName]; - let innerNode = this.innerNodes[old.node.index]; - - if (innerNode.type === "PrimitiveNode") { - innerNode.primitiveValue = newValue; - const primitiveLinked = this.groupData.primitiveToWidget[old.node.index]; - for (const linked of primitiveLinked ?? []) { - const node = this.innerNodes[linked.nodeId]; - const widget = node.widgets.find((w) => w.name === linked.inputName); - - if (widget) { - widget.value = newValue; - } - } - continue; - } else if (innerNode.type === "Reroute") { - const rerouteLinks = this.groupData.linksFrom[old.node.index]; - if (rerouteLinks) { - for (const [_, , targetNodeId, targetSlot] of rerouteLinks["0"]) { - const node = this.innerNodes[targetNodeId]; - const input = node.inputs[targetSlot]; - if (input.widget) { - const widget = node.widgets?.find((w) => w.name === input.widget.name); - if (widget) { - widget.value = newValue; - } - } - } - } - } - - const widget = innerNode.widgets?.find((w) => w.name === old.inputName); - if (widget) { - widget.value = newValue; - } - } - } - - populatePrimitive(node, nodeId, oldName, i, linkedShift) { - // Converted widget, populate primitive if linked - const primitiveId = this.groupData.widgetToPrimitive[nodeId]?.[oldName]; - if (primitiveId == null) return; - const targetWidgetName = this.groupData.oldToNewWidgetMap[primitiveId]["value"]; - const targetWidgetIndex = this.node.widgets.findIndex((w) => w.name === targetWidgetName); - if (targetWidgetIndex > -1) { - const primitiveNode = this.innerNodes[primitiveId]; - let len = primitiveNode.widgets.length; - if (len - 1 !== this.node.widgets[targetWidgetIndex].linkedWidgets?.length) { - // Fallback handling for if some reason the primitive has a different number of widgets - // we dont want to overwrite random widgets, better to leave blank - len = 1; - } - for (let i = 0; i < len; i++) { - this.node.widgets[targetWidgetIndex + i].value = primitiveNode.widgets[i].value; - } - } - return true; - } - - populateReroute(node, nodeId, map) { - if (node.type !== "Reroute") return; - - const link = this.groupData.linksFrom[nodeId]?.[0]?.[0]; - if (!link) return; - const [, , targetNodeId, targetNodeSlot] = link; - const targetNode = this.groupData.nodeData.nodes[targetNodeId]; - const inputs = targetNode.inputs; - const targetWidget = inputs?.[targetNodeSlot]?.widget; - if (!targetWidget) return; - - const offset = inputs.length - (targetNode.widgets_values?.length ?? 0); - const v = targetNode.widgets_values?.[targetNodeSlot - offset]; - if (v == null) return; - - const widgetName = Object.values(map)[0]; - const widget = this.node.widgets.find((w) => w.name === widgetName); - if (widget) { - widget.value = v; - } - } - - populateWidgets() { - if (!this.node.widgets) return; - - for (let nodeId = 0; nodeId < this.groupData.nodeData.nodes.length; nodeId++) { - const node = this.groupData.nodeData.nodes[nodeId]; - const map = this.groupData.oldToNewWidgetMap[nodeId] ?? {}; - const widgets = Object.keys(map); - - if (!node.widgets_values?.length) { - // special handling for populating values into reroutes - // this allows primitives connect to them to pick up the correct value - this.populateReroute(node, nodeId, map); - continue; - } - - let linkedShift = 0; - for (let i = 0; i < widgets.length; i++) { - const oldName = widgets[i]; - const newName = map[oldName]; - const widgetIndex = this.node.widgets.findIndex((w) => w.name === newName); - const mainWidget = this.node.widgets[widgetIndex]; - if (this.populatePrimitive(node, nodeId, oldName, i, linkedShift) || widgetIndex === -1) { - // Find the inner widget and shift by the number of linked widgets as they will have been removed too - const innerWidget = this.innerNodes[nodeId].widgets?.find((w) => w.name === oldName); - linkedShift += innerWidget?.linkedWidgets?.length ?? 0; - } - if (widgetIndex === -1) { - continue; - } - - // Populate the main and any linked widget - mainWidget.value = node.widgets_values[i + linkedShift]; - for (let w = 0; w < mainWidget.linkedWidgets?.length; w++) { - this.node.widgets[widgetIndex + w + 1].value = node.widgets_values[i + ++linkedShift]; - } - } - } - } - - replaceNodes(nodes) { - let top; - let left; - - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - if (left == null || node.pos[0] < left) { - left = node.pos[0]; - } - if (top == null || node.pos[1] < top) { - top = node.pos[1]; - } - - this.linkOutputs(node, i); - app.graph.remove(node); - } - - this.linkInputs(); - this.node.pos = [left, top]; - } - - linkOutputs(originalNode, nodeId) { - if (!originalNode.outputs) return; - - for (const output of originalNode.outputs) { - if (!output.links) continue; - // Clone the links as they'll be changed if we reconnect - const links = [...output.links]; - for (const l of links) { - const link = app.graph.links[l]; - if (!link) continue; - - const targetNode = app.graph.getNodeById(link.target_id); - const newSlot = this.groupData.oldToNewOutputMap[nodeId]?.[link.origin_slot]; - if (newSlot != null) { - this.node.connect(newSlot, targetNode, link.target_slot); - } - } - } - } - - linkInputs() { - for (const link of this.groupData.nodeData.links ?? []) { - const [, originSlot, targetId, targetSlot, actualOriginId] = link; - const originNode = app.graph.getNodeById(actualOriginId); - if (!originNode) continue; // this node is in the group - originNode.connect(originSlot, this.node.id, this.groupData.oldToNewInputMap[targetId][targetSlot]); - } - } - - static getGroupData(node) { - return (node.nodeData ?? node.constructor?.nodeData)?.[GROUP]; - } - - static isGroupNode(node) { - return !!node.constructor?.nodeData?.[GROUP]; - } - - static async fromNodes(nodes) { - // Process the nodes into the stored workflow group node data - const builder = new GroupNodeBuilder(nodes); - const res = builder.build(); - if (!res) return; - - const { name, nodeData } = res; - - // Convert this data into a LG node definition and register it - const config = new GroupNodeConfig(name, nodeData); - await config.registerType(); - - const groupNode = LiteGraph.createNode(`workflow/${name}`); - // Reuse the existing nodes for this instance - groupNode.setInnerNodes(builder.nodes); - groupNode[GROUP].populateWidgets(); - app.graph.add(groupNode); - - // Remove all converted nodes and relink them - groupNode[GROUP].replaceNodes(builder.nodes); - return groupNode; - } -} - -function addConvertToGroupOptions() { - function addConvertOption(options, index) { - const selected = Object.values(app.canvas.selected_nodes ?? {}); - const disabled = selected.length < 2 || selected.find((n) => GroupNodeHandler.isGroupNode(n)); - options.splice(index + 1, null, { - content: `Convert to Group Node`, - disabled, - callback: async () => { - return await GroupNodeHandler.fromNodes(selected); - }, - }); - } - - function addManageOption(options, index) { - const groups = app.graph.extra?.groupNodes; - const disabled = !groups || !Object.keys(groups).length; - options.splice(index + 1, null, { - content: `Manage Group Nodes`, - disabled, - callback: () => { - new ManageGroupDialog(app).show(); - }, - }); - } - - // Add to canvas - const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions; - LGraphCanvas.prototype.getCanvasMenuOptions = function () { - const options = getCanvasMenuOptions.apply(this, arguments); - const index = options.findIndex((o) => o?.content === "Add Group") + 1 || options.length; - addConvertOption(options, index); - addManageOption(options, index + 1); - return options; - }; - - // Add to nodes - const getNodeMenuOptions = LGraphCanvas.prototype.getNodeMenuOptions; - LGraphCanvas.prototype.getNodeMenuOptions = function (node) { - const options = getNodeMenuOptions.apply(this, arguments); - if (!GroupNodeHandler.isGroupNode(node)) { - const index = options.findIndex((o) => o?.content === "Outputs") + 1 || options.length - 1; - addConvertOption(options, index); - } - return options; - }; -} - -const id = "Comfy.GroupNode"; -let globalDefs; -const ext = { - name: id, - setup() { - addConvertToGroupOptions(); - }, - async beforeConfigureGraph(graphData, missingNodeTypes) { - const nodes = graphData?.extra?.groupNodes; - if (nodes) { - await GroupNodeConfig.registerFromWorkflow(nodes, missingNodeTypes); - } - }, - addCustomNodeDefs(defs) { - // Store this so we can mutate it later with group nodes - globalDefs = defs; - }, - nodeCreated(node) { - if (GroupNodeHandler.isGroupNode(node)) { - node[GROUP] = new GroupNodeHandler(node); - } - }, - async refreshComboInNodes(defs) { - // Re-register group nodes so new ones are created with the correct options - Object.assign(globalDefs, defs); - const nodes = app.graph.extra?.groupNodes; - if (nodes) { - await GroupNodeConfig.registerFromWorkflow(nodes, {}); - } - } -}; - -app.registerExtension(ext); \ No newline at end of file diff --git a/web/extensions/core/groupNodeManage.css b/web/extensions/core/groupNodeManage.css deleted file mode 100644 index 5470ecb5e..000000000 --- a/web/extensions/core/groupNodeManage.css +++ /dev/null @@ -1,149 +0,0 @@ -.comfy-group-manage { - background: var(--bg-color); - color: var(--fg-color); - padding: 0; - font-family: Arial, Helvetica, sans-serif; - border-color: black; - margin: 20vh auto; - max-height: 60vh; -} -.comfy-group-manage-outer { - max-height: 60vh; - min-width: 500px; - display: flex; - flex-direction: column; -} -.comfy-group-manage-outer > header { - display: flex; - align-items: center; - gap: 10px; - justify-content: space-between; - background: var(--comfy-menu-bg); - padding: 15px 20px; -} -.comfy-group-manage-outer > header select { - background: var(--comfy-input-bg); - border: 1px solid var(--border-color); - color: var(--input-text); - padding: 5px 10px; - border-radius: 5px; -} -.comfy-group-manage h2 { - margin: 0; - font-weight: normal; -} -.comfy-group-manage main { - display: flex; - overflow: hidden; -} -.comfy-group-manage .drag-handle { - font-weight: bold; -} -.comfy-group-manage-list { - border-right: 1px solid var(--comfy-menu-bg); -} -.comfy-group-manage-list ul { - margin: 40px 0 0; - padding: 0; - list-style: none; -} -.comfy-group-manage-list-items { - max-height: calc(100% - 40px); - overflow-y: scroll; - overflow-x: hidden; -} -.comfy-group-manage-list li { - display: flex; - padding: 10px 20px 10px 10px; - cursor: pointer; - align-items: center; - gap: 5px; -} -.comfy-group-manage-list div { - display: flex; - flex-direction: column; -} -.comfy-group-manage-list li:not(.selected):hover div { - text-decoration: underline; -} -.comfy-group-manage-list li.selected { - background: var(--border-color); -} -.comfy-group-manage-list li span { - opacity: 0.7; - font-size: smaller; -} -.comfy-group-manage-node { - flex: auto; - background: var(--border-color); - display: flex; - flex-direction: column; -} -.comfy-group-manage-node > div { - overflow: auto; -} -.comfy-group-manage-node header { - display: flex; - background: var(--bg-color); - height: 40px; -} -.comfy-group-manage-node header a { - text-align: center; - flex: auto; - border-right: 1px solid var(--comfy-menu-bg); - border-bottom: 1px solid var(--comfy-menu-bg); - padding: 10px; - cursor: pointer; - font-size: 15px; -} -.comfy-group-manage-node header a:last-child { - border-right: none; -} -.comfy-group-manage-node header a:not(.active):hover { - text-decoration: underline; -} -.comfy-group-manage-node header a.active { - background: var(--border-color); - border-bottom: none; -} -.comfy-group-manage-node-page { - display: none; - overflow: auto; -} -.comfy-group-manage-node-page.active { - display: block; -} -.comfy-group-manage-node-page div { - padding: 10px; - display: flex; - align-items: center; - gap: 10px; -} -.comfy-group-manage-node-page input { - border: none; - color: var(--input-text); - background: var(--comfy-input-bg); - padding: 5px 10px; -} -.comfy-group-manage-node-page input[type="text"] { - flex: auto; -} -.comfy-group-manage-node-page label { - display: flex; - gap: 5px; - align-items: center; -} -.comfy-group-manage footer { - border-top: 1px solid var(--comfy-menu-bg); - padding: 10px; - display: flex; - gap: 10px; -} -.comfy-group-manage footer button { - font-size: 14px; - padding: 5px 10px; - border-radius: 0; -} -.comfy-group-manage footer button:first-child { - margin-right: auto; -} diff --git a/web/extensions/core/groupNodeManage.js b/web/extensions/core/groupNodeManage.js deleted file mode 100644 index 1ab338386..000000000 --- a/web/extensions/core/groupNodeManage.js +++ /dev/null @@ -1,422 +0,0 @@ -import { $el, ComfyDialog } from "../../scripts/ui.js"; -import { DraggableList } from "../../scripts/ui/draggableList.js"; -import { addStylesheet } from "../../scripts/utils.js"; -import { GroupNodeConfig, GroupNodeHandler } from "./groupNode.js"; - -addStylesheet(import.meta.url); - -const ORDER = Symbol(); - -function merge(target, source) { - if (typeof target === "object" && typeof source === "object") { - for (const key in source) { - const sv = source[key]; - if (typeof sv === "object") { - let tv = target[key]; - if (!tv) tv = target[key] = {}; - merge(tv, source[key]); - } else { - target[key] = sv; - } - } - } - - return target; -} - -export class ManageGroupDialog extends ComfyDialog { - /** @type { Record<"Inputs" | "Outputs" | "Widgets", {tab: HTMLAnchorElement, page: HTMLElement}> } */ - tabs = {}; - /** @type { number | null | undefined } */ - selectedNodeIndex; - /** @type { keyof ManageGroupDialog["tabs"] } */ - selectedTab = "Inputs"; - /** @type { string | undefined } */ - selectedGroup; - - /** @type { Record>> } */ - modifications = {}; - - get selectedNodeInnerIndex() { - return +this.nodeItems[this.selectedNodeIndex].dataset.nodeindex; - } - - constructor(app) { - super(); - this.app = app; - this.element = $el("dialog.comfy-group-manage", { - parent: document.body, - }); - } - - changeTab(tab) { - this.tabs[this.selectedTab].tab.classList.remove("active"); - this.tabs[this.selectedTab].page.classList.remove("active"); - this.tabs[tab].tab.classList.add("active"); - this.tabs[tab].page.classList.add("active"); - this.selectedTab = tab; - } - - changeNode(index, force) { - if (!force && this.selectedNodeIndex === index) return; - - if (this.selectedNodeIndex != null) { - this.nodeItems[this.selectedNodeIndex].classList.remove("selected"); - } - this.nodeItems[index].classList.add("selected"); - this.selectedNodeIndex = index; - - if (!this.buildInputsPage() && this.selectedTab === "Inputs") { - this.changeTab("Widgets"); - } - if (!this.buildWidgetsPage() && this.selectedTab === "Widgets") { - this.changeTab("Outputs"); - } - if (!this.buildOutputsPage() && this.selectedTab === "Outputs") { - this.changeTab("Inputs"); - } - - this.changeTab(this.selectedTab); - } - - getGroupData() { - this.groupNodeType = LiteGraph.registered_node_types["workflow/" + this.selectedGroup]; - this.groupNodeDef = this.groupNodeType.nodeData; - this.groupData = GroupNodeHandler.getGroupData(this.groupNodeType); - } - - changeGroup(group, reset = true) { - this.selectedGroup = group; - this.getGroupData(); - - const nodes = this.groupData.nodeData.nodes; - this.nodeItems = nodes.map((n, i) => - $el( - "li.draggable-item", - { - dataset: { - nodeindex: n.index + "", - }, - onclick: () => { - this.changeNode(i); - }, - }, - [ - $el("span.drag-handle"), - $el( - "div", - { - textContent: n.title ?? n.type, - }, - n.title - ? $el("span", { - textContent: n.type, - }) - : [] - ), - ] - ) - ); - - this.innerNodesList.replaceChildren(...this.nodeItems); - - if (reset) { - this.selectedNodeIndex = null; - this.changeNode(0); - } else { - const items = this.draggable.getAllItems(); - let index = items.findIndex(item => item.classList.contains("selected")); - if(index === -1) index = this.selectedNodeIndex; - this.changeNode(index, true); - } - - const ordered = [...nodes]; - this.draggable?.dispose(); - this.draggable = new DraggableList(this.innerNodesList, "li"); - this.draggable.addEventListener("dragend", ({ detail: { oldPosition, newPosition } }) => { - if (oldPosition === newPosition) return; - ordered.splice(newPosition, 0, ordered.splice(oldPosition, 1)[0]); - for (let i = 0; i < ordered.length; i++) { - this.storeModification({ nodeIndex: ordered[i].index, section: ORDER, prop: "order", value: i }); - } - }); - } - - storeModification({ nodeIndex, section, prop, value }) { - const groupMod = (this.modifications[this.selectedGroup] ??= {}); - const nodesMod = (groupMod.nodes ??= {}); - const nodeMod = (nodesMod[nodeIndex ?? this.selectedNodeInnerIndex] ??= {}); - const typeMod = (nodeMod[section] ??= {}); - if (typeof value === "object") { - const objMod = (typeMod[prop] ??= {}); - Object.assign(objMod, value); - } else { - typeMod[prop] = value; - } - } - - getEditElement(section, prop, value, placeholder, checked, checkable = true) { - if (value === placeholder) value = ""; - - const mods = this.modifications[this.selectedGroup]?.nodes?.[this.selectedNodeInnerIndex]?.[section]?.[prop]; - if (mods) { - if (mods.name != null) { - value = mods.name; - } - if (mods.visible != null) { - checked = mods.visible; - } - } - - return $el("div", [ - $el("input", { - value, - placeholder, - type: "text", - onchange: (e) => { - this.storeModification({ section, prop, value: { name: e.target.value } }); - }, - }), - $el("label", { textContent: "Visible" }, [ - $el("input", { - type: "checkbox", - checked, - disabled: !checkable, - onchange: (e) => { - this.storeModification({ section, prop, value: { visible: !!e.target.checked } }); - }, - }), - ]), - ]); - } - - buildWidgetsPage() { - const widgets = this.groupData.oldToNewWidgetMap[this.selectedNodeInnerIndex]; - const items = Object.keys(widgets ?? {}); - const type = app.graph.extra.groupNodes[this.selectedGroup]; - const config = type.config?.[this.selectedNodeInnerIndex]?.input; - this.widgetsPage.replaceChildren( - ...items.map((oldName) => { - return this.getEditElement("input", oldName, widgets[oldName], oldName, config?.[oldName]?.visible !== false); - }) - ); - return !!items.length; - } - - buildInputsPage() { - const inputs = this.groupData.nodeInputs[this.selectedNodeInnerIndex]; - const items = Object.keys(inputs ?? {}); - const type = app.graph.extra.groupNodes[this.selectedGroup]; - const config = type.config?.[this.selectedNodeInnerIndex]?.input; - this.inputsPage.replaceChildren( - ...items - .map((oldName) => { - let value = inputs[oldName]; - if (!value) { - return; - } - - return this.getEditElement("input", oldName, value, oldName, config?.[oldName]?.visible !== false); - }) - .filter(Boolean) - ); - return !!items.length; - } - - buildOutputsPage() { - const nodes = this.groupData.nodeData.nodes; - const innerNodeDef = this.groupData.getNodeDef(nodes[this.selectedNodeInnerIndex]); - const outputs = innerNodeDef?.output ?? []; - const groupOutputs = this.groupData.oldToNewOutputMap[this.selectedNodeInnerIndex]; - - const type = app.graph.extra.groupNodes[this.selectedGroup]; - const config = type.config?.[this.selectedNodeInnerIndex]?.output; - const node = this.groupData.nodeData.nodes[this.selectedNodeInnerIndex]; - const checkable = node.type !== "PrimitiveNode"; - this.outputsPage.replaceChildren( - ...outputs - .map((type, slot) => { - const groupOutputIndex = groupOutputs?.[slot]; - const oldName = innerNodeDef.output_name?.[slot] ?? type; - let value = config?.[slot]?.name; - const visible = config?.[slot]?.visible || groupOutputIndex != null; - if (!value || value === oldName) { - value = ""; - } - return this.getEditElement("output", slot, value, oldName, visible, checkable); - }) - .filter(Boolean) - ); - return !!outputs.length; - } - - show(type) { - const groupNodes = Object.keys(app.graph.extra?.groupNodes ?? {}).sort((a, b) => a.localeCompare(b)); - - this.innerNodesList = $el("ul.comfy-group-manage-list-items"); - this.widgetsPage = $el("section.comfy-group-manage-node-page"); - this.inputsPage = $el("section.comfy-group-manage-node-page"); - this.outputsPage = $el("section.comfy-group-manage-node-page"); - const pages = $el("div", [this.widgetsPage, this.inputsPage, this.outputsPage]); - - this.tabs = [ - ["Inputs", this.inputsPage], - ["Widgets", this.widgetsPage], - ["Outputs", this.outputsPage], - ].reduce((p, [name, page]) => { - p[name] = { - tab: $el("a", { - onclick: () => { - this.changeTab(name); - }, - textContent: name, - }), - page, - }; - return p; - }, {}); - - const outer = $el("div.comfy-group-manage-outer", [ - $el("header", [ - $el("h2", "Group Nodes"), - $el( - "select", - { - onchange: (e) => { - this.changeGroup(e.target.value); - }, - }, - groupNodes.map((g) => - $el("option", { - textContent: g, - selected: "workflow/" + g === type, - value: g, - }) - ) - ), - ]), - $el("main", [ - $el("section.comfy-group-manage-list", this.innerNodesList), - $el("section.comfy-group-manage-node", [ - $el( - "header", - Object.values(this.tabs).map((t) => t.tab) - ), - pages, - ]), - ]), - $el("footer", [ - $el( - "button.comfy-btn", - { - onclick: (e) => { - const node = app.graph._nodes.find((n) => n.type === "workflow/" + this.selectedGroup); - if (node) { - alert("This group node is in use in the current workflow, please first remove these."); - return; - } - if (confirm(`Are you sure you want to remove the node: "${this.selectedGroup}"`)) { - delete app.graph.extra.groupNodes[this.selectedGroup]; - LiteGraph.unregisterNodeType("workflow/" + this.selectedGroup); - } - this.show(); - }, - }, - "Delete Group Node" - ), - $el( - "button.comfy-btn", - { - onclick: async () => { - let nodesByType; - let recreateNodes = []; - const types = {}; - for (const g in this.modifications) { - const type = app.graph.extra.groupNodes[g]; - let config = (type.config ??= {}); - - let nodeMods = this.modifications[g]?.nodes; - if (nodeMods) { - const keys = Object.keys(nodeMods); - if (nodeMods[keys[0]][ORDER]) { - // If any node is reordered, they will all need sequencing - const orderedNodes = []; - const orderedMods = {}; - const orderedConfig = {}; - - for (const n of keys) { - const order = nodeMods[n][ORDER].order; - orderedNodes[order] = type.nodes[+n]; - orderedMods[order] = nodeMods[n]; - orderedNodes[order].index = order; - } - - // Rewrite links - for (const l of type.links) { - if (l[0] != null) l[0] = type.nodes[l[0]].index; - if (l[2] != null) l[2] = type.nodes[l[2]].index; - } - - // Rewrite externals - if (type.external) { - for (const ext of type.external) { - ext[0] = type.nodes[ext[0]]; - } - } - - // Rewrite modifications - for (const id of keys) { - if (config[id]) { - orderedConfig[type.nodes[id].index] = config[id]; - } - delete config[id]; - } - - type.nodes = orderedNodes; - nodeMods = orderedMods; - type.config = config = orderedConfig; - } - - merge(config, nodeMods); - } - - types[g] = type; - - if (!nodesByType) { - nodesByType = app.graph._nodes.reduce((p, n) => { - p[n.type] ??= []; - p[n.type].push(n); - return p; - }, {}); - } - - const nodes = nodesByType["workflow/" + g]; - if (nodes) recreateNodes.push(...nodes); - } - - await GroupNodeConfig.registerFromWorkflow(types, {}); - - for (const node of recreateNodes) { - node.recreate(); - } - - this.modifications = {}; - this.app.graph.setDirtyCanvas(true, true); - this.changeGroup(this.selectedGroup, false); - }, - }, - "Save" - ), - $el("button.comfy-btn", { onclick: () => this.element.close() }, "Close"), - ]), - ]); - - this.element.replaceChildren(outer); - this.changeGroup(type ? groupNodes.find((g) => "workflow/" + g === type) : groupNodes[0]); - this.element.showModal(); - - this.element.addEventListener("close", () => { - this.draggable?.dispose(); - }); - } -} \ No newline at end of file diff --git a/web/extensions/core/groupOptions.js b/web/extensions/core/groupOptions.js deleted file mode 100644 index 5dd21e730..000000000 --- a/web/extensions/core/groupOptions.js +++ /dev/null @@ -1,259 +0,0 @@ -import {app} from "../../scripts/app.js"; - -function setNodeMode(node, mode) { - node.mode = mode; - node.graph.change(); -} - -function addNodesToGroup(group, nodes=[]) { - var x1, y1, x2, y2; - var nx1, ny1, nx2, ny2; - var node; - - x1 = y1 = x2 = y2 = -1; - nx1 = ny1 = nx2 = ny2 = -1; - - for (var n of [group._nodes, nodes]) { - for (var i in n) { - node = n[i] - - nx1 = node.pos[0] - ny1 = node.pos[1] - nx2 = node.pos[0] + node.size[0] - ny2 = node.pos[1] + node.size[1] - - if (node.type != "Reroute") { - ny1 -= LiteGraph.NODE_TITLE_HEIGHT; - } - - if (node.flags?.collapsed) { - ny2 = ny1 + LiteGraph.NODE_TITLE_HEIGHT; - - if (node?._collapsed_width) { - nx2 = nx1 + Math.round(node._collapsed_width); - } - } - - if (x1 == -1 || nx1 < x1) { - x1 = nx1; - } - - if (y1 == -1 || ny1 < y1) { - y1 = ny1; - } - - if (x2 == -1 || nx2 > x2) { - x2 = nx2; - } - - if (y2 == -1 || ny2 > y2) { - y2 = ny2; - } - } - } - - var padding = 10; - - y1 = y1 - Math.round(group.font_size * 1.4); - - group.pos = [x1 - padding, y1 - padding]; - group.size = [x2 - x1 + padding * 2, y2 - y1 + padding * 2]; -} - -app.registerExtension({ - name: "Comfy.GroupOptions", - setup() { - const orig = LGraphCanvas.prototype.getCanvasMenuOptions; - // graph_mouse - LGraphCanvas.prototype.getCanvasMenuOptions = function () { - const options = orig.apply(this, arguments); - const group = this.graph.getGroupOnPos(this.graph_mouse[0], this.graph_mouse[1]); - if (!group) { - options.push({ - content: "Add Group For Selected Nodes", - disabled: !Object.keys(app.canvas.selected_nodes || {}).length, - callback: () => { - var group = new LiteGraph.LGraphGroup(); - addNodesToGroup(group, this.selected_nodes) - app.canvas.graph.add(group); - this.graph.change(); - } - }); - - return options; - } - - // Group nodes aren't recomputed until the group is moved, this ensures the nodes are up-to-date - group.recomputeInsideNodes(); - const nodesInGroup = group._nodes; - - options.push({ - content: "Add Selected Nodes To Group", - disabled: !Object.keys(app.canvas.selected_nodes || {}).length, - callback: () => { - addNodesToGroup(group, this.selected_nodes) - this.graph.change(); - } - }); - - // No nodes in group, return default options - if (nodesInGroup.length === 0) { - return options; - } else { - // Add a separator between the default options and the group options - options.push(null); - } - - // Check if all nodes are the same mode - let allNodesAreSameMode = true; - for (let i = 1; i < nodesInGroup.length; i++) { - if (nodesInGroup[i].mode !== nodesInGroup[0].mode) { - allNodesAreSameMode = false; - break; - } - } - - options.push({ - content: "Fit Group To Nodes", - callback: () => { - addNodesToGroup(group) - this.graph.change(); - } - }); - - options.push({ - content: "Select Nodes", - callback: () => { - this.selectNodes(nodesInGroup); - this.graph.change(); - this.canvas.focus(); - } - }); - - // Modes - // 0: Always - // 1: On Event - // 2: Never - // 3: On Trigger - // 4: Bypass - // If all nodes are the same mode, add a menu option to change the mode - if (allNodesAreSameMode) { - const mode = nodesInGroup[0].mode; - switch (mode) { - case 0: - // All nodes are always, option to disable, and bypass - options.push({ - content: "Set Group Nodes to Never", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 2); - } - } - }); - options.push({ - content: "Bypass Group Nodes", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 4); - } - } - }); - break; - case 2: - // All nodes are never, option to enable, and bypass - options.push({ - content: "Set Group Nodes to Always", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 0); - } - } - }); - options.push({ - content: "Bypass Group Nodes", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 4); - } - } - }); - break; - case 4: - // All nodes are bypass, option to enable, and disable - options.push({ - content: "Set Group Nodes to Always", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 0); - } - } - }); - options.push({ - content: "Set Group Nodes to Never", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 2); - } - } - }); - break; - default: - // All nodes are On Trigger or On Event(Or other?), option to disable, set to always, or bypass - options.push({ - content: "Set Group Nodes to Always", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 0); - } - } - }); - options.push({ - content: "Set Group Nodes to Never", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 2); - } - } - }); - options.push({ - content: "Bypass Group Nodes", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 4); - } - } - }); - break; - } - } else { - // Nodes are not all the same mode, add a menu option to change the mode to always, never, or bypass - options.push({ - content: "Set Group Nodes to Always", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 0); - } - } - }); - options.push({ - content: "Set Group Nodes to Never", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 2); - } - } - }); - options.push({ - content: "Bypass Group Nodes", - callback: () => { - for (const node of nodesInGroup) { - setNodeMode(node, 4); - } - } - }); - } - - return options - } - } -}); diff --git a/web/extensions/core/invertMenuScrolling.js b/web/extensions/core/invertMenuScrolling.js deleted file mode 100644 index 98a1786ab..000000000 --- a/web/extensions/core/invertMenuScrolling.js +++ /dev/null @@ -1,36 +0,0 @@ -import { app } from "../../scripts/app.js"; - -// Inverts the scrolling of context menus - -const id = "Comfy.InvertMenuScrolling"; -app.registerExtension({ - name: id, - init() { - const ctxMenu = LiteGraph.ContextMenu; - const replace = () => { - LiteGraph.ContextMenu = function (values, options) { - options = options || {}; - if (options.scroll_speed) { - options.scroll_speed *= -1; - } else { - options.scroll_speed = -0.1; - } - return ctxMenu.call(this, values, options); - }; - LiteGraph.ContextMenu.prototype = ctxMenu.prototype; - }; - app.ui.settings.addSetting({ - id, - name: "Invert Menu Scrolling", - type: "boolean", - defaultValue: false, - onChange(value) { - if (value) { - replace(); - } else { - LiteGraph.ContextMenu = ctxMenu; - } - }, - }); - }, -}); diff --git a/web/extensions/core/keybinds.js b/web/extensions/core/keybinds.js deleted file mode 100644 index ac367c116..000000000 --- a/web/extensions/core/keybinds.js +++ /dev/null @@ -1,69 +0,0 @@ -import {app} from "../../scripts/app.js"; - -app.registerExtension({ - name: "Comfy.Keybinds", - init() { - const keybindListener = function (event) { - const modifierPressed = event.ctrlKey || event.metaKey; - - // Queue prompt using ctrl or command + enter - if (modifierPressed && event.key === "Enter") { - app.queuePrompt(event.shiftKey ? -1 : 0).then(); - return; - } - - const target = event.composedPath()[0]; - if (["INPUT", "TEXTAREA"].includes(target.tagName)) { - return; - } - - const modifierKeyIdMap = { - s: "#comfy-save-button", - o: "#comfy-file-input", - Backspace: "#comfy-clear-button", - d: "#comfy-load-default-button", - }; - - const modifierKeybindId = modifierKeyIdMap[event.key]; - if (modifierPressed && modifierKeybindId) { - event.preventDefault(); - - const elem = document.querySelector(modifierKeybindId); - elem.click(); - return; - } - - // Finished Handling all modifier keybinds, now handle the rest - if (event.ctrlKey || event.altKey || event.metaKey) { - return; - } - - // Close out of modals using escape - if (event.key === "Escape") { - const modals = document.querySelectorAll(".comfy-modal"); - const modal = Array.from(modals).find(modal => window.getComputedStyle(modal).getPropertyValue("display") !== "none"); - if (modal) { - modal.style.display = "none"; - } - - [...document.querySelectorAll("dialog")].forEach(d => { - d.close(); - }); - } - - const keyIdMap = { - q: "#comfy-view-queue-button", - h: "#comfy-view-history-button", - r: "#comfy-refresh-button", - }; - - const buttonId = keyIdMap[event.key]; - if (buttonId) { - const button = document.querySelector(buttonId); - button.click(); - } - } - - window.addEventListener("keydown", keybindListener, true); - } -}); diff --git a/web/extensions/core/linkRenderMode.js b/web/extensions/core/linkRenderMode.js deleted file mode 100644 index fb4df4234..000000000 --- a/web/extensions/core/linkRenderMode.js +++ /dev/null @@ -1,25 +0,0 @@ -import { app } from "../../scripts/app.js"; - -const id = "Comfy.LinkRenderMode"; -const ext = { - name: id, - async setup(app) { - app.ui.settings.addSetting({ - id, - name: "Link Render Mode", - defaultValue: 2, - type: "combo", - options: [...LiteGraph.LINK_RENDER_MODES, "Hidden"].map((m, i) => ({ - value: i, - text: m, - selected: i == app.canvas.links_render_mode, - })), - onChange(value) { - app.canvas.links_render_mode = +value; - app.graph.setDirtyCanvas(true); - }, - }); - }, -}; - -app.registerExtension(ext); diff --git a/web/extensions/core/maskeditor.js b/web/extensions/core/maskeditor.js deleted file mode 100644 index 36f7496e7..000000000 --- a/web/extensions/core/maskeditor.js +++ /dev/null @@ -1,967 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { ComfyDialog, $el } from "../../scripts/ui.js"; -import { ComfyApp } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js" -import { ClipspaceDialog } from "./clipspace.js"; - -// Helper function to convert a data URL to a Blob object -function dataURLToBlob(dataURL) { - const parts = dataURL.split(';base64,'); - const contentType = parts[0].split(':')[1]; - const byteString = atob(parts[1]); - const arrayBuffer = new ArrayBuffer(byteString.length); - const uint8Array = new Uint8Array(arrayBuffer); - for (let i = 0; i < byteString.length; i++) { - uint8Array[i] = byteString.charCodeAt(i); - } - return new Blob([arrayBuffer], { type: contentType }); -} - -function loadedImageToBlob(image) { - const canvas = document.createElement('canvas'); - - canvas.width = image.width; - canvas.height = image.height; - - const ctx = canvas.getContext('2d'); - - ctx.drawImage(image, 0, 0); - - const dataURL = canvas.toDataURL('image/png', 1); - const blob = dataURLToBlob(dataURL); - - return blob; -} - -function loadImage(imagePath) { - return new Promise((resolve, reject) => { - const image = new Image(); - - image.onload = function() { - resolve(image); - }; - - image.src = imagePath; - }); -} - -async function uploadMask(filepath, formData) { - await api.fetchApi('/upload/mask', { - method: 'POST', - body: formData - }).then(response => {}).catch(error => { - console.error('Error:', error); - }); - - ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']] = new Image(); - ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src = api.apiURL("/view?" + new URLSearchParams(filepath).toString() + app.getPreviewFormatParam() + app.getRandParam()); - - if(ComfyApp.clipspace.images) - ComfyApp.clipspace.images[ComfyApp.clipspace['selectedIndex']] = filepath; - - ClipspaceDialog.invalidatePreview(); -} - -function prepare_mask(image, maskCanvas, maskCtx, maskColor) { - // paste mask data into alpha channel - maskCtx.drawImage(image, 0, 0, maskCanvas.width, maskCanvas.height); - const maskData = maskCtx.getImageData(0, 0, maskCanvas.width, maskCanvas.height); - - // invert mask - for (let i = 0; i < maskData.data.length; i += 4) { - if(maskData.data[i+3] == 255) - maskData.data[i+3] = 0; - else - maskData.data[i+3] = 255; - - maskData.data[i] = maskColor.r; - maskData.data[i+1] = maskColor.g; - maskData.data[i+2] = maskColor.b; - } - - maskCtx.globalCompositeOperation = 'source-over'; - maskCtx.putImageData(maskData, 0, 0); -} - -class MaskEditorDialog extends ComfyDialog { - static instance = null; - - static getInstance() { - if(!MaskEditorDialog.instance) { - MaskEditorDialog.instance = new MaskEditorDialog(app); - } - - return MaskEditorDialog.instance; - } - - is_layout_created = false; - - constructor() { - super(); - this.element = $el("div.comfy-modal", { parent: document.body }, - [ $el("div.comfy-modal-content", - [...this.createButtons()]), - ]); - } - - createButtons() { - return []; - } - - createButton(name, callback) { - var button = document.createElement("button"); - button.style.pointerEvents = "auto"; - button.innerText = name; - button.addEventListener("click", callback); - return button; - } - - createLeftButton(name, callback) { - var button = this.createButton(name, callback); - button.style.cssFloat = "left"; - button.style.marginRight = "4px"; - return button; - } - - createRightButton(name, callback) { - var button = this.createButton(name, callback); - button.style.cssFloat = "right"; - button.style.marginLeft = "4px"; - return button; - } - - createLeftSlider(self, name, callback) { - const divElement = document.createElement('div'); - divElement.id = "maskeditor-slider"; - divElement.style.cssFloat = "left"; - divElement.style.fontFamily = "sans-serif"; - divElement.style.marginRight = "4px"; - divElement.style.color = "var(--input-text)"; - divElement.style.backgroundColor = "var(--comfy-input-bg)"; - divElement.style.borderRadius = "8px"; - divElement.style.borderColor = "var(--border-color)"; - divElement.style.borderStyle = "solid"; - divElement.style.fontSize = "15px"; - divElement.style.height = "21px"; - divElement.style.padding = "1px 6px"; - divElement.style.display = "flex"; - divElement.style.position = "relative"; - divElement.style.top = "2px"; - divElement.style.pointerEvents = "auto"; - self.brush_slider_input = document.createElement('input'); - self.brush_slider_input.setAttribute('type', 'range'); - self.brush_slider_input.setAttribute('min', '1'); - self.brush_slider_input.setAttribute('max', '100'); - self.brush_slider_input.setAttribute('value', '10'); - const labelElement = document.createElement("label"); - labelElement.textContent = name; - - divElement.appendChild(labelElement); - divElement.appendChild(self.brush_slider_input); - - self.brush_slider_input.addEventListener("change", callback); - - return divElement; - } - - createOpacitySlider(self, name, callback) { - const divElement = document.createElement('div'); - divElement.id = "maskeditor-opacity-slider"; - divElement.style.cssFloat = "left"; - divElement.style.fontFamily = "sans-serif"; - divElement.style.marginRight = "4px"; - divElement.style.color = "var(--input-text)"; - divElement.style.backgroundColor = "var(--comfy-input-bg)"; - divElement.style.borderRadius = "8px"; - divElement.style.borderColor = "var(--border-color)"; - divElement.style.borderStyle = "solid"; - divElement.style.fontSize = "15px"; - divElement.style.height = "21px"; - divElement.style.padding = "1px 6px"; - divElement.style.display = "flex"; - divElement.style.position = "relative"; - divElement.style.top = "2px"; - divElement.style.pointerEvents = "auto"; - self.opacity_slider_input = document.createElement('input'); - self.opacity_slider_input.setAttribute('type', 'range'); - self.opacity_slider_input.setAttribute('min', '0.1'); - self.opacity_slider_input.setAttribute('max', '1.0'); - self.opacity_slider_input.setAttribute('step', '0.01') - self.opacity_slider_input.setAttribute('value', '0.7'); - const labelElement = document.createElement("label"); - labelElement.textContent = name; - - divElement.appendChild(labelElement); - divElement.appendChild(self.opacity_slider_input); - - self.opacity_slider_input.addEventListener("input", callback); - - return divElement; - } - - setlayout(imgCanvas, maskCanvas) { - const self = this; - - // If it is specified as relative, using it only as a hidden placeholder for padding is recommended - // to prevent anomalies where it exceeds a certain size and goes outside of the window. - var bottom_panel = document.createElement("div"); - bottom_panel.style.position = "absolute"; - bottom_panel.style.bottom = "0px"; - bottom_panel.style.left = "20px"; - bottom_panel.style.right = "20px"; - bottom_panel.style.height = "50px"; - bottom_panel.style.pointerEvents = "none"; - - var brush = document.createElement("div"); - brush.id = "brush"; - brush.style.backgroundColor = "transparent"; - brush.style.outline = "1px dashed black"; - brush.style.boxShadow = "0 0 0 1px white"; - brush.style.borderRadius = "50%"; - brush.style.MozBorderRadius = "50%"; - brush.style.WebkitBorderRadius = "50%"; - brush.style.position = "absolute"; - brush.style.zIndex = 8889; - brush.style.pointerEvents = "none"; - this.brush = brush; - this.element.appendChild(imgCanvas); - this.element.appendChild(maskCanvas); - this.element.appendChild(bottom_panel); - document.body.appendChild(brush); - - var clearButton = this.createLeftButton("Clear", () => { - self.maskCtx.clearRect(0, 0, self.maskCanvas.width, self.maskCanvas.height); - }); - - this.brush_size_slider = this.createLeftSlider(self, "Thickness", (event) => { - self.brush_size = event.target.value; - self.updateBrushPreview(self, null, null); - }); - - this.brush_opacity_slider = this.createOpacitySlider(self, "Opacity", (event) => { - self.brush_opacity = event.target.value; - if (self.brush_color_mode !== "negative") { - self.maskCanvas.style.opacity = self.brush_opacity; - } - }); - - this.colorButton = this.createLeftButton(this.getColorButtonText(), () => { - if (self.brush_color_mode === "black") { - self.brush_color_mode = "white"; - } - else if (self.brush_color_mode === "white") { - self.brush_color_mode = "negative"; - } - else { - self.brush_color_mode = "black"; - } - - self.updateWhenBrushColorModeChanged(); - }); - - var cancelButton = this.createRightButton("Cancel", () => { - document.removeEventListener("mouseup", MaskEditorDialog.handleMouseUp); - document.removeEventListener("keydown", MaskEditorDialog.handleKeyDown); - self.close(); - }); - - this.saveButton = this.createRightButton("Save", () => { - document.removeEventListener("mouseup", MaskEditorDialog.handleMouseUp); - document.removeEventListener("keydown", MaskEditorDialog.handleKeyDown); - self.save(); - }); - - this.element.appendChild(imgCanvas); - this.element.appendChild(maskCanvas); - this.element.appendChild(bottom_panel); - - bottom_panel.appendChild(clearButton); - bottom_panel.appendChild(this.saveButton); - bottom_panel.appendChild(cancelButton); - bottom_panel.appendChild(this.brush_size_slider); - bottom_panel.appendChild(this.brush_opacity_slider); - bottom_panel.appendChild(this.colorButton); - - imgCanvas.style.position = "absolute"; - maskCanvas.style.position = "absolute"; - - imgCanvas.style.top = "200"; - imgCanvas.style.left = "0"; - - maskCanvas.style.top = imgCanvas.style.top; - maskCanvas.style.left = imgCanvas.style.left; - - const maskCanvasStyle = this.getMaskCanvasStyle(); - maskCanvas.style.mixBlendMode = maskCanvasStyle.mixBlendMode; - maskCanvas.style.opacity = maskCanvasStyle.opacity; - } - - async show() { - this.zoom_ratio = 1.0; - this.pan_x = 0; - this.pan_y = 0; - - if(!this.is_layout_created) { - // layout - const imgCanvas = document.createElement('canvas'); - const maskCanvas = document.createElement('canvas'); - - imgCanvas.id = "imageCanvas"; - maskCanvas.id = "maskCanvas"; - - this.setlayout(imgCanvas, maskCanvas); - - // prepare content - this.imgCanvas = imgCanvas; - this.maskCanvas = maskCanvas; - this.maskCtx = maskCanvas.getContext('2d', {willReadFrequently: true }); - - this.setEventHandler(maskCanvas); - - this.is_layout_created = true; - - // replacement of onClose hook since close is not real close - const self = this; - const observer = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - if (mutation.type === 'attributes' && mutation.attributeName === 'style') { - if(self.last_display_style && self.last_display_style != 'none' && self.element.style.display == 'none') { - document.removeEventListener("mouseup", MaskEditorDialog.handleMouseUp); - self.brush.style.display = "none"; - ComfyApp.onClipspaceEditorClosed(); - } - - self.last_display_style = self.element.style.display; - } - }); - }); - - const config = { attributes: true }; - observer.observe(this.element, config); - } - - // The keydown event needs to be reconfigured when closing the dialog as it gets removed. - document.addEventListener('keydown', MaskEditorDialog.handleKeyDown); - - if(ComfyApp.clipspace_return_node) { - this.saveButton.innerText = "Save to node"; - } - else { - this.saveButton.innerText = "Save"; - } - this.saveButton.disabled = false; - - this.element.style.display = "block"; - this.element.style.width = "85%"; - this.element.style.margin = "0 7.5%"; - this.element.style.height = "100vh"; - this.element.style.top = "50%"; - this.element.style.left = "42%"; - this.element.style.zIndex = 8888; // NOTE: alert dialog must be high priority. - - await this.setImages(this.imgCanvas); - - this.is_visible = true; - } - - isOpened() { - return this.element.style.display == "block"; - } - - invalidateCanvas(orig_image, mask_image) { - this.imgCanvas.width = orig_image.width; - this.imgCanvas.height = orig_image.height; - - this.maskCanvas.width = orig_image.width; - this.maskCanvas.height = orig_image.height; - - let imgCtx = this.imgCanvas.getContext('2d', {willReadFrequently: true }); - let maskCtx = this.maskCanvas.getContext('2d', {willReadFrequently: true }); - - imgCtx.drawImage(orig_image, 0, 0, orig_image.width, orig_image.height); - prepare_mask(mask_image, this.maskCanvas, maskCtx, this.getMaskColor()); - } - - async setImages(imgCanvas) { - let self = this; - - const imgCtx = imgCanvas.getContext('2d', {willReadFrequently: true }); - const maskCtx = this.maskCtx; - const maskCanvas = this.maskCanvas; - - imgCtx.clearRect(0,0,this.imgCanvas.width,this.imgCanvas.height); - maskCtx.clearRect(0,0,this.maskCanvas.width,this.maskCanvas.height); - - // image load - const filepath = ComfyApp.clipspace.images; - - const alpha_url = new URL(ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src) - alpha_url.searchParams.delete('channel'); - alpha_url.searchParams.delete('preview'); - alpha_url.searchParams.set('channel', 'a'); - let mask_image = await loadImage(alpha_url); - - // original image load - const rgb_url = new URL(ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src); - rgb_url.searchParams.delete('channel'); - rgb_url.searchParams.set('channel', 'rgb'); - this.image = new Image(); - this.image.onload = function() { - maskCanvas.width = self.image.width; - maskCanvas.height = self.image.height; - - self.invalidateCanvas(self.image, mask_image); - self.initializeCanvasPanZoom(); - }; - this.image.src = rgb_url; - } - - initializeCanvasPanZoom() { - // set initialize - let drawWidth = this.image.width; - let drawHeight = this.image.height; - - let width = this.element.clientWidth; - let height = this.element.clientHeight; - - if (this.image.width > width) { - drawWidth = width; - drawHeight = (drawWidth / this.image.width) * this.image.height; - } - - if (drawHeight > height) { - drawHeight = height; - drawWidth = (drawHeight / this.image.height) * this.image.width; - } - - this.zoom_ratio = drawWidth/this.image.width; - - const canvasX = (width - drawWidth) / 2; - const canvasY = (height - drawHeight) / 2; - this.pan_x = canvasX; - this.pan_y = canvasY; - - this.invalidatePanZoom(); - } - - - invalidatePanZoom() { - let raw_width = this.image.width * this.zoom_ratio; - let raw_height = this.image.height * this.zoom_ratio; - - if(this.pan_x + raw_width < 10) { - this.pan_x = 10 - raw_width; - } - - if(this.pan_y + raw_height < 10) { - this.pan_y = 10 - raw_height; - } - - let width = `${raw_width}px`; - let height = `${raw_height}px`; - - let left = `${this.pan_x}px`; - let top = `${this.pan_y}px`; - - this.maskCanvas.style.width = width; - this.maskCanvas.style.height = height; - this.maskCanvas.style.left = left; - this.maskCanvas.style.top = top; - - this.imgCanvas.style.width = width; - this.imgCanvas.style.height = height; - this.imgCanvas.style.left = left; - this.imgCanvas.style.top = top; - } - - - setEventHandler(maskCanvas) { - const self = this; - - if(!this.handler_registered) { - maskCanvas.addEventListener("contextmenu", (event) => { - event.preventDefault(); - }); - - this.element.addEventListener('wheel', (event) => this.handleWheelEvent(self,event)); - this.element.addEventListener('pointermove', (event) => this.pointMoveEvent(self,event)); - this.element.addEventListener('touchmove', (event) => this.pointMoveEvent(self,event)); - - this.element.addEventListener('dragstart', (event) => { - if(event.ctrlKey) { - event.preventDefault(); - } - }); - - maskCanvas.addEventListener('pointerdown', (event) => this.handlePointerDown(self,event)); - maskCanvas.addEventListener('pointermove', (event) => this.draw_move(self,event)); - maskCanvas.addEventListener('touchmove', (event) => this.draw_move(self,event)); - maskCanvas.addEventListener('pointerover', (event) => { this.brush.style.display = "block"; }); - maskCanvas.addEventListener('pointerleave', (event) => { this.brush.style.display = "none"; }); - - document.addEventListener('pointerup', MaskEditorDialog.handlePointerUp); - - this.handler_registered = true; - } - } - - getMaskCanvasStyle() { - if (this.brush_color_mode === "negative") { - return { - mixBlendMode: "difference", - opacity: "1", - }; - } - else { - return { - mixBlendMode: "initial", - opacity: this.brush_opacity, - }; - } - } - - getMaskColor() { - if (this.brush_color_mode === "black") { - return { r: 0, g: 0, b: 0 }; - } - if (this.brush_color_mode === "white") { - return { r: 255, g: 255, b: 255 }; - } - if (this.brush_color_mode === "negative") { - // negative effect only works with white color - return { r: 255, g: 255, b: 255 }; - } - - return { r: 0, g: 0, b: 0 }; - } - - getMaskFillStyle() { - const maskColor = this.getMaskColor(); - - return "rgb(" + maskColor.r + "," + maskColor.g + "," + maskColor.b + ")"; - } - - getColorButtonText() { - let colorCaption = "unknown"; - - if (this.brush_color_mode === "black") { - colorCaption = "black"; - } - else if (this.brush_color_mode === "white") { - colorCaption = "white"; - } - else if (this.brush_color_mode === "negative") { - colorCaption = "negative"; - } - - return "Color: " + colorCaption; - } - - updateWhenBrushColorModeChanged() { - this.colorButton.innerText = this.getColorButtonText(); - - // update mask canvas css styles - - const maskCanvasStyle = this.getMaskCanvasStyle(); - this.maskCanvas.style.mixBlendMode = maskCanvasStyle.mixBlendMode; - this.maskCanvas.style.opacity = maskCanvasStyle.opacity; - - // update mask canvas rgb colors - - const maskColor = this.getMaskColor(); - - const maskData = this.maskCtx.getImageData(0, 0, this.maskCanvas.width, this.maskCanvas.height); - - for (let i = 0; i < maskData.data.length; i += 4) { - maskData.data[i] = maskColor.r; - maskData.data[i+1] = maskColor.g; - maskData.data[i+2] = maskColor.b; - } - - this.maskCtx.putImageData(maskData, 0, 0); - } - - brush_opacity = 0.7; - brush_size = 10; - brush_color_mode = "black"; - drawing_mode = false; - lastx = -1; - lasty = -1; - lasttime = 0; - - static handleKeyDown(event) { - const self = MaskEditorDialog.instance; - if (event.key === ']') { - self.brush_size = Math.min(self.brush_size+2, 100); - self.brush_slider_input.value = self.brush_size; - } else if (event.key === '[') { - self.brush_size = Math.max(self.brush_size-2, 1); - self.brush_slider_input.value = self.brush_size; - } else if(event.key === 'Enter') { - self.save(); - } - - self.updateBrushPreview(self); - } - - static handlePointerUp(event) { - event.preventDefault(); - - this.mousedown_x = null; - this.mousedown_y = null; - - MaskEditorDialog.instance.drawing_mode = false; - } - - updateBrushPreview(self) { - const brush = self.brush; - - var centerX = self.cursorX; - var centerY = self.cursorY; - - brush.style.width = self.brush_size * 2 * this.zoom_ratio + "px"; - brush.style.height = self.brush_size * 2 * this.zoom_ratio + "px"; - brush.style.left = (centerX - self.brush_size * this.zoom_ratio) + "px"; - brush.style.top = (centerY - self.brush_size * this.zoom_ratio) + "px"; - } - - handleWheelEvent(self, event) { - event.preventDefault(); - - if(event.ctrlKey) { - // zoom canvas - if(event.deltaY < 0) { - this.zoom_ratio = Math.min(10.0, this.zoom_ratio+0.2); - } - else { - this.zoom_ratio = Math.max(0.2, this.zoom_ratio-0.2); - } - - this.invalidatePanZoom(); - } - else { - // adjust brush size - if(event.deltaY < 0) - this.brush_size = Math.min(this.brush_size+2, 100); - else - this.brush_size = Math.max(this.brush_size-2, 1); - - this.brush_slider_input.value = this.brush_size; - - this.updateBrushPreview(this); - } - } - - pointMoveEvent(self, event) { - this.cursorX = event.pageX; - this.cursorY = event.pageY; - - self.updateBrushPreview(self); - - if(event.ctrlKey) { - event.preventDefault(); - self.pan_move(self, event); - } - - let left_button_down = window.TouchEvent && event instanceof TouchEvent || event.buttons == 1; - - if(event.shiftKey && left_button_down) { - self.drawing_mode = false; - - const y = event.clientY; - let delta = (self.zoom_lasty - y)*0.005; - self.zoom_ratio = Math.max(Math.min(10.0, self.last_zoom_ratio - delta), 0.2); - - this.invalidatePanZoom(); - return; - } - } - - pan_move(self, event) { - if(event.buttons == 1) { - if(this.mousedown_x) { - let deltaX = this.mousedown_x - event.clientX; - let deltaY = this.mousedown_y - event.clientY; - - self.pan_x = this.mousedown_pan_x - deltaX; - self.pan_y = this.mousedown_pan_y - deltaY; - - self.invalidatePanZoom(); - } - } - } - - draw_move(self, event) { - if(event.ctrlKey || event.shiftKey) { - return; - } - - event.preventDefault(); - - this.cursorX = event.pageX; - this.cursorY = event.pageY; - - self.updateBrushPreview(self); - - let left_button_down = window.TouchEvent && event instanceof TouchEvent || event.buttons == 1; - let right_button_down = [2, 5, 32].includes(event.buttons); - - if (!event.altKey && left_button_down) { - var diff = performance.now() - self.lasttime; - - const maskRect = self.maskCanvas.getBoundingClientRect(); - - var x = event.offsetX; - var y = event.offsetY - - if(event.offsetX == null) { - x = event.targetTouches[0].clientX - maskRect.left; - } - - if(event.offsetY == null) { - y = event.targetTouches[0].clientY - maskRect.top; - } - - x /= self.zoom_ratio; - y /= self.zoom_ratio; - - var brush_size = this.brush_size; - if(event instanceof PointerEvent && event.pointerType == 'pen') { - brush_size *= event.pressure; - this.last_pressure = event.pressure; - } - else if(window.TouchEvent && event instanceof TouchEvent && diff < 20){ - // The firing interval of PointerEvents in Pen is unreliable, so it is supplemented by TouchEvents. - brush_size *= this.last_pressure; - } - else { - brush_size = this.brush_size; - } - - if(diff > 20 && !this.drawing_mode) - requestAnimationFrame(() => { - self.maskCtx.beginPath(); - self.maskCtx.fillStyle = this.getMaskFillStyle(); - self.maskCtx.globalCompositeOperation = "source-over"; - self.maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false); - self.maskCtx.fill(); - self.lastx = x; - self.lasty = y; - }); - else - requestAnimationFrame(() => { - self.maskCtx.beginPath(); - self.maskCtx.fillStyle = this.getMaskFillStyle(); - self.maskCtx.globalCompositeOperation = "source-over"; - - var dx = x - self.lastx; - var dy = y - self.lasty; - - var distance = Math.sqrt(dx * dx + dy * dy); - var directionX = dx / distance; - var directionY = dy / distance; - - for (var i = 0; i < distance; i+=5) { - var px = self.lastx + (directionX * i); - var py = self.lasty + (directionY * i); - self.maskCtx.arc(px, py, brush_size, 0, Math.PI * 2, false); - self.maskCtx.fill(); - } - self.lastx = x; - self.lasty = y; - }); - - self.lasttime = performance.now(); - } - else if((event.altKey && left_button_down) || right_button_down) { - const maskRect = self.maskCanvas.getBoundingClientRect(); - const x = (event.offsetX || event.targetTouches[0].clientX - maskRect.left) / self.zoom_ratio; - const y = (event.offsetY || event.targetTouches[0].clientY - maskRect.top) / self.zoom_ratio; - - var brush_size = this.brush_size; - if(event instanceof PointerEvent && event.pointerType == 'pen') { - brush_size *= event.pressure; - this.last_pressure = event.pressure; - } - else if(window.TouchEvent && event instanceof TouchEvent && diff < 20){ - brush_size *= this.last_pressure; - } - else { - brush_size = this.brush_size; - } - - if(diff > 20 && !drawing_mode) // cannot tracking drawing_mode for touch event - requestAnimationFrame(() => { - self.maskCtx.beginPath(); - self.maskCtx.globalCompositeOperation = "destination-out"; - self.maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false); - self.maskCtx.fill(); - self.lastx = x; - self.lasty = y; - }); - else - requestAnimationFrame(() => { - self.maskCtx.beginPath(); - self.maskCtx.globalCompositeOperation = "destination-out"; - - var dx = x - self.lastx; - var dy = y - self.lasty; - - var distance = Math.sqrt(dx * dx + dy * dy); - var directionX = dx / distance; - var directionY = dy / distance; - - for (var i = 0; i < distance; i+=5) { - var px = self.lastx + (directionX * i); - var py = self.lasty + (directionY * i); - self.maskCtx.arc(px, py, brush_size, 0, Math.PI * 2, false); - self.maskCtx.fill(); - } - self.lastx = x; - self.lasty = y; - }); - - self.lasttime = performance.now(); - } - } - - handlePointerDown(self, event) { - if(event.ctrlKey) { - if (event.buttons == 1) { - this.mousedown_x = event.clientX; - this.mousedown_y = event.clientY; - - this.mousedown_pan_x = this.pan_x; - this.mousedown_pan_y = this.pan_y; - } - return; - } - - var brush_size = this.brush_size; - if(event instanceof PointerEvent && event.pointerType == 'pen') { - brush_size *= event.pressure; - this.last_pressure = event.pressure; - } - - if ([0, 2, 5].includes(event.button)) { - self.drawing_mode = true; - - event.preventDefault(); - - if(event.shiftKey) { - self.zoom_lasty = event.clientY; - self.last_zoom_ratio = self.zoom_ratio; - return; - } - - const maskRect = self.maskCanvas.getBoundingClientRect(); - const x = (event.offsetX || event.targetTouches[0].clientX - maskRect.left) / self.zoom_ratio; - const y = (event.offsetY || event.targetTouches[0].clientY - maskRect.top) / self.zoom_ratio; - - self.maskCtx.beginPath(); - if (!event.altKey && event.button == 0) { - self.maskCtx.fillStyle = this.getMaskFillStyle(); - self.maskCtx.globalCompositeOperation = "source-over"; - } else { - self.maskCtx.globalCompositeOperation = "destination-out"; - } - self.maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false); - self.maskCtx.fill(); - self.lastx = x; - self.lasty = y; - self.lasttime = performance.now(); - } - } - - async save() { - const backupCanvas = document.createElement('canvas'); - const backupCtx = backupCanvas.getContext('2d', {willReadFrequently:true}); - backupCanvas.width = this.image.width; - backupCanvas.height = this.image.height; - - backupCtx.clearRect(0,0, backupCanvas.width, backupCanvas.height); - backupCtx.drawImage(this.maskCanvas, - 0, 0, this.maskCanvas.width, this.maskCanvas.height, - 0, 0, backupCanvas.width, backupCanvas.height); - - // paste mask data into alpha channel - const backupData = backupCtx.getImageData(0, 0, backupCanvas.width, backupCanvas.height); - - // refine mask image - for (let i = 0; i < backupData.data.length; i += 4) { - if(backupData.data[i+3] == 255) - backupData.data[i+3] = 0; - else - backupData.data[i+3] = 255; - - backupData.data[i] = 0; - backupData.data[i+1] = 0; - backupData.data[i+2] = 0; - } - - backupCtx.globalCompositeOperation = 'source-over'; - backupCtx.putImageData(backupData, 0, 0); - - const formData = new FormData(); - const filename = "clipspace-mask-" + performance.now() + ".png"; - - const item = - { - "filename": filename, - "subfolder": "clipspace", - "type": "input", - }; - - if(ComfyApp.clipspace.images) - ComfyApp.clipspace.images[0] = item; - - if(ComfyApp.clipspace.widgets) { - const index = ComfyApp.clipspace.widgets.findIndex(obj => obj.name === 'image'); - - if(index >= 0) - ComfyApp.clipspace.widgets[index].value = item; - } - - const dataURL = backupCanvas.toDataURL(); - const blob = dataURLToBlob(dataURL); - - let original_url = new URL(this.image.src); - - const original_ref = { filename: original_url.searchParams.get('filename') }; - - let original_subfolder = original_url.searchParams.get("subfolder"); - if(original_subfolder) - original_ref.subfolder = original_subfolder; - - let original_type = original_url.searchParams.get("type"); - if(original_type) - original_ref.type = original_type; - - formData.append('image', blob, filename); - formData.append('original_ref', JSON.stringify(original_ref)); - formData.append('type', "input"); - formData.append('subfolder', "clipspace"); - - this.saveButton.innerText = "Saving..."; - this.saveButton.disabled = true; - await uploadMask(item, formData); - ComfyApp.onClipspaceEditorSave(); - this.close(); - } -} - -app.registerExtension({ - name: "Comfy.MaskEditor", - init(app) { - ComfyApp.open_maskeditor = - function () { - const dlg = MaskEditorDialog.getInstance(); - if(!dlg.isOpened()) { - dlg.show(); - } - }; - - const context_predicate = () => ComfyApp.clipspace && ComfyApp.clipspace.imgs && ComfyApp.clipspace.imgs.length > 0 - ClipspaceDialog.registerButton("MaskEditor", context_predicate, ComfyApp.open_maskeditor); - } -}); diff --git a/web/extensions/core/nodeTemplates.js b/web/extensions/core/nodeTemplates.js deleted file mode 100644 index 9350ba654..000000000 --- a/web/extensions/core/nodeTemplates.js +++ /dev/null @@ -1,412 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js"; -import { ComfyDialog, $el } from "../../scripts/ui.js"; -import { GroupNodeConfig, GroupNodeHandler } from "./groupNode.js"; - -// Adds the ability to save and add multiple nodes as a template -// To save: -// Select multiple nodes (ctrl + drag to select a region or ctrl+click individual nodes) -// Right click the canvas -// Save Node Template -> give it a name -// -// To add: -// Right click the canvas -// Node templates -> click the one to add -// -// To delete/rename: -// Right click the canvas -// Node templates -> Manage -// -// To rearrange: -// Open the manage dialog and Drag and drop elements using the "Name:" label as handle - -const id = "Comfy.NodeTemplates"; -const file = "comfy.templates.json"; - -class ManageTemplates extends ComfyDialog { - constructor() { - super(); - this.load().then((v) => { - this.templates = v; - }); - - this.element.classList.add("comfy-manage-templates"); - this.draggedEl = null; - this.saveVisualCue = null; - this.emptyImg = new Image(); - this.emptyImg.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="; - - this.importInput = $el("input", { - type: "file", - accept: ".json", - multiple: true, - style: { display: "none" }, - parent: document.body, - onchange: () => this.importAll(), - }); - } - - createButtons() { - const btns = super.createButtons(); - btns[0].textContent = "Close"; - btns[0].onclick = (e) => { - clearTimeout(this.saveVisualCue); - this.close(); - }; - btns.unshift( - $el("button", { - type: "button", - textContent: "Export", - onclick: () => this.exportAll(), - }) - ); - btns.unshift( - $el("button", { - type: "button", - textContent: "Import", - onclick: () => { - this.importInput.click(); - }, - }) - ); - return btns; - } - - async load() { - let templates = []; - if (app.storageLocation === "server") { - if (app.isNewUserSession) { - // New user so migrate existing templates - const json = localStorage.getItem(id); - if (json) { - templates = JSON.parse(json); - } - await api.storeUserData(file, json, { stringify: false }); - } else { - const res = await api.getUserData(file); - if (res.status === 200) { - try { - templates = await res.json(); - } catch (error) { - } - } else if (res.status !== 404) { - console.error(res.status + " " + res.statusText); - } - } - } else { - const json = localStorage.getItem(id); - if (json) { - templates = JSON.parse(json); - } - } - - return templates ?? []; - } - - async store() { - if(app.storageLocation === "server") { - const templates = JSON.stringify(this.templates, undefined, 4); - localStorage.setItem(id, templates); // Backwards compatibility - try { - await api.storeUserData(file, templates, { stringify: false }); - } catch (error) { - console.error(error); - alert(error.message); - } - } else { - localStorage.setItem(id, JSON.stringify(this.templates)); - } - } - - async importAll() { - for (const file of this.importInput.files) { - if (file.type === "application/json" || file.name.endsWith(".json")) { - const reader = new FileReader(); - reader.onload = async () => { - const importFile = JSON.parse(reader.result); - if (importFile?.templates) { - for (const template of importFile.templates) { - if (template?.name && template?.data) { - this.templates.push(template); - } - } - await this.store(); - } - }; - await reader.readAsText(file); - } - } - - this.importInput.value = null; - - this.close(); - } - - exportAll() { - if (this.templates.length == 0) { - alert("No templates to export."); - return; - } - - const json = JSON.stringify({ templates: this.templates }, null, 2); // convert the data to a JSON string - const blob = new Blob([json], { type: "application/json" }); - const url = URL.createObjectURL(blob); - const a = $el("a", { - href: url, - download: "node_templates.json", - style: { display: "none" }, - parent: document.body, - }); - a.click(); - setTimeout(function () { - a.remove(); - window.URL.revokeObjectURL(url); - }, 0); - } - - show() { - // Show list of template names + delete button - super.show( - $el( - "div", - {}, - this.templates.flatMap((t,i) => { - let nameInput; - return [ - $el( - "div", - { - dataset: { id: i }, - className: "tempateManagerRow", - style: { - display: "grid", - gridTemplateColumns: "1fr auto", - border: "1px dashed transparent", - gap: "5px", - backgroundColor: "var(--comfy-menu-bg)" - }, - ondragstart: (e) => { - this.draggedEl = e.currentTarget; - e.currentTarget.style.opacity = "0.6"; - e.currentTarget.style.border = "1px dashed yellow"; - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setDragImage(this.emptyImg, 0, 0); - }, - ondragend: (e) => { - e.target.style.opacity = "1"; - e.currentTarget.style.border = "1px dashed transparent"; - e.currentTarget.removeAttribute("draggable"); - - // rearrange the elements - this.element.querySelectorAll('.tempateManagerRow').forEach((el,i) => { - var prev_i = el.dataset.id; - - if ( el == this.draggedEl && prev_i != i ) { - this.templates.splice(i, 0, this.templates.splice(prev_i, 1)[0]); - } - el.dataset.id = i; - }); - this.store(); - }, - ondragover: (e) => { - e.preventDefault(); - if ( e.currentTarget == this.draggedEl ) - return; - - let rect = e.currentTarget.getBoundingClientRect(); - if (e.clientY > rect.top + rect.height / 2) { - e.currentTarget.parentNode.insertBefore(this.draggedEl, e.currentTarget.nextSibling); - } else { - e.currentTarget.parentNode.insertBefore(this.draggedEl, e.currentTarget); - } - } - }, - [ - $el( - "label", - { - textContent: "Name: ", - style: { - cursor: "grab", - }, - onmousedown: (e) => { - // enable dragging only from the label - if (e.target.localName == 'label') - e.currentTarget.parentNode.draggable = 'true'; - } - }, - [ - $el("input", { - value: t.name, - dataset: { name: t.name }, - style: { - transitionProperty: 'background-color', - transitionDuration: '0s', - }, - onchange: (e) => { - clearTimeout(this.saveVisualCue); - var el = e.target; - var row = el.parentNode.parentNode; - this.templates[row.dataset.id].name = el.value.trim() || 'untitled'; - this.store(); - el.style.backgroundColor = 'rgb(40, 95, 40)'; - el.style.transitionDuration = '0s'; - this.saveVisualCue = setTimeout(function () { - el.style.transitionDuration = '.7s'; - el.style.backgroundColor = 'var(--comfy-input-bg)'; - }, 15); - }, - onkeypress: (e) => { - var el = e.target; - clearTimeout(this.saveVisualCue); - el.style.transitionDuration = '0s'; - el.style.backgroundColor = 'var(--comfy-input-bg)'; - }, - $: (el) => (nameInput = el), - }) - ] - ), - $el( - "div", - {}, - [ - $el("button", { - textContent: "Export", - style: { - fontSize: "12px", - fontWeight: "normal", - }, - onclick: (e) => { - const json = JSON.stringify({templates: [t]}, null, 2); // convert the data to a JSON string - const blob = new Blob([json], {type: "application/json"}); - const url = URL.createObjectURL(blob); - const a = $el("a", { - href: url, - download: (nameInput.value || t.name) + ".json", - style: {display: "none"}, - parent: document.body, - }); - a.click(); - setTimeout(function () { - a.remove(); - window.URL.revokeObjectURL(url); - }, 0); - }, - }), - $el("button", { - textContent: "Delete", - style: { - fontSize: "12px", - color: "red", - fontWeight: "normal", - }, - onclick: (e) => { - const item = e.target.parentNode.parentNode; - item.parentNode.removeChild(item); - this.templates.splice(item.dataset.id*1, 1); - this.store(); - // update the rows index, setTimeout ensures that the list is updated - var that = this; - setTimeout(function (){ - that.element.querySelectorAll('.tempateManagerRow').forEach((el,i) => { - el.dataset.id = i; - }); - }, 0); - }, - }), - ] - ), - ] - ) - ]; - }) - ) - ); - } -} - -app.registerExtension({ - name: id, - setup() { - const manage = new ManageTemplates(); - - const clipboardAction = async (cb) => { - // We use the clipboard functions but dont want to overwrite the current user clipboard - // Restore it after we've run our callback - const old = localStorage.getItem("litegrapheditor_clipboard"); - await cb(); - localStorage.setItem("litegrapheditor_clipboard", old); - }; - - const orig = LGraphCanvas.prototype.getCanvasMenuOptions; - LGraphCanvas.prototype.getCanvasMenuOptions = function () { - const options = orig.apply(this, arguments); - - options.push(null); - options.push({ - content: `Save Selected as Template`, - disabled: !Object.keys(app.canvas.selected_nodes || {}).length, - callback: () => { - const name = prompt("Enter name"); - if (!name?.trim()) return; - - clipboardAction(() => { - app.canvas.copyToClipboard(); - let data = localStorage.getItem("litegrapheditor_clipboard"); - data = JSON.parse(data); - const nodeIds = Object.keys(app.canvas.selected_nodes); - for (let i = 0; i < nodeIds.length; i++) { - const node = app.graph.getNodeById(nodeIds[i]); - const nodeData = node?.constructor.nodeData; - - let groupData = GroupNodeHandler.getGroupData(node); - if (groupData) { - groupData = groupData.nodeData; - if (!data.groupNodes) { - data.groupNodes = {}; - } - data.groupNodes[nodeData.name] = groupData; - data.nodes[i].type = nodeData.name; - } - } - - manage.templates.push({ - name, - data: JSON.stringify(data), - }); - manage.store(); - }); - }, - }); - - // Map each template to a menu item - const subItems = manage.templates.map((t) => { - return { - content: t.name, - callback: () => { - clipboardAction(async () => { - const data = JSON.parse(t.data); - await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {}); - localStorage.setItem("litegrapheditor_clipboard", t.data); - app.canvas.pasteFromClipboard(); - }); - }, - }; - }); - - subItems.push(null, { - content: "Manage", - callback: () => manage.show(), - }); - - options.push({ - content: "Node Templates", - submenu: { - options: subItems, - }, - }); - - return options; - }; - }, -}); diff --git a/web/extensions/core/noteNode.js b/web/extensions/core/noteNode.js deleted file mode 100644 index 8d89054e9..000000000 --- a/web/extensions/core/noteNode.js +++ /dev/null @@ -1,41 +0,0 @@ -import {app} from "../../scripts/app.js"; -import {ComfyWidgets} from "../../scripts/widgets.js"; -// Node that add notes to your project - -app.registerExtension({ - name: "Comfy.NoteNode", - registerCustomNodes() { - class NoteNode { - color=LGraphCanvas.node_colors.yellow.color; - bgcolor=LGraphCanvas.node_colors.yellow.bgcolor; - groupcolor = LGraphCanvas.node_colors.yellow.groupcolor; - constructor() { - if (!this.properties) { - this.properties = {}; - this.properties.text=""; - } - - ComfyWidgets.STRING(this, "", ["", {default:this.properties.text, multiline: true}], app) - - this.serialize_widgets = true; - this.isVirtualNode = true; - - } - - - } - - // Load default visibility - - LiteGraph.registerNodeType( - "Note", - Object.assign(NoteNode, { - title_mode: LiteGraph.NORMAL_TITLE, - title: "Note", - collapsable: true, - }) - ); - - NoteNode.category = "utils"; - }, -}); diff --git a/web/extensions/core/rerouteNode.js b/web/extensions/core/rerouteNode.js deleted file mode 100644 index 4feff91e5..000000000 --- a/web/extensions/core/rerouteNode.js +++ /dev/null @@ -1,274 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { mergeIfValid, getWidgetConfig, setWidgetConfig } from "./widgetInputs.js"; - -// Node that allows you to redirect connections for cleaner graphs - -app.registerExtension({ - name: "Comfy.RerouteNode", - registerCustomNodes(app) { - class RerouteNode { - constructor() { - if (!this.properties) { - this.properties = {}; - } - this.properties.showOutputText = RerouteNode.defaultVisibility; - this.properties.horizontal = false; - - this.addInput("", "*"); - this.addOutput(this.properties.showOutputText ? "*" : "", "*"); - - this.onAfterGraphConfigured = function () { - requestAnimationFrame(() => { - this.onConnectionsChange(LiteGraph.INPUT, null, true, null); - }); - }; - - this.onConnectionsChange = function (type, index, connected, link_info) { - this.applyOrientation(); - - // Prevent multiple connections to different types when we have no input - if (connected && type === LiteGraph.OUTPUT) { - // Ignore wildcard nodes as these will be updated to real types - const types = new Set(this.outputs[0].links.map((l) => app.graph.links[l].type).filter((t) => t !== "*")); - if (types.size > 1) { - const linksToDisconnect = []; - for (let i = 0; i < this.outputs[0].links.length - 1; i++) { - const linkId = this.outputs[0].links[i]; - const link = app.graph.links[linkId]; - linksToDisconnect.push(link); - } - for (const link of linksToDisconnect) { - const node = app.graph.getNodeById(link.target_id); - node.disconnectInput(link.target_slot); - } - } - } - - // Find root input - let currentNode = this; - let updateNodes = []; - let inputType = null; - let inputNode = null; - while (currentNode) { - updateNodes.unshift(currentNode); - const linkId = currentNode.inputs[0].link; - if (linkId !== null) { - const link = app.graph.links[linkId]; - if (!link) return; - const node = app.graph.getNodeById(link.origin_id); - const type = node.constructor.type; - if (type === "Reroute") { - if (node === this) { - // We've found a circle - currentNode.disconnectInput(link.target_slot); - currentNode = null; - } else { - // Move the previous node - currentNode = node; - } - } else { - // We've found the end - inputNode = currentNode; - inputType = node.outputs[link.origin_slot]?.type ?? null; - break; - } - } else { - // This path has no input node - currentNode = null; - break; - } - } - - // Find all outputs - const nodes = [this]; - let outputType = null; - while (nodes.length) { - currentNode = nodes.pop(); - const outputs = (currentNode.outputs ? currentNode.outputs[0].links : []) || []; - if (outputs.length) { - for (const linkId of outputs) { - const link = app.graph.links[linkId]; - - // When disconnecting sometimes the link is still registered - if (!link) continue; - - const node = app.graph.getNodeById(link.target_id); - const type = node.constructor.type; - - if (type === "Reroute") { - // Follow reroute nodes - nodes.push(node); - updateNodes.push(node); - } else { - // We've found an output - const nodeOutType = - node.inputs && node.inputs[link?.target_slot] && node.inputs[link.target_slot].type - ? node.inputs[link.target_slot].type - : null; - if (inputType && inputType !== "*" && nodeOutType !== inputType) { - // The output doesnt match our input so disconnect it - node.disconnectInput(link.target_slot); - } else { - outputType = nodeOutType; - } - } - } - } else { - // No more outputs for this path - } - } - - const displayType = inputType || outputType || "*"; - const color = LGraphCanvas.link_type_colors[displayType]; - - let widgetConfig; - let targetWidget; - let widgetType; - // Update the types of each node - for (const node of updateNodes) { - // If we dont have an input type we are always wildcard but we'll show the output type - // This lets you change the output link to a different type and all nodes will update - node.outputs[0].type = inputType || "*"; - node.__outputType = displayType; - node.outputs[0].name = node.properties.showOutputText ? displayType : ""; - node.size = node.computeSize(); - node.applyOrientation(); - - for (const l of node.outputs[0].links || []) { - const link = app.graph.links[l]; - if (link) { - link.color = color; - - if (app.configuringGraph) continue; - const targetNode = app.graph.getNodeById(link.target_id); - const targetInput = targetNode.inputs?.[link.target_slot]; - if (targetInput?.widget) { - const config = getWidgetConfig(targetInput); - if (!widgetConfig) { - widgetConfig = config[1] ?? {}; - widgetType = config[0]; - } - if (!targetWidget) { - targetWidget = targetNode.widgets?.find((w) => w.name === targetInput.widget.name); - } - - const merged = mergeIfValid(targetInput, [config[0], widgetConfig]); - if (merged.customConfig) { - widgetConfig = merged.customConfig; - } - } - } - } - } - - for (const node of updateNodes) { - if (widgetConfig && outputType) { - node.inputs[0].widget = { name: "value" }; - setWidgetConfig(node.inputs[0], [widgetType ?? displayType, widgetConfig], targetWidget); - } else { - setWidgetConfig(node.inputs[0], null); - } - } - - if (inputNode) { - const link = app.graph.links[inputNode.inputs[0].link]; - if (link) { - link.color = color; - } - } - }; - - this.clone = function () { - const cloned = RerouteNode.prototype.clone.apply(this); - cloned.removeOutput(0); - cloned.addOutput(this.properties.showOutputText ? "*" : "", "*"); - cloned.size = cloned.computeSize(); - return cloned; - }; - - // This node is purely frontend and does not impact the resulting prompt so should not be serialized - this.isVirtualNode = true; - } - - getExtraMenuOptions(_, options) { - options.unshift( - { - content: (this.properties.showOutputText ? "Hide" : "Show") + " Type", - callback: () => { - this.properties.showOutputText = !this.properties.showOutputText; - if (this.properties.showOutputText) { - this.outputs[0].name = this.__outputType || this.outputs[0].type; - } else { - this.outputs[0].name = ""; - } - this.size = this.computeSize(); - this.applyOrientation(); - app.graph.setDirtyCanvas(true, true); - }, - }, - { - content: (RerouteNode.defaultVisibility ? "Hide" : "Show") + " Type By Default", - callback: () => { - RerouteNode.setDefaultTextVisibility(!RerouteNode.defaultVisibility); - }, - }, - { - // naming is inverted with respect to LiteGraphNode.horizontal - // LiteGraphNode.horizontal == true means that - // each slot in the inputs and outputs are layed out horizontally, - // which is the opposite of the visual orientation of the inputs and outputs as a node - content: "Set " + (this.properties.horizontal ? "Horizontal" : "Vertical"), - callback: () => { - this.properties.horizontal = !this.properties.horizontal; - this.applyOrientation(); - }, - } - ); - } - applyOrientation() { - this.horizontal = this.properties.horizontal; - if (this.horizontal) { - // we correct the input position, because LiteGraphNode.horizontal - // doesn't account for title presence - // which reroute nodes don't have - this.inputs[0].pos = [this.size[0] / 2, 0]; - } else { - delete this.inputs[0].pos; - } - app.graph.setDirtyCanvas(true, true); - } - - computeSize() { - return [ - this.properties.showOutputText && this.outputs && this.outputs.length - ? Math.max(75, LiteGraph.NODE_TEXT_SIZE * this.outputs[0].name.length * 0.6 + 40) - : 75, - 26, - ]; - } - - static setDefaultTextVisibility(visible) { - RerouteNode.defaultVisibility = visible; - if (visible) { - localStorage["Comfy.RerouteNode.DefaultVisibility"] = "true"; - } else { - delete localStorage["Comfy.RerouteNode.DefaultVisibility"]; - } - } - } - - // Load default visibility - RerouteNode.setDefaultTextVisibility(!!localStorage["Comfy.RerouteNode.DefaultVisibility"]); - - LiteGraph.registerNodeType( - "Reroute", - Object.assign(RerouteNode, { - title_mode: LiteGraph.NO_TITLE, - title: "Reroute", - collapsable: false, - }) - ); - - RerouteNode.category = "utils"; - }, -}); diff --git a/web/extensions/core/saveImageExtraOutput.js b/web/extensions/core/saveImageExtraOutput.js deleted file mode 100644 index a0506b43b..000000000 --- a/web/extensions/core/saveImageExtraOutput.js +++ /dev/null @@ -1,35 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { applyTextReplacements } from "../../scripts/utils.js"; -// Use widget values and dates in output filenames - -app.registerExtension({ - name: "Comfy.SaveImageExtraOutput", - async beforeRegisterNodeDef(nodeType, nodeData, app) { - if (nodeData.name === "SaveImage") { - const onNodeCreated = nodeType.prototype.onNodeCreated; - // When the SaveImage node is created we want to override the serialization of the output name widget to run our S&R - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; - - const widget = this.widgets.find((w) => w.name === "filename_prefix"); - widget.serializeValue = () => { - return applyTextReplacements(app, widget.value); - }; - - return r; - }; - } else { - // When any other node is created add a property to alias the node - const onNodeCreated = nodeType.prototype.onNodeCreated; - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; - - if (!this.properties || !("Node name for S&R" in this.properties)) { - this.addProperty("Node name for S&R", this.constructor.type, "string"); - } - - return r; - }; - } - }, -}); diff --git a/web/extensions/core/simpleTouchSupport.js b/web/extensions/core/simpleTouchSupport.js deleted file mode 100644 index 041fc2c4c..000000000 --- a/web/extensions/core/simpleTouchSupport.js +++ /dev/null @@ -1,102 +0,0 @@ -import { app } from "../../scripts/app.js"; - -let touchZooming; -let touchCount = 0; - -app.registerExtension({ - name: "Comfy.SimpleTouchSupport", - setup() { - let zoomPos; - let touchTime; - let lastTouch; - - function getMultiTouchPos(e) { - return Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY); - } - - app.canvasEl.addEventListener( - "touchstart", - (e) => { - touchCount++; - lastTouch = null; - if (e.touches?.length === 1) { - // Store start time for press+hold for context menu - touchTime = new Date(); - lastTouch = e.touches[0]; - } else { - touchTime = null; - if (e.touches?.length === 2) { - // Store center pos for zoom - zoomPos = getMultiTouchPos(e); - app.canvas.pointer_is_down = false; - } - } - }, - true - ); - - app.canvasEl.addEventListener("touchend", (e) => { - touchZooming = false; - touchCount = e.touches?.length ?? touchCount - 1; - if (touchTime && !e.touches?.length) { - if (new Date() - touchTime > 600) { - try { - // hack to get litegraph to use this event - e.constructor = CustomEvent; - } catch (error) {} - e.clientX = lastTouch.clientX; - e.clientY = lastTouch.clientY; - - app.canvas.pointer_is_down = true; - app.canvas._mousedown_callback(e); - } - touchTime = null; - } - }); - - app.canvasEl.addEventListener( - "touchmove", - (e) => { - touchTime = null; - if (e.touches?.length === 2) { - app.canvas.pointer_is_down = false; - touchZooming = true; - LiteGraph.closeAllContextMenus(); - app.canvas.search_box?.close(); - const newZoomPos = getMultiTouchPos(e); - - const midX = (e.touches[0].clientX + e.touches[1].clientX) / 2; - const midY = (e.touches[0].clientY + e.touches[1].clientY) / 2; - - let scale = app.canvas.ds.scale; - const diff = zoomPos - newZoomPos; - if (diff > 0.5) { - scale *= 1 / 1.07; - } else if (diff < -0.5) { - scale *= 1.07; - } - app.canvas.ds.changeScale(scale, [midX, midY]); - app.canvas.setDirty(true, true); - zoomPos = newZoomPos; - } - }, - true - ); - }, -}); - -const processMouseDown = LGraphCanvas.prototype.processMouseDown; -LGraphCanvas.prototype.processMouseDown = function (e) { - if (touchZooming || touchCount) { - return; - } - return processMouseDown.apply(this, arguments); -}; - -const processMouseMove = LGraphCanvas.prototype.processMouseMove; -LGraphCanvas.prototype.processMouseMove = function (e) { - if (touchZooming || touchCount > 1) { - return; - } - return processMouseMove.apply(this, arguments); -}; diff --git a/web/extensions/core/slotDefaults.js b/web/extensions/core/slotDefaults.js deleted file mode 100644 index 718d25405..000000000 --- a/web/extensions/core/slotDefaults.js +++ /dev/null @@ -1,91 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { ComfyWidgets } from "../../scripts/widgets.js"; -// Adds defaults for quickly adding nodes with middle click on the input/output - -app.registerExtension({ - name: "Comfy.SlotDefaults", - suggestionsNumber: null, - init() { - LiteGraph.search_filter_enabled = true; - LiteGraph.middle_click_slot_add_default_node = true; - this.suggestionsNumber = app.ui.settings.addSetting({ - id: "Comfy.NodeSuggestions.number", - name: "Number of nodes suggestions", - type: "slider", - attrs: { - min: 1, - max: 100, - step: 1, - }, - defaultValue: 5, - onChange: (newVal, oldVal) => { - this.setDefaults(newVal); - } - }); - }, - slot_types_default_out: {}, - slot_types_default_in: {}, - async beforeRegisterNodeDef(nodeType, nodeData, app) { - var nodeId = nodeData.name; - var inputs = []; - inputs = nodeData["input"]["required"]; //only show required inputs to reduce the mess also not logical to create node with optional inputs - for (const inputKey in inputs) { - var input = (inputs[inputKey]); - if (typeof input[0] !== "string") continue; - - var type = input[0] - if (type in ComfyWidgets) { - var customProperties = input[1] - if (!(customProperties?.forceInput)) continue; //ignore widgets that don't force input - } - - if (!(type in this.slot_types_default_out)) { - this.slot_types_default_out[type] = ["Reroute"]; - } - if (this.slot_types_default_out[type].includes(nodeId)) continue; - this.slot_types_default_out[type].push(nodeId); - - // Input types have to be stored as lower case - // Store each node that can handle this input type - const lowerType = type.toLocaleLowerCase(); - if (!(lowerType in LiteGraph.registered_slot_in_types)) { - LiteGraph.registered_slot_in_types[lowerType] = { nodes: [] }; - } - LiteGraph.registered_slot_in_types[lowerType].nodes.push(nodeType.comfyClass); - } - - var outputs = nodeData["output"]; - for (const key in outputs) { - var type = outputs[key]; - if (!(type in this.slot_types_default_in)) { - this.slot_types_default_in[type] = ["Reroute"];// ["Reroute", "Primitive"]; primitive doesn't always work :'() - } - - this.slot_types_default_in[type].push(nodeId); - - // Store each node that can handle this output type - if (!(type in LiteGraph.registered_slot_out_types)) { - LiteGraph.registered_slot_out_types[type] = { nodes: [] }; - } - LiteGraph.registered_slot_out_types[type].nodes.push(nodeType.comfyClass); - - if(!LiteGraph.slot_types_out.includes(type)) { - LiteGraph.slot_types_out.push(type); - } - } - var maxNum = this.suggestionsNumber.value; - this.setDefaults(maxNum); - }, - setDefaults(maxNum) { - - LiteGraph.slot_types_default_out = {}; - LiteGraph.slot_types_default_in = {}; - - for (const type in this.slot_types_default_out) { - LiteGraph.slot_types_default_out[type] = this.slot_types_default_out[type].slice(0, maxNum); - } - for (const type in this.slot_types_default_in) { - LiteGraph.slot_types_default_in[type] = this.slot_types_default_in[type].slice(0, maxNum); - } - } -}); diff --git a/web/extensions/core/snapToGrid.js b/web/extensions/core/snapToGrid.js deleted file mode 100644 index aac017748..000000000 --- a/web/extensions/core/snapToGrid.js +++ /dev/null @@ -1,171 +0,0 @@ -import { app } from "../../scripts/app.js"; - -// Shift + drag/resize to snap to grid - -/** Rounds a Vector2 in-place to the current CANVAS_GRID_SIZE. */ -function roundVectorToGrid(vec) { - vec[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(vec[0] / LiteGraph.CANVAS_GRID_SIZE); - vec[1] = LiteGraph.CANVAS_GRID_SIZE * Math.round(vec[1] / LiteGraph.CANVAS_GRID_SIZE); - return vec; -} - -app.registerExtension({ - name: "Comfy.SnapToGrid", - init() { - // Add setting to control grid size - app.ui.settings.addSetting({ - id: "Comfy.SnapToGrid.GridSize", - name: "Grid Size", - type: "slider", - attrs: { - min: 1, - max: 500, - }, - tooltip: - "When dragging and resizing nodes while holding shift they will be aligned to the grid, this controls the size of that grid.", - defaultValue: LiteGraph.CANVAS_GRID_SIZE, - onChange(value) { - LiteGraph.CANVAS_GRID_SIZE = +value; - }, - }); - - // After moving a node, if the shift key is down align it to grid - const onNodeMoved = app.canvas.onNodeMoved; - app.canvas.onNodeMoved = function (node) { - const r = onNodeMoved?.apply(this, arguments); - - if (app.shiftDown) { - // Ensure all selected nodes are realigned - for (const id in this.selected_nodes) { - this.selected_nodes[id].alignToGrid(); - } - } - - return r; - }; - - // When a node is added, add a resize handler to it so we can fix align the size with the grid - const onNodeAdded = app.graph.onNodeAdded; - app.graph.onNodeAdded = function (node) { - const onResize = node.onResize; - node.onResize = function () { - if (app.shiftDown) { - roundVectorToGrid(node.size); - } - return onResize?.apply(this, arguments); - }; - return onNodeAdded?.apply(this, arguments); - }; - - // Draw a preview of where the node will go if holding shift and the node is selected - const origDrawNode = LGraphCanvas.prototype.drawNode; - LGraphCanvas.prototype.drawNode = function (node, ctx) { - if (app.shiftDown && this.node_dragged && node.id in this.selected_nodes) { - const [x, y] = roundVectorToGrid([...node.pos]); - const shiftX = x - node.pos[0]; - let shiftY = y - node.pos[1]; - - let w, h; - if (node.flags.collapsed) { - w = node._collapsed_width; - h = LiteGraph.NODE_TITLE_HEIGHT; - shiftY -= LiteGraph.NODE_TITLE_HEIGHT; - } else { - w = node.size[0]; - h = node.size[1]; - let titleMode = node.constructor.title_mode; - if (titleMode !== LiteGraph.TRANSPARENT_TITLE && titleMode !== LiteGraph.NO_TITLE) { - h += LiteGraph.NODE_TITLE_HEIGHT; - shiftY -= LiteGraph.NODE_TITLE_HEIGHT; - } - } - const f = ctx.fillStyle; - ctx.fillStyle = "rgba(100, 100, 100, 0.5)"; - ctx.fillRect(shiftX, shiftY, w, h); - ctx.fillStyle = f; - } - - return origDrawNode.apply(this, arguments); - }; - - - - /** - * The currently moving, selected group only. Set after the `selected_group` has actually started - * moving. - */ - let selectedAndMovingGroup = null; - - /** - * Handles moving a group; tracking when a group has been moved (to show the ghost in `drawGroups` - * below) as well as handle the last move call from LiteGraph's `processMouseUp`. - */ - const groupMove = LGraphGroup.prototype.move; - LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) { - const v = groupMove.apply(this, arguments); - // When we've started moving, set `selectedAndMovingGroup` as LiteGraph sets `selected_group` - // too eagerly and we don't want to behave like we're moving until we get a delta. - if (!selectedAndMovingGroup && app.canvas.selected_group === this && (deltax || deltay)) { - selectedAndMovingGroup = this; - } - - // LiteGraph will call group.move both on mouse-move as well as mouse-up though we only want - // to snap on a mouse-up which we can determine by checking if `app.canvas.last_mouse_dragging` - // has been set to `false`. Essentially, this check here is the equivilant to calling an - // `LGraphGroup.prototype.onNodeMoved` if it had existed. - if (app.canvas.last_mouse_dragging === false && app.shiftDown) { - // After moving a group (while app.shiftDown), snap all the child nodes and, finally, - // align the group itself. - this.recomputeInsideNodes(); - for (const node of this._nodes) { - node.alignToGrid(); - } - LGraphNode.prototype.alignToGrid.apply(this); - } - return v; - }; - - /** - * Handles drawing a group when, snapping the size when one is actively being resized tracking and/or - * drawing a ghost box when one is actively being moved. This mimics the node snapping behavior for - * both. - */ - const drawGroups = LGraphCanvas.prototype.drawGroups; - LGraphCanvas.prototype.drawGroups = function (canvas, ctx) { - if (this.selected_group && app.shiftDown) { - if (this.selected_group_resizing) { - roundVectorToGrid(this.selected_group.size); - } else if (selectedAndMovingGroup) { - const [x, y] = roundVectorToGrid([...selectedAndMovingGroup.pos]); - const f = ctx.fillStyle; - const s = ctx.strokeStyle; - ctx.fillStyle = "rgba(100, 100, 100, 0.33)"; - ctx.strokeStyle = "rgba(100, 100, 100, 0.66)"; - ctx.rect(x, y, ...selectedAndMovingGroup.size); - ctx.fill(); - ctx.stroke(); - ctx.fillStyle = f; - ctx.strokeStyle = s; - } - } else if (!this.selected_group) { - selectedAndMovingGroup = null; - } - return drawGroups.apply(this, arguments); - }; - - - /** Handles adding a group in a snapping-enabled state. */ - const onGroupAdd = LGraphCanvas.onGroupAdd; - LGraphCanvas.onGroupAdd = function() { - const v = onGroupAdd.apply(app.canvas, arguments); - if (app.shiftDown) { - const lastGroup = app.graph._groups[app.graph._groups.length - 1]; - if (lastGroup) { - roundVectorToGrid(lastGroup.pos); - roundVectorToGrid(lastGroup.size); - } - } - return v; - }; - }, -}); diff --git a/web/extensions/core/tooltips.js b/web/extensions/core/tooltips.js deleted file mode 100644 index 08ab05efa..000000000 --- a/web/extensions/core/tooltips.js +++ /dev/null @@ -1,122 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { $el } from "../../scripts/ui.js"; - -// Adds support for tooltips - -function getHoveredWidget() { - if (!app) { - return; - } - - const node = app.canvas.node_over; - if (!node.widgets) return; - - const graphPos = app.canvas.graph_mouse; - - const x = graphPos[0] - node.pos[0]; - const y = graphPos[1] - node.pos[1]; - - for (const w of node.widgets) { - let widgetWidth, widgetHeight; - if (w.computeSize) { - const sz = w.computeSize(); - widgetWidth = sz[0]; - widgetHeight = sz[1]; - } else { - widgetWidth = w.width || node.size[0]; - widgetHeight = LiteGraph.NODE_WIDGET_HEIGHT; - } - - if (w.last_y !== undefined && x >= 6 && x <= widgetWidth - 12 && y >= w.last_y && y <= w.last_y + widgetHeight) { - return w; - } - } -} - -app.registerExtension({ - name: "Comfy.Tooltips", - setup() { - const tooltipEl = $el("div.comfy-graph-tooltip", { - parent: document.body, - }); - let idleTimeout; - - const hideTooltip = () => { - tooltipEl.style.display = "none"; - }; - const showTooltip = (tooltip) => { - if (!tooltip) return; - - tooltipEl.textContent = tooltip; - tooltipEl.style.display = "block"; - tooltipEl.style.left = app.canvas.mouse[0] + "px"; - tooltipEl.style.top = app.canvas.mouse[1] + "px"; - const rect = tooltipEl.getBoundingClientRect(); - if (rect.right > window.innerWidth) { - tooltipEl.style.left = app.canvas.mouse[0] - rect.width + "px"; - } - - if (rect.top < 0) { - tooltipEl.style.top = app.canvas.mouse[1] + rect.height + "px"; - } - }; - const getInputTooltip = (nodeData, name) => { - const inputDef = nodeData.input?.required?.[name] ?? nodeData.input?.optional?.[name]; - return inputDef?.[1]?.tooltip; - }; - const onIdle = () => { - const { canvas } = app; - const node = canvas.node_over; - if (!node) return; - - const nodeData = node.constructor.nodeData ?? {}; - - if (node.constructor.title_mode !== LiteGraph.NO_TITLE && canvas.graph_mouse[1] < node.pos[1]) { - return showTooltip(nodeData.description); - } - - if (node.flags?.collapsed) return; - - const inputSlot = canvas.isOverNodeInput(node, canvas.graph_mouse[0], canvas.graph_mouse[1], [0, 0]); - if (inputSlot !== -1) { - const inputName = node.inputs[inputSlot].name; - return showTooltip(getInputTooltip(nodeData, inputName)); - } - - const outputSlot = canvas.isOverNodeOutput(node, canvas.graph_mouse[0], canvas.graph_mouse[1], [0, 0]); - if (outputSlot !== -1) { - return showTooltip(nodeData.output_tooltips?.[outputSlot]); - } - - const widget = getHoveredWidget(); - // Dont show for DOM widgets, these use native browser tooltips as we dont get proper mouse events on these - if (widget && !widget.element) { - return showTooltip(widget.tooltip ?? getInputTooltip(nodeData, widget.name)); - } - }; - - const onMouseMove = (e) => { - hideTooltip(); - clearTimeout(idleTimeout); - - if(e.target.nodeName !== "CANVAS") return - idleTimeout = setTimeout(onIdle, 500); - }; - - app.ui.settings.addSetting({ - id: "Comfy.EnableTooltips", - name: "Enable Tooltips", - type: "boolean", - defaultValue: true, - onChange(value) { - if (value) { - window.addEventListener("mousemove", onMouseMove); - window.addEventListener("click", hideTooltip); - } else { - window.removeEventListener("mousemove", onMouseMove); - window.removeEventListener("click", hideTooltip); - } - }, - }); - }, -}); diff --git a/web/extensions/core/uploadAudio.js b/web/extensions/core/uploadAudio.js deleted file mode 100644 index 9dfa029bf..000000000 --- a/web/extensions/core/uploadAudio.js +++ /dev/null @@ -1,186 +0,0 @@ -import { app } from "../../scripts/app.js" -import { api } from "../../scripts/api.js" - -function splitFilePath(path) { - const folder_separator = path.lastIndexOf("/") - if (folder_separator === -1) { - return ["", path] - } - return [ - path.substring(0, folder_separator), - path.substring(folder_separator + 1) - ] -} - -function getResourceURL(subfolder, filename, type = "input") { - const params = [ - "filename=" + encodeURIComponent(filename), - "type=" + type, - "subfolder=" + subfolder, - app.getRandParam().substring(1) - ].join("&") - - return `/view?${params}` -} - -async function uploadFile( - audioWidget, - audioUIWidget, - file, - updateNode, - pasted = false -) { - try { - // Wrap file in formdata so it includes filename - const body = new FormData() - body.append("image", file) - if (pasted) body.append("subfolder", "pasted") - const resp = await api.fetchApi("/upload/image", { - method: "POST", - body - }) - - if (resp.status === 200) { - const data = await resp.json() - // Add the file to the dropdown list and update the widget value - let path = data.name - if (data.subfolder) path = data.subfolder + "/" + path - - if (!audioWidget.options.values.includes(path)) { - audioWidget.options.values.push(path) - } - - if (updateNode) { - audioUIWidget.element.src = api.apiURL( - getResourceURL(...splitFilePath(path)) - ) - audioWidget.value = path - } - } else { - alert(resp.status + " - " + resp.statusText) - } - } catch (error) { - alert(error) - } -} - -// AudioWidget MUST be registered first, as AUDIOUPLOAD depends on AUDIO_UI to be -// present. -app.registerExtension({ - name: "Comfy.AudioWidget", - async beforeRegisterNodeDef(nodeType, nodeData) { - if (["LoadAudio", "SaveAudio", "PreviewAudio"].includes(nodeType.comfyClass)) { - nodeData.input.required.audioUI = ["AUDIO_UI"] - } - }, - getCustomWidgets() { - return { - AUDIO_UI(node, inputName) { - const audio = document.createElement("audio") - audio.controls = true - audio.classList.add("comfy-audio") - audio.setAttribute("name", "media") - - const audioUIWidget = node.addDOMWidget( - inputName, - /* name=*/ "audioUI", - audio - ) - // @ts-ignore - // TODO: Sort out the DOMWidget type. - audioUIWidget.serialize = false - - const isOutputNode = node.constructor.nodeData.output_node - if (isOutputNode) { - // Hide the audio widget when there is no audio initially. - audioUIWidget.element.classList.add("empty-audio-widget") - // Populate the audio widget UI on node execution. - const onExecuted = node.onExecuted - node.onExecuted = function(message) { - onExecuted?.apply(this, arguments) - const audios = message.audio - if (!audios) return - const audio = audios[0] - audioUIWidget.element.src = api.apiURL( - getResourceURL(audio.subfolder, audio.filename, audio.type) - ) - audioUIWidget.element.classList.remove("empty-audio-widget") - } - } - return { widget: audioUIWidget } - } - } - }, - onNodeOutputsUpdated(nodeOutputs) { - for (const [nodeId, output] of Object.entries(nodeOutputs)) { - const node = app.graph.getNodeById(Number.parseInt(nodeId)); - if ("audio" in output) { - const audioUIWidget = node.widgets.find((w) => w.name === "audioUI"); - const audio = output.audio[0]; - audioUIWidget.element.src = api.apiURL(getResourceURL(audio.subfolder, audio.filename, audio.type)); - audioUIWidget.element.classList.remove("empty-audio-widget"); - } - } - }, -}) - -app.registerExtension({ - name: "Comfy.UploadAudio", - async beforeRegisterNodeDef(nodeType, nodeData) { - if (nodeData?.input?.required?.audio?.[1]?.audio_upload === true) { - nodeData.input.required.upload = ["AUDIOUPLOAD"] - } - }, - getCustomWidgets() { - return { - AUDIOUPLOAD(node, inputName) { - // The widget that allows user to select file. - const audioWidget = node.widgets.find(w => w.name === "audio") - const audioUIWidget = node.widgets.find(w => w.name === "audioUI") - - const onAudioWidgetUpdate = () => { - audioUIWidget.element.src = api.apiURL( - getResourceURL(...splitFilePath(audioWidget.value)) - ) - } - // Initially load default audio file to audioUIWidget. - if (audioWidget.value) { - onAudioWidgetUpdate() - } - audioWidget.callback = onAudioWidgetUpdate - - // Load saved audio file widget values if restoring from workflow - const onGraphConfigured = node.onGraphConfigured; - node.onGraphConfigured = function() { - onGraphConfigured?.apply(this, arguments) - if (audioWidget.value) { - onAudioWidgetUpdate() - } - } - - const fileInput = document.createElement("input") - fileInput.type = "file" - fileInput.accept = "audio/*" - fileInput.style.display = "none" - fileInput.onchange = () => { - if (fileInput.files.length) { - uploadFile(audioWidget, audioUIWidget, fileInput.files[0], true) - } - } - // The widget to pop up the upload dialog. - const uploadWidget = node.addWidget( - "button", - inputName, - /* value=*/ "", - () => { - fileInput.click() - } - ) - uploadWidget.label = "choose file to upload" - uploadWidget.serialize = false - - return { widget: uploadWidget } - } - } - } -}) diff --git a/web/extensions/core/uploadImage.js b/web/extensions/core/uploadImage.js deleted file mode 100644 index 530c4599e..000000000 --- a/web/extensions/core/uploadImage.js +++ /dev/null @@ -1,12 +0,0 @@ -import { app } from "../../scripts/app.js"; - -// Adds an upload button to the nodes - -app.registerExtension({ - name: "Comfy.UploadImage", - async beforeRegisterNodeDef(nodeType, nodeData, app) { - if (nodeData?.input?.required?.image?.[1]?.image_upload === true) { - nodeData.input.required.upload = ["IMAGEUPLOAD"]; - } - }, -}); diff --git a/web/extensions/core/webcamCapture.js b/web/extensions/core/webcamCapture.js deleted file mode 100644 index dd5725bd4..000000000 --- a/web/extensions/core/webcamCapture.js +++ /dev/null @@ -1,126 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js"; - -const WEBCAM_READY = Symbol(); - -app.registerExtension({ - name: "Comfy.WebcamCapture", - getCustomWidgets(app) { - return { - WEBCAM(node, inputName) { - let res; - node[WEBCAM_READY] = new Promise((resolve) => (res = resolve)); - - const container = document.createElement("div"); - container.style.background = "rgba(0,0,0,0.25)"; - container.style.textAlign = "center"; - - const video = document.createElement("video"); - video.style.height = video.style.width = "100%"; - - const loadVideo = async () => { - try { - const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false }); - container.replaceChildren(video); - - setTimeout(() => res(video), 500); // Fallback as loadedmetadata doesnt fire sometimes? - video.addEventListener("loadedmetadata", () => res(video), false); - video.srcObject = stream; - video.play(); - } catch (error) { - const label = document.createElement("div"); - label.style.color = "red"; - label.style.overflow = "auto"; - label.style.maxHeight = "100%"; - label.style.whiteSpace = "pre-wrap"; - - if (window.isSecureContext) { - label.textContent = "Unable to load webcam, please ensure access is granted:\n" + error.message; - } else { - label.textContent = "Unable to load webcam. A secure context is required, if you are not accessing ComfyUI on localhost (127.0.0.1) you will have to enable TLS (https)\n\n" + error.message; - } - - container.replaceChildren(label); - } - }; - - loadVideo(); - - return { widget: node.addDOMWidget(inputName, "WEBCAM", container) }; - }, - }; - }, - nodeCreated(node) { - if ((node.type, node.constructor.comfyClass !== "WebcamCapture")) return; - - let video; - const camera = node.widgets.find((w) => w.name === "image"); - const w = node.widgets.find((w) => w.name === "width"); - const h = node.widgets.find((w) => w.name === "height"); - const captureOnQueue = node.widgets.find((w) => w.name === "capture_on_queue"); - - const canvas = document.createElement("canvas"); - - const capture = () => { - canvas.width = w.value; - canvas.height = h.value; - const ctx = canvas.getContext("2d"); - ctx.drawImage(video, 0, 0, w.value, h.value); - const data = canvas.toDataURL("image/png"); - - const img = new Image(); - img.onload = () => { - node.imgs = [img]; - app.graph.setDirtyCanvas(true); - requestAnimationFrame(() => { - node.setSizeForImage?.(); - }); - }; - img.src = data; - }; - - const btn = node.addWidget("button", "waiting for camera...", "capture", capture); - btn.disabled = true; - btn.serializeValue = () => undefined; - - camera.serializeValue = async () => { - if (captureOnQueue.value) { - capture(); - } else if (!node.imgs?.length) { - const err = `No webcam image captured`; - alert(err); - throw new Error(err); - } - - // Upload image to temp storage - const blob = await new Promise((r) => canvas.toBlob(r)); - const name = `${+new Date()}.png`; - const file = new File([blob], name); - const body = new FormData(); - body.append("image", file); - body.append("subfolder", "webcam"); - body.append("type", "temp"); - const resp = await api.fetchApi("/upload/image", { - method: "POST", - body, - }); - if (resp.status !== 200) { - const err = `Error uploading camera image: ${resp.status} - ${resp.statusText}`; - alert(err); - throw new Error(err); - } - return `webcam/${name} [temp]`; - }; - - node[WEBCAM_READY].then((v) => { - video = v; - // If width isnt specified then use video output resolution - if (!w.value) { - w.value = video.videoWidth || 640; - h.value = video.videoHeight || 480; - } - btn.disabled = false; - btn.label = "capture"; - }); - }, -}); diff --git a/web/extensions/core/widgetInputs.js b/web/extensions/core/widgetInputs.js deleted file mode 100644 index 88f1f9d8a..000000000 --- a/web/extensions/core/widgetInputs.js +++ /dev/null @@ -1,800 +0,0 @@ -import { ComfyWidgets, addValueControlWidgets } from "../../scripts/widgets.js"; -import { app } from "../../scripts/app.js"; -import { applyTextReplacements } from "../../scripts/utils.js"; - -const CONVERTED_TYPE = "converted-widget"; -const VALID_TYPES = ["STRING", "combo", "number", "toggle", "BOOLEAN"]; -const CONFIG = Symbol(); -const GET_CONFIG = Symbol(); -const TARGET = Symbol(); // Used for reroutes to specify the real target widget - -export function getWidgetConfig(slot) { - return slot.widget[CONFIG] ?? slot.widget[GET_CONFIG](); -} - -function getConfig(widgetName) { - const { nodeData } = this.constructor; - return nodeData?.input?.required[widgetName] ?? nodeData?.input?.optional?.[widgetName]; -} - -function isConvertableWidget(widget, config) { - return (VALID_TYPES.includes(widget.type) || VALID_TYPES.includes(config[0])) && !widget.options?.forceInput; -} - -function hideWidget(node, widget, suffix = "") { - if (widget.type?.startsWith(CONVERTED_TYPE)) return; - widget.origType = widget.type; - widget.origComputeSize = widget.computeSize; - widget.origSerializeValue = widget.serializeValue; - widget.computeSize = () => [0, -4]; // -4 is due to the gap litegraph adds between widgets automatically - widget.type = CONVERTED_TYPE + suffix; - widget.serializeValue = () => { - // Prevent serializing the widget if we have no input linked - if (!node.inputs) { - return undefined; - } - let node_input = node.inputs.find((i) => i.widget?.name === widget.name); - - if (!node_input || !node_input.link) { - return undefined; - } - return widget.origSerializeValue ? widget.origSerializeValue() : widget.value; - }; - - // Hide any linked widgets, e.g. seed+seedControl - if (widget.linkedWidgets) { - for (const w of widget.linkedWidgets) { - hideWidget(node, w, ":" + widget.name); - } - } -} - -function showWidget(widget) { - widget.type = widget.origType; - widget.computeSize = widget.origComputeSize; - widget.serializeValue = widget.origSerializeValue; - - delete widget.origType; - delete widget.origComputeSize; - delete widget.origSerializeValue; - - // Hide any linked widgets, e.g. seed+seedControl - if (widget.linkedWidgets) { - for (const w of widget.linkedWidgets) { - showWidget(w); - } - } -} - -function convertToInput(node, widget, config) { - hideWidget(node, widget); - - const { type } = getWidgetType(config); - - // Add input and store widget config for creating on primitive node - const sz = node.size; - node.addInput(widget.name, type, { - widget: { name: widget.name, [GET_CONFIG]: () => config }, - }); - - for (const widget of node.widgets) { - widget.last_y += LiteGraph.NODE_SLOT_HEIGHT; - } - - // Restore original size but grow if needed - node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])]); -} - -function convertToWidget(node, widget) { - showWidget(widget); - const sz = node.size; - node.removeInput(node.inputs.findIndex((i) => i.widget?.name === widget.name)); - - for (const widget of node.widgets) { - widget.last_y -= LiteGraph.NODE_SLOT_HEIGHT; - } - - // Restore original size but grow if needed - node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])]); -} - -function getWidgetType(config) { - // Special handling for COMBO so we restrict links based on the entries - let type = config[0]; - if (type instanceof Array) { - type = "COMBO"; - } - return { type }; -} - -function isValidCombo(combo, obj) { - // New input isnt a combo - if (!(obj instanceof Array)) { - console.log(`connection rejected: tried to connect combo to ${obj}`); - return false; - } - // New imput combo has a different size - if (combo.length !== obj.length) { - console.log(`connection rejected: combo lists dont match`); - return false; - } - // New input combo has different elements - if (combo.find((v, i) => obj[i] !== v)) { - console.log(`connection rejected: combo lists dont match`); - return false; - } - - return true; -} - -export function setWidgetConfig(slot, config, target) { - if (!slot.widget) return; - if (config) { - slot.widget[GET_CONFIG] = () => config; - slot.widget[TARGET] = target; - } else { - delete slot.widget; - } - - if (slot.link) { - const link = app.graph.links[slot.link]; - if (link) { - const originNode = app.graph.getNodeById(link.origin_id); - if (originNode.type === "PrimitiveNode") { - if (config) { - originNode.recreateWidget(); - } else if(!app.configuringGraph) { - originNode.disconnectOutput(0); - originNode.onLastDisconnect(); - } - } - } - } -} - -export function mergeIfValid(output, config2, forceUpdate, recreateWidget, config1) { - if (!config1) { - config1 = output.widget[CONFIG] ?? output.widget[GET_CONFIG](); - } - - if (config1[0] instanceof Array) { - if (!isValidCombo(config1[0], config2[0])) return false; - } else if (config1[0] !== config2[0]) { - // Types dont match - console.log(`connection rejected: types dont match`, config1[0], config2[0]); - return false; - } - - const keys = new Set([...Object.keys(config1[1] ?? {}), ...Object.keys(config2[1] ?? {})]); - - let customConfig; - const getCustomConfig = () => { - if (!customConfig) { - if (typeof structuredClone === "undefined") { - customConfig = JSON.parse(JSON.stringify(config1[1] ?? {})); - } else { - customConfig = structuredClone(config1[1] ?? {}); - } - } - return customConfig; - }; - - const isNumber = config1[0] === "INT" || config1[0] === "FLOAT"; - for (const k of keys.values()) { - if (k !== "default" && k !== "forceInput" && k !== "defaultInput" && k !== "control_after_generate" && k !== "multiline" && k !== "tooltip") { - let v1 = config1[1][k]; - let v2 = config2[1]?.[k]; - - if (v1 === v2 || (!v1 && !v2)) continue; - - if (isNumber) { - if (k === "min") { - const theirMax = config2[1]?.["max"]; - if (theirMax != null && v1 > theirMax) { - console.log("connection rejected: min > max", v1, theirMax); - return false; - } - getCustomConfig()[k] = v1 == null ? v2 : v2 == null ? v1 : Math.max(v1, v2); - continue; - } else if (k === "max") { - const theirMin = config2[1]?.["min"]; - if (theirMin != null && v1 < theirMin) { - console.log("connection rejected: max < min", v1, theirMin); - return false; - } - getCustomConfig()[k] = v1 == null ? v2 : v2 == null ? v1 : Math.min(v1, v2); - continue; - } else if (k === "step") { - let step; - if (v1 == null) { - // No current step - step = v2; - } else if (v2 == null) { - // No new step - step = v1; - } else { - if (v1 < v2) { - // Ensure v1 is larger for the mod - const a = v2; - v2 = v1; - v1 = a; - } - if (v1 % v2) { - console.log("connection rejected: steps not divisible", "current:", v1, "new:", v2); - return false; - } - - step = v1; - } - - getCustomConfig()[k] = step; - continue; - } - } - - console.log(`connection rejected: config ${k} values dont match`, v1, v2); - return false; - } - } - - if (customConfig || forceUpdate) { - if (customConfig) { - output.widget[CONFIG] = [config1[0], customConfig]; - } - - const widget = recreateWidget?.call(this); - // When deleting a node this can be null - if (widget) { - const min = widget.options.min; - const max = widget.options.max; - if (min != null && widget.value < min) widget.value = min; - if (max != null && widget.value > max) widget.value = max; - widget.callback(widget.value); - } - } - - return { customConfig }; -} - -let useConversionSubmenusSetting; -app.registerExtension({ - name: "Comfy.WidgetInputs", - init() { - useConversionSubmenusSetting = app.ui.settings.addSetting({ - id: "Comfy.NodeInputConversionSubmenus", - name: "Node widget/input conversion sub-menus", - tooltip: "In the node context menu, place the entries that convert between input/widget in sub-menus.", - type: "boolean", - defaultValue: true, - }); - }, - async beforeRegisterNodeDef(nodeType, nodeData, app) { - // Add menu options to conver to/from widgets - const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; - nodeType.prototype.convertWidgetToInput = function (widget) { - const config = getConfig.call(this, widget.name) ?? [widget.type, widget.options || {}]; - if (!isConvertableWidget(widget, config)) return false; - convertToInput(this, widget, config); - return true; - }; - nodeType.prototype.getExtraMenuOptions = function (_, options) { - const r = origGetExtraMenuOptions ? origGetExtraMenuOptions.apply(this, arguments) : undefined; - - if (this.widgets) { - let toInput = []; - let toWidget = []; - for (const w of this.widgets) { - if (w.options?.forceInput) { - continue; - } - if (w.type === CONVERTED_TYPE) { - toWidget.push({ - content: `Convert ${w.name} to widget`, - callback: () => convertToWidget(this, w), - }); - } else { - const config = getConfig.call(this, w.name) ?? [w.type, w.options || {}]; - if (isConvertableWidget(w, config)) { - toInput.push({ - content: `Convert ${w.name} to input`, - callback: () => convertToInput(this, w, config), - }); - } - } - } - - //Convert.. main menu - if (toInput.length) { - if (useConversionSubmenusSetting.value) { - options.push({ - content: "Convert Widget to Input", - submenu: { - options: toInput, - }, - }); - } else { - options.push(...toInput, null); - } - } - if (toWidget.length) { - if (useConversionSubmenusSetting.value) { - options.push({ - content: "Convert Input to Widget", - submenu: { - options: toWidget, - }, - }); - } else { - options.push(...toWidget, null); - } - } - } - - return r; - }; - - nodeType.prototype.onGraphConfigured = function () { - if (!this.inputs) return; - - for (const input of this.inputs) { - if (input.widget) { - if (!input.widget[GET_CONFIG]) { - input.widget[GET_CONFIG] = () => getConfig.call(this, input.widget.name); - } - - // Cleanup old widget config - if (input.widget.config) { - if (input.widget.config[0] instanceof Array) { - // If we are an old converted combo then replace the input type and the stored link data - input.type = "COMBO"; - - const link = app.graph.links[input.link]; - if (link) { - link.type = input.type; - } - } - delete input.widget.config; - } - - const w = this.widgets.find((w) => w.name === input.widget.name); - if (w) { - hideWidget(this, w); - } else { - convertToWidget(this, input); - } - } - } - }; - - const origOnNodeCreated = nodeType.prototype.onNodeCreated; - nodeType.prototype.onNodeCreated = function () { - const r = origOnNodeCreated ? origOnNodeCreated.apply(this) : undefined; - - // When node is created, convert any force/default inputs - if (!app.configuringGraph && this.widgets) { - for (const w of this.widgets) { - if (w?.options?.forceInput || w?.options?.defaultInput) { - const config = getConfig.call(this, w.name) ?? [w.type, w.options || {}]; - convertToInput(this, w, config); - } - } - } - - return r; - }; - - const origOnConfigure = nodeType.prototype.onConfigure; - nodeType.prototype.onConfigure = function () { - const r = origOnConfigure ? origOnConfigure.apply(this, arguments) : undefined; - if (!app.configuringGraph && this.inputs) { - // On copy + paste of nodes, ensure that widget configs are set up - for (const input of this.inputs) { - if (input.widget && !input.widget[GET_CONFIG]) { - input.widget[GET_CONFIG] = () => getConfig.call(this, input.widget.name); - const w = this.widgets.find((w) => w.name === input.widget.name); - if (w) { - hideWidget(this, w); - } - } - } - } - - return r; - }; - - function isNodeAtPos(pos) { - for (const n of app.graph._nodes) { - if (n.pos[0] === pos[0] && n.pos[1] === pos[1]) { - return true; - } - } - return false; - } - - // Double click a widget input to automatically attach a primitive - const origOnInputDblClick = nodeType.prototype.onInputDblClick; - const ignoreDblClick = Symbol(); - nodeType.prototype.onInputDblClick = function (slot) { - const r = origOnInputDblClick ? origOnInputDblClick.apply(this, arguments) : undefined; - - const input = this.inputs[slot]; - if (!input.widget || !input[ignoreDblClick]) { - // Not a widget input or already handled input - if (!(input.type in ComfyWidgets) && !(input.widget[GET_CONFIG]?.()?.[0] instanceof Array)) { - return r; //also Not a ComfyWidgets input or combo (do nothing) - } - } - - // Create a primitive node - const node = LiteGraph.createNode("PrimitiveNode"); - app.graph.add(node); - - // Calculate a position that wont directly overlap another node - const pos = [this.pos[0] - node.size[0] - 30, this.pos[1]]; - while (isNodeAtPos(pos)) { - pos[1] += LiteGraph.NODE_TITLE_HEIGHT; - } - - node.pos = pos; - node.connect(0, this, slot); - node.title = input.name; - - // Prevent adding duplicates due to triple clicking - input[ignoreDblClick] = true; - setTimeout(() => { - delete input[ignoreDblClick]; - }, 300); - - return r; - }; - - // Prevent connecting COMBO lists to converted inputs that dont match types - const onConnectInput = nodeType.prototype.onConnectInput; - nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) { - const v = onConnectInput?.(this, arguments); - // Not a combo, ignore - if (type !== "COMBO") return v; - // Primitive output, allow that to handle - if (originNode.outputs[originSlot].widget) return v; - - // Ensure target is also a combo - const targetCombo = this.inputs[targetSlot].widget?.[GET_CONFIG]?.()?.[0]; - if (!targetCombo || !(targetCombo instanceof Array)) return v; - - // Check they match - const originConfig = originNode.constructor?.nodeData?.output?.[originSlot]; - if (!originConfig || !isValidCombo(targetCombo, originConfig)) { - return false; - } - - return v; - }; - }, - registerCustomNodes() { - const replacePropertyName = "Run widget replace on values"; - class PrimitiveNode { - constructor() { - this.addOutput("connect to widget input", "*"); - this.serialize_widgets = true; - this.isVirtualNode = true; - - if (!this.properties || !(replacePropertyName in this.properties)) { - this.addProperty(replacePropertyName, false, "boolean"); - } - } - - applyToGraph(extraLinks = []) { - if (!this.outputs[0].links?.length) return; - - function get_links(node) { - let links = []; - for (const l of node.outputs[0].links) { - const linkInfo = app.graph.links[l]; - const n = node.graph.getNodeById(linkInfo.target_id); - if (n.type == "Reroute") { - links = links.concat(get_links(n)); - } else { - links.push(l); - } - } - return links; - } - - let links = [...get_links(this).map((l) => app.graph.links[l]), ...extraLinks]; - let v = this.widgets?.[0].value; - if(v && this.properties[replacePropertyName]) { - v = applyTextReplacements(app, v); - } - - // For each output link copy our value over the original widget value - for (const linkInfo of links) { - const node = this.graph.getNodeById(linkInfo.target_id); - const input = node.inputs[linkInfo.target_slot]; - let widget; - if (input.widget[TARGET]) { - widget = input.widget[TARGET]; - } else { - const widgetName = input.widget.name; - if (widgetName) { - widget = node.widgets.find((w) => w.name === widgetName); - } - } - - if (widget) { - widget.value = v; - if (widget.callback) { - widget.callback(widget.value, app.canvas, node, app.canvas.graph_mouse, {}); - } - } - } - } - - refreshComboInNode() { - const widget = this.widgets?.[0]; - if (widget?.type === "combo") { - widget.options.values = this.outputs[0].widget[GET_CONFIG]()[0]; - - if (!widget.options.values.includes(widget.value)) { - widget.value = widget.options.values[0]; - widget.callback(widget.value); - } - } - } - - onAfterGraphConfigured() { - if (this.outputs[0].links?.length && !this.widgets?.length) { - if (!this.#onFirstConnection()) return; - - // Populate widget values from config data - if (this.widgets) { - for (let i = 0; i < this.widgets_values.length; i++) { - const w = this.widgets[i]; - if (w) { - w.value = this.widgets_values[i]; - } - } - } - - // Merge values if required - this.#mergeWidgetConfig(); - } - } - - onConnectionsChange(_, index, connected) { - if (app.configuringGraph) { - // Dont run while the graph is still setting up - return; - } - - const links = this.outputs[0].links; - if (connected) { - if (links?.length && !this.widgets?.length) { - this.#onFirstConnection(); - } - } else { - // We may have removed a link that caused the constraints to change - this.#mergeWidgetConfig(); - - if (!links?.length) { - this.onLastDisconnect(); - } - } - } - - onConnectOutput(slot, type, input, target_node, target_slot) { - // Fires before the link is made allowing us to reject it if it isn't valid - // No widget, we cant connect - if (!input.widget) { - if (!(input.type in ComfyWidgets)) return false; - } - - if (this.outputs[slot].links?.length) { - const valid = this.#isValidConnection(input); - if (valid) { - // On connect of additional outputs, copy our value to their widget - this.applyToGraph([{ target_id: target_node.id, target_slot }]); - } - return valid; - } - } - - #onFirstConnection(recreating) { - // First connection can fire before the graph is ready on initial load so random things can be missing - if (!this.outputs[0].links) { - this.onLastDisconnect(); - return; - } - const linkId = this.outputs[0].links[0]; - const link = this.graph.links[linkId]; - if (!link) return; - - const theirNode = this.graph.getNodeById(link.target_id); - if (!theirNode || !theirNode.inputs) return; - - const input = theirNode.inputs[link.target_slot]; - if (!input) return; - - let widget; - if (!input.widget) { - if (!(input.type in ComfyWidgets)) return; - widget = { name: input.name, [GET_CONFIG]: () => [input.type, {}] }; //fake widget - } else { - widget = input.widget; - } - - const config = widget[GET_CONFIG]?.(); - if (!config) return; - - const { type } = getWidgetType(config); - // Update our output to restrict to the widget type - this.outputs[0].type = type; - this.outputs[0].name = type; - this.outputs[0].widget = widget; - - this.#createWidget(widget[CONFIG] ?? config, theirNode, widget.name, recreating, widget[TARGET]); - } - - #createWidget(inputData, node, widgetName, recreating, targetWidget) { - let type = inputData[0]; - - if (type instanceof Array) { - type = "COMBO"; - } - - let widget; - if (type in ComfyWidgets) { - widget = (ComfyWidgets[type](this, "value", inputData, app) || {}).widget; - } else { - widget = this.addWidget(type, "value", null, () => {}, {}); - } - - if (targetWidget) { - widget.value = targetWidget.value; - } else if (node?.widgets && widget) { - const theirWidget = node.widgets.find((w) => w.name === widgetName); - if (theirWidget) { - widget.value = theirWidget.value; - } - } - - if (!inputData?.[1]?.control_after_generate && (widget.type === "number" || widget.type === "combo")) { - let control_value = this.widgets_values?.[1]; - if (!control_value) { - control_value = "fixed"; - } - addValueControlWidgets(this, widget, control_value, undefined, inputData); - let filter = this.widgets_values?.[2]; - if (filter && this.widgets.length === 3) { - this.widgets[2].value = filter; - } - } - - // Restore any saved control values - const controlValues = this.controlValues; - if(this.lastType === this.widgets[0].type && controlValues?.length === this.widgets.length - 1) { - for(let i = 0; i < controlValues.length; i++) { - this.widgets[i + 1].value = controlValues[i]; - } - } - - // When our value changes, update other widgets to reflect our changes - // e.g. so LoadImage shows correct image - const callback = widget.callback; - const self = this; - widget.callback = function () { - const r = callback ? callback.apply(this, arguments) : undefined; - self.applyToGraph(); - return r; - }; - - if (!recreating) { - // Grow our node if required - const sz = this.computeSize(); - if (this.size[0] < sz[0]) { - this.size[0] = sz[0]; - } - if (this.size[1] < sz[1]) { - this.size[1] = sz[1]; - } - - requestAnimationFrame(() => { - if (this.onResize) { - this.onResize(this.size); - } - }); - } - } - - recreateWidget() { - const values = this.widgets?.map((w) => w.value); - this.#removeWidgets(); - this.#onFirstConnection(true); - if (values?.length) { - for (let i = 0; i < this.widgets?.length; i++) this.widgets[i].value = values[i]; - } - return this.widgets?.[0]; - } - - #mergeWidgetConfig() { - // Merge widget configs if the node has multiple outputs - const output = this.outputs[0]; - const links = output.links; - - const hasConfig = !!output.widget[CONFIG]; - if (hasConfig) { - delete output.widget[CONFIG]; - } - - if (links?.length < 2 && hasConfig) { - // Copy the widget options from the source - if (links.length) { - this.recreateWidget(); - } - - return; - } - - const config1 = output.widget[GET_CONFIG](); - const isNumber = config1[0] === "INT" || config1[0] === "FLOAT"; - if (!isNumber) return; - - for (const linkId of links) { - const link = app.graph.links[linkId]; - if (!link) continue; // Can be null when removing a node - - const theirNode = app.graph.getNodeById(link.target_id); - const theirInput = theirNode.inputs[link.target_slot]; - - // Call is valid connection so it can merge the configs when validating - this.#isValidConnection(theirInput, hasConfig); - } - } - - #isValidConnection(input, forceUpdate) { - // Only allow connections where the configs match - const output = this.outputs[0]; - const config2 = input.widget[GET_CONFIG](); - return !!mergeIfValid.call(this, output, config2, forceUpdate, this.recreateWidget); - } - - #removeWidgets() { - if (this.widgets) { - // Allow widgets to cleanup - for (const w of this.widgets) { - if (w.onRemove) { - w.onRemove(); - } - } - - // Temporarily store the current values in case the node is being recreated - // e.g. by group node conversion - this.controlValues = []; - this.lastType = this.widgets[0]?.type; - for(let i = 1; i < this.widgets.length; i++) { - this.controlValues.push(this.widgets[i].value); - } - setTimeout(() => { delete this.lastType; delete this.controlValues }, 15); - this.widgets.length = 0; - } - } - - onLastDisconnect() { - // We cant remove + re-add the output here as if you drag a link over the same link - // it removes, then re-adds, causing it to break - this.outputs[0].type = "*"; - this.outputs[0].name = "connect to widget input"; - delete this.outputs[0].widget; - - this.#removeWidgets(); - } - } - - LiteGraph.registerNodeType( - "PrimitiveNode", - Object.assign(PrimitiveNode, { - title: "Primitive", - }) - ); - PrimitiveNode.category = "utils"; - }, -}); diff --git a/web/extensions/logging.js.example b/web/extensions/logging.js.example deleted file mode 100644 index d015096a2..000000000 --- a/web/extensions/logging.js.example +++ /dev/null @@ -1,55 +0,0 @@ -import { app } from "../scripts/app.js"; - -const ext = { - // Unique name for the extension - name: "Example.LoggingExtension", - async init(app) { - // Any initial setup to run as soon as the page loads - console.log("[logging]", "extension init"); - }, - async setup(app) { - // Any setup to run after the app is created - console.log("[logging]", "extension setup"); - }, - async addCustomNodeDefs(defs, app) { - // Add custom node definitions - // These definitions will be configured and registered automatically - // defs is a lookup core nodes, add yours into this - console.log("[logging]", "add custom node definitions", "current nodes:", Object.keys(defs)); - }, - async getCustomWidgets(app) { - // Return custom widget types - // See ComfyWidgets for widget examples - console.log("[logging]", "provide custom widgets"); - }, - async beforeRegisterNodeDef(nodeType, nodeData, app) { - // Run custom logic before a node definition is registered with the graph - console.log("[logging]", "before register node: ", nodeType, nodeData); - - // This fires for every node definition so only log once - delete ext.beforeRegisterNodeDef; - }, - async registerCustomNodes(app) { - // Register any custom node implementations here allowing for more flexability than a custom node def - console.log("[logging]", "register custom nodes"); - }, - loadedGraphNode(node, app) { - // Fires for each node when loading/dragging/etc a workflow json or png - // If you break something in the backend and want to patch workflows in the frontend - // This is the place to do this - console.log("[logging]", "loaded graph node: ", node); - - // This fires for every node on each load so only log once - delete ext.loadedGraphNode; - }, - nodeCreated(node, app) { - // Fires every time a node is constructed - // You can modify widgets/add handlers/etc here - console.log("[logging]", "node created: ", node); - - // This fires for every node so only log once - delete ext.nodeCreated; - } -}; - -app.registerExtension(ext); diff --git a/web/fonts/materialdesignicons-webfont.woff2 b/web/fonts/materialdesignicons-webfont.woff2 deleted file mode 100644 index 8c69b85f666bf3996d7e3194623bf53e774c44a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 403216 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

A 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

~kC(4S|_D1W! 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%0A?P6XIbsbH%^bgaA!ca7le#O1f9ZxwdH9s;d7b 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 diff --git a/web/index.html b/web/index.html deleted file mode 100644 index 5ebf03c01..000000000 --- a/web/index.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - ComfyUI - - - - - - - - - - -

- - diff --git a/web/jsconfig.json b/web/jsconfig.json deleted file mode 100644 index cd55a0cc7..000000000 --- a/web/jsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "/*": ["./*"] - }, - "lib": ["DOM", "ES2022", "DOM.Iterable"], - "target": "ES2015", - "module": "es2020" - }, - "include": ["."] -} diff --git a/web/lib/litegraph.core.js b/web/lib/litegraph.core.js deleted file mode 100644 index 427a62b59..000000000 --- a/web/lib/litegraph.core.js +++ /dev/null @@ -1,14424 +0,0 @@ -//packer version - - -(function(global) { - // ************************************************************* - // LiteGraph CLASS ******* - // ************************************************************* - - /** - * The Global Scope. It contains all the registered node classes. - * - * @class LiteGraph - * @constructor - */ - - var LiteGraph = (global.LiteGraph = { - VERSION: 0.4, - - CANVAS_GRID_SIZE: 10, - - NODE_TITLE_HEIGHT: 30, - NODE_TITLE_TEXT_Y: 20, - NODE_SLOT_HEIGHT: 20, - NODE_WIDGET_HEIGHT: 20, - NODE_WIDTH: 140, - NODE_MIN_WIDTH: 50, - NODE_COLLAPSED_RADIUS: 10, - NODE_COLLAPSED_WIDTH: 80, - NODE_TITLE_COLOR: "#999", - NODE_SELECTED_TITLE_COLOR: "#FFF", - NODE_TEXT_SIZE: 14, - NODE_TEXT_COLOR: "#AAA", - NODE_SUBTEXT_SIZE: 12, - NODE_DEFAULT_COLOR: "#333", - NODE_DEFAULT_BGCOLOR: "#353535", - NODE_DEFAULT_BOXCOLOR: "#666", - NODE_DEFAULT_SHAPE: "box", - NODE_BOX_OUTLINE_COLOR: "#FFF", - DEFAULT_SHADOW_COLOR: "rgba(0,0,0,0.5)", - DEFAULT_GROUP_FONT: 24, - - WIDGET_BGCOLOR: "#222", - WIDGET_OUTLINE_COLOR: "#666", - WIDGET_TEXT_COLOR: "#DDD", - WIDGET_SECONDARY_TEXT_COLOR: "#999", - - LINK_COLOR: "#9A9", - EVENT_LINK_COLOR: "#A86", - CONNECTING_LINK_COLOR: "#AFA", - - MAX_NUMBER_OF_NODES: 10000, //avoid infinite loops - DEFAULT_POSITION: [100, 100], //default node position - VALID_SHAPES: ["default", "box", "round", "card"], //,"circle" - - //shapes are used for nodes but also for slots - BOX_SHAPE: 1, - ROUND_SHAPE: 2, - CIRCLE_SHAPE: 3, - CARD_SHAPE: 4, - ARROW_SHAPE: 5, - GRID_SHAPE: 6, // intended for slot arrays - - //enums - INPUT: 1, - OUTPUT: 2, - - EVENT: -1, //for outputs - ACTION: -1, //for inputs - - NODE_MODES: ["Always", "On Event", "Never", "On Trigger"], // helper, will add "On Request" and more in the future - NODE_MODES_COLORS:["#666","#422","#333","#224","#626"], // use with node_box_coloured_by_mode - ALWAYS: 0, - ON_EVENT: 1, - NEVER: 2, - ON_TRIGGER: 3, - - UP: 1, - DOWN: 2, - LEFT: 3, - RIGHT: 4, - CENTER: 5, - - LINK_RENDER_MODES: ["Straight", "Linear", "Spline"], // helper - STRAIGHT_LINK: 0, - LINEAR_LINK: 1, - SPLINE_LINK: 2, - - NORMAL_TITLE: 0, - NO_TITLE: 1, - TRANSPARENT_TITLE: 2, - AUTOHIDE_TITLE: 3, - VERTICAL_LAYOUT: "vertical", // arrange nodes vertically - - proxy: null, //used to redirect calls - node_images_path: "", - - debug: false, - catch_exceptions: true, - throw_errors: true, - allow_scripts: false, //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits - registered_node_types: {}, //nodetypes by string - node_types_by_file_extension: {}, //used for dropping files in the canvas - Nodes: {}, //node types by classname - Globals: {}, //used to store vars between graphs - - searchbox_extras: {}, //used to add extra features to the search box - auto_sort_node_types: false, // [true!] If set to true, will automatically sort node types / categories in the context menus - - node_box_coloured_when_on: false, // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback - node_box_coloured_by_mode: false, // [true!] nodebox based on node mode, visual feedback - - dialog_close_on_mouse_leave: false, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false - dialog_close_on_mouse_leave_delay: 500, - - shift_click_do_break_link_from: false, // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys - click_do_break_link_to: false, // [false!]prefer false, way too easy to break links - - search_hide_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false - search_filter_enabled: false, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out] - search_show_all_on_open: true, // [true!] opens the results list when opening the search widget - - auto_load_slot_types: false, // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out] - - // set these values if not using auto_load_slot_types - registered_slot_in_types: {}, // slot types for nodeclass - registered_slot_out_types: {}, // slot types for nodeclass - slot_types_in: [], // slot types IN - slot_types_out: [], // slot types OUT - slot_types_default_in: [], // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search - slot_types_default_out: [], // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search - - alt_drag_do_clone_nodes: false, // [true!] very handy, ALT click to clone and drag the new node - - do_add_triggers_slots: false, // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this - - allow_multi_output_for_events: true, // [false!] being events, it is strongly reccomended to use them sequentially, one by one - - middle_click_slot_add_default_node: false, //[true!] allows to create and connect a ndoe clicking with the third button (wheel) - - release_link_on_empty_shows_menu: false, //[true!] dragging a link to empty space will open a menu, add from list, search or defaults - - pointerevents_method: "pointer", // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) - // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary) - - ctrl_shift_v_paste_connect_unselected_outputs: true, //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes - - // if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers. - // use this if you must have node IDs that are unique across all graphs and subgraphs. - use_uuids: false, - - /** - * Register a node class so it can be listed when the user wants to create a new one - * @method registerNodeType - * @param {String} type name of the node and path - * @param {Class} base_class class containing the structure of a node - */ - - registerNodeType: function(type, base_class) { - if (!base_class.prototype) { - throw "Cannot register a simple object, it must be a class with a prototype"; - } - base_class.type = type; - - if (LiteGraph.debug) { - console.log("Node registered: " + type); - } - - const classname = base_class.name; - - const pos = type.lastIndexOf("/"); - base_class.category = type.substring(0, pos); - - if (!base_class.title) { - base_class.title = classname; - } - - //extend class - for (var i in LGraphNode.prototype) { - if (!base_class.prototype[i]) { - base_class.prototype[i] = LGraphNode.prototype[i]; - } - } - - const prev = this.registered_node_types[type]; - if(prev) { - console.log("replacing node type: " + type); - } - if( !Object.prototype.hasOwnProperty.call( base_class.prototype, "shape") ) { - Object.defineProperty(base_class.prototype, "shape", { - set: function(v) { - switch (v) { - case "default": - delete this._shape; - break; - case "box": - this._shape = LiteGraph.BOX_SHAPE; - break; - case "round": - this._shape = LiteGraph.ROUND_SHAPE; - break; - case "circle": - this._shape = LiteGraph.CIRCLE_SHAPE; - break; - case "card": - this._shape = LiteGraph.CARD_SHAPE; - break; - default: - this._shape = v; - } - }, - get: function() { - return this._shape; - }, - enumerable: true, - configurable: true - }); - - - //used to know which nodes to create when dragging files to the canvas - if (base_class.supported_extensions) { - for (let i in base_class.supported_extensions) { - const ext = base_class.supported_extensions[i]; - if(ext && ext.constructor === String) { - this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class; - } - } - } - } - - this.registered_node_types[type] = base_class; - if (base_class.constructor.name) { - this.Nodes[classname] = base_class; - } - if (LiteGraph.onNodeTypeRegistered) { - LiteGraph.onNodeTypeRegistered(type, base_class); - } - if (prev && LiteGraph.onNodeTypeReplaced) { - LiteGraph.onNodeTypeReplaced(type, base_class, prev); - } - - //warnings - if (base_class.prototype.onPropertyChange) { - console.warn( - "LiteGraph node class " + - type + - " has onPropertyChange method, it must be called onPropertyChanged with d at the end" - ); - } - - // TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types - if (this.auto_load_slot_types) { - new base_class(base_class.title || "tmpnode"); - } - }, - - /** - * removes a node type from the system - * @method unregisterNodeType - * @param {String|Object} type name of the node or the node constructor itself - */ - unregisterNodeType: function(type) { - const base_class = - type.constructor === String - ? this.registered_node_types[type] - : type; - if (!base_class) { - throw "node type not found: " + type; - } - delete this.registered_node_types[base_class.type]; - if (base_class.constructor.name) { - delete this.Nodes[base_class.constructor.name]; - } - }, - - /** - * Save a slot type and his node - * @method registerSlotType - * @param {String|Object} type name of the node or the node constructor itself - * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, .. - */ - registerNodeAndSlotType: function(type, slot_type, out){ - out = out || false; - const base_class = - type.constructor === String && - this.registered_node_types[type] !== "anonymous" - ? this.registered_node_types[type] - : type; - - const class_type = base_class.constructor.type; - - let allTypes = []; - if (typeof slot_type === "string") { - allTypes = slot_type.split(","); - } else if (slot_type == this.EVENT || slot_type == this.ACTION) { - allTypes = ["_event_"]; - } else { - allTypes = ["*"]; - } - - for (let i = 0; i < allTypes.length; ++i) { - let slotType = allTypes[i]; - if (slotType === "") { - slotType = "*"; - } - const registerTo = out - ? "registered_slot_out_types" - : "registered_slot_in_types"; - if (this[registerTo][slotType] === undefined) { - this[registerTo][slotType] = { nodes: [] }; - } - if (!this[registerTo][slotType].nodes.includes(class_type)) { - this[registerTo][slotType].nodes.push(class_type); - } - - // check if is a new type - if (!out) { - if (!this.slot_types_in.includes(slotType.toLowerCase())) { - this.slot_types_in.push(slotType.toLowerCase()); - this.slot_types_in.sort(); - } - } else { - if (!this.slot_types_out.includes(slotType.toLowerCase())) { - this.slot_types_out.push(slotType.toLowerCase()); - this.slot_types_out.sort(); - } - } - } - }, - - /** - * Create a new nodetype by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. - * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. - * @method wrapFunctionAsNode - * @param {String} name node name with namespace (p.e.: 'math/sum') - * @param {Function} func - * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type - * @param {String} return_type [optional] string with the return type, otherwise it will be generic - * @param {Object} properties [optional] properties to be configurable - */ - wrapFunctionAsNode: function( - name, - func, - param_types, - return_type, - properties - ) { - var params = Array(func.length); - var code = ""; - var names = LiteGraph.getParameterNames(func); - for (var i = 0; i < names.length; ++i) { - code += - "this.addInput('" + - names[i] + - "'," + - (param_types && param_types[i] - ? "'" + param_types[i] + "'" - : "0") + - ");\n"; - } - code += - "this.addOutput('out'," + - (return_type ? "'" + return_type + "'" : 0) + - ");\n"; - if (properties) { - code += - "this.properties = " + JSON.stringify(properties) + ";\n"; - } - var classobj = Function(code); - classobj.title = name.split("/").pop(); - classobj.desc = "Generated from " + func.name; - classobj.prototype.onExecute = function onExecute() { - for (var i = 0; i < params.length; ++i) { - params[i] = this.getInputData(i); - } - var r = func.apply(this, params); - this.setOutputData(0, r); - }; - this.registerNodeType(name, classobj); - }, - - /** - * Removes all previously registered node's types - */ - clearRegisteredTypes: function() { - this.registered_node_types = {}; - this.node_types_by_file_extension = {}; - this.Nodes = {}; - this.searchbox_extras = {}; - }, - - /** - * Adds this method to all nodetypes, existing and to be created - * (You can add it to LGraphNode.prototype but then existing node types wont have it) - * @method addNodeMethod - * @param {Function} func - */ - addNodeMethod: function(name, func) { - LGraphNode.prototype[name] = func; - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; - if (type.prototype[name]) { - type.prototype["_" + name] = type.prototype[name]; - } //keep old in case of replacing - type.prototype[name] = func; - } - }, - - /** - * Create a node of a given type with a name. The node is not attached to any graph yet. - * @method createNode - * @param {String} type full name of the node class. p.e. "math/sin" - * @param {String} name a name to distinguish from other nodes - * @param {Object} options to set options - */ - - createNode: function(type, title, options) { - var base_class = this.registered_node_types[type]; - if (!base_class) { - if (LiteGraph.debug) { - console.log( - 'GraphNode type "' + type + '" not registered.' - ); - } - return null; - } - - var prototype = base_class.prototype || base_class; - - title = title || base_class.title || type; - - var node = null; - - if (LiteGraph.catch_exceptions) { - try { - node = new base_class(title); - } catch (err) { - console.error(err); - return null; - } - } else { - node = new base_class(title); - } - - node.type = type; - - if (!node.title && title) { - node.title = title; - } - if (!node.properties) { - node.properties = {}; - } - if (!node.properties_info) { - node.properties_info = []; - } - if (!node.flags) { - node.flags = {}; - } - if (!node.size) { - node.size = node.computeSize(); - //call onresize? - } - if (!node.pos) { - node.pos = LiteGraph.DEFAULT_POSITION.concat(); - } - if (!node.mode) { - node.mode = LiteGraph.ALWAYS; - } - - //extra options - if (options) { - for (var i in options) { - node[i] = options[i]; - } - } - - // callback - if ( node.onNodeCreated ) { - node.onNodeCreated(); - } - - return node; - }, - - /** - * Returns a registered node type with a given name - * @method getNodeType - * @param {String} type full name of the node class. p.e. "math/sin" - * @return {Class} the node class - */ - getNodeType: function(type) { - return this.registered_node_types[type]; - }, - - /** - * Returns a list of node types matching one category - * @method getNodeType - * @param {String} category category name - * @return {Array} array with all the node classes - */ - - getNodeTypesInCategory: function(category, filter) { - var r = []; - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; - if (type.filter != filter) { - continue; - } - - if (category == "") { - if (type.category == null) { - r.push(type); - } - } else if (type.category == category) { - r.push(type); - } - } - - if (this.auto_sort_node_types) { - r.sort(function(a,b){return a.title.localeCompare(b.title)}); - } - - return r; - }, - - /** - * Returns a list with all the node type categories - * @method getNodeTypesCategories - * @param {String} filter only nodes with ctor.filter equal can be shown - * @return {Array} array with all the names of the categories - */ - getNodeTypesCategories: function( filter ) { - var categories = { "": 1 }; - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; - if ( type.category && !type.skip_list ) - { - if(type.filter != filter) - continue; - categories[type.category] = 1; - } - } - var result = []; - for (var i in categories) { - result.push(i); - } - return this.auto_sort_node_types ? result.sort() : result; - }, - - //debug purposes: reloads all the js scripts that matches a wildcard - reloadNodes: function(folder_wildcard) { - var tmp = document.getElementsByTagName("script"); - //weird, this array changes by its own, so we use a copy - var script_files = []; - for (var i=0; i < tmp.length; i++) { - script_files.push(tmp[i]); - } - - var docHeadObj = document.getElementsByTagName("head")[0]; - folder_wildcard = document.location.href + folder_wildcard; - - for (var i=0; i < script_files.length; i++) { - var src = script_files[i].src; - if ( - !src || - src.substr(0, folder_wildcard.length) != folder_wildcard - ) { - continue; - } - - try { - if (LiteGraph.debug) { - console.log("Reloading: " + src); - } - var dynamicScript = document.createElement("script"); - dynamicScript.type = "text/javascript"; - dynamicScript.src = src; - docHeadObj.appendChild(dynamicScript); - docHeadObj.removeChild(script_files[i]); - } catch (err) { - if (LiteGraph.throw_errors) { - throw err; - } - if (LiteGraph.debug) { - console.log("Error while reloading " + src); - } - } - } - - if (LiteGraph.debug) { - console.log("Nodes reloaded"); - } - }, - - //separated just to improve if it doesn't work - cloneObject: function(obj, target) { - if (obj == null) { - return null; - } - var r = JSON.parse(JSON.stringify(obj)); - if (!target) { - return r; - } - - for (var i in r) { - target[i] = r[i]; - } - return target; - }, - - /* - * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670 - */ - uuidv4: function() { - return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,a=>(a^Math.random()*16>>a/4).toString(16)); - }, - - /** - * Returns if the types of two slots are compatible (taking into account wildcards, etc) - * @method isValidConnection - * @param {String} type_a - * @param {String} type_b - * @return {Boolean} true if they can be connected - */ - isValidConnection: function(type_a, type_b) { - if (type_a=="" || type_a==="*") type_a = 0; - if (type_b=="" || type_b==="*") type_b = 0; - if ( - !type_a //generic output - || !type_b // generic input - || type_a == type_b //same type (is valid for triggers) - || (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION) - ) { - return true; - } - - // Enforce string type to handle toLowerCase call (-1 number not ok) - type_a = String(type_a); - type_b = String(type_b); - type_a = type_a.toLowerCase(); - type_b = type_b.toLowerCase(); - - // For nodes supporting multiple connection types - if (type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1) { - return type_a == type_b; - } - - // Check all permutations to see if one is valid - var supported_types_a = type_a.split(","); - var supported_types_b = type_b.split(","); - for (var i = 0; i < supported_types_a.length; ++i) { - for (var j = 0; j < supported_types_b.length; ++j) { - if(this.isValidConnection(supported_types_a[i],supported_types_b[j])){ - //if (supported_types_a[i] == supported_types_b[j]) { - return true; - } - } - } - - return false; - }, - - /** - * Register a string in the search box so when the user types it it will recommend this node - * @method registerSearchboxExtra - * @param {String} node_type the node recommended - * @param {String} description text to show next to it - * @param {Object} data it could contain info of how the node should be configured - * @return {Boolean} true if they can be connected - */ - registerSearchboxExtra: function(node_type, description, data) { - this.searchbox_extras[description.toLowerCase()] = { - type: node_type, - desc: description, - data: data - }; - }, - - /** - * Wrapper to load files (from url using fetch or from file using FileReader) - * @method fetchFile - * @param {String|File|Blob} url the url of the file (or the file itself) - * @param {String} type an string to know how to fetch it: "text","arraybuffer","json","blob" - * @param {Function} on_complete callback(data) - * @param {Function} on_error in case of an error - * @return {FileReader|Promise} returns the object used to - */ - fetchFile: function( url, type, on_complete, on_error ) { - var that = this; - if(!url) - return null; - - type = type || "text"; - if( url.constructor === String ) - { - if (url.substr(0, 4) == "http" && LiteGraph.proxy) { - url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); - } - return fetch(url) - .then(function(response) { - if(!response.ok) - throw new Error("File not found"); //it will be catch below - if(type == "arraybuffer") - return response.arrayBuffer(); - else if(type == "text" || type == "string") - return response.text(); - else if(type == "json") - return response.json(); - else if(type == "blob") - return response.blob(); - }) - .then(function(data) { - if(on_complete) - on_complete(data); - }) - .catch(function(error) { - console.error("error fetching file:",url); - if(on_error) - on_error(error); - }); - } - else if( url.constructor === File || url.constructor === Blob) - { - var reader = new FileReader(); - reader.onload = function(e) - { - var v = e.target.result; - if( type == "json" ) - v = JSON.parse(v); - if(on_complete) - on_complete(v); - } - if(type == "arraybuffer") - return reader.readAsArrayBuffer(url); - else if(type == "text" || type == "json") - return reader.readAsText(url); - else if(type == "blob") - return reader.readAsBinaryString(url); - } - return null; - } - }); - - //timer that works everywhere - if (typeof performance != "undefined") { - LiteGraph.getTime = performance.now.bind(performance); - } else if (typeof Date != "undefined" && Date.now) { - LiteGraph.getTime = Date.now.bind(Date); - } else if (typeof process != "undefined") { - LiteGraph.getTime = function() { - var t = process.hrtime(); - return t[0] * 0.001 + t[1] * 1e-6; - }; - } else { - LiteGraph.getTime = function getTime() { - return new Date().getTime(); - }; - } - - //********************************************************************************* - // LGraph CLASS - //********************************************************************************* - - /** - * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop. - * supported callbacks: - + onNodeAdded: when a new node is added to the graph - + onNodeRemoved: when a node inside this graph is removed - + onNodeConnectionChange: some connection has changed in the graph (connected or disconnected) - * - * @class LGraph - * @constructor - * @param {Object} o data from previous serialization [optional] - */ - - function LGraph(o) { - if (LiteGraph.debug) { - console.log("Graph created"); - } - this.list_of_graphcanvas = null; - this.clear(); - - if (o) { - this.configure(o); - } - } - - global.LGraph = LiteGraph.LGraph = LGraph; - - //default supported types - LGraph.supported_types = ["number", "string", "boolean"]; - - //used to know which types of connections support this graph (some graphs do not allow certain types) - LGraph.prototype.getSupportedTypes = function() { - return this.supported_types || LGraph.supported_types; - }; - - LGraph.STATUS_STOPPED = 1; - LGraph.STATUS_RUNNING = 2; - - /** - * Removes all nodes from this graph - * @method clear - */ - - LGraph.prototype.clear = function() { - this.stop(); - this.status = LGraph.STATUS_STOPPED; - - this.last_node_id = 0; - this.last_link_id = 0; - - this._version = -1; //used to detect changes - - //safe clear - if (this._nodes) { - for (var i = 0; i < this._nodes.length; ++i) { - var node = this._nodes[i]; - if (node.onRemoved) { - node.onRemoved(); - } - } - } - - //nodes - this._nodes = []; - this._nodes_by_id = {}; - this._nodes_in_order = []; //nodes sorted in execution order - this._nodes_executable = null; //nodes that contain onExecute sorted in execution order - - //other scene stuff - this._groups = []; - - //links - this.links = {}; //container with all the links - - //iterations - this.iteration = 0; - - //custom data - this.config = {}; - this.vars = {}; - this.extra = {}; //to store custom data - - //timing - this.globaltime = 0; - this.runningtime = 0; - this.fixedtime = 0; - this.fixedtime_lapse = 0.01; - this.elapsed_time = 0.01; - this.last_update_time = 0; - this.starttime = 0; - - this.catch_errors = true; - - this.nodes_executing = []; - this.nodes_actioning = []; - this.nodes_executedAction = []; - - //subgraph_data - this.inputs = {}; - this.outputs = {}; - - //notify canvas to redraw - this.change(); - - this.sendActionToCanvas("clear"); - }; - - /** - * Attach Canvas to this graph - * @method attachCanvas - * @param {GraphCanvas} graph_canvas - */ - - LGraph.prototype.attachCanvas = function(graphcanvas) { - if (graphcanvas.constructor != LGraphCanvas) { - throw "attachCanvas expects a LGraphCanvas instance"; - } - if (graphcanvas.graph && graphcanvas.graph != this) { - graphcanvas.graph.detachCanvas(graphcanvas); - } - - graphcanvas.graph = this; - - if (!this.list_of_graphcanvas) { - this.list_of_graphcanvas = []; - } - this.list_of_graphcanvas.push(graphcanvas); - }; - - /** - * Detach Canvas from this graph - * @method detachCanvas - * @param {GraphCanvas} graph_canvas - */ - LGraph.prototype.detachCanvas = function(graphcanvas) { - if (!this.list_of_graphcanvas) { - return; - } - - var pos = this.list_of_graphcanvas.indexOf(graphcanvas); - if (pos == -1) { - return; - } - graphcanvas.graph = null; - this.list_of_graphcanvas.splice(pos, 1); - }; - - /** - * Starts running this graph every interval milliseconds. - * @method start - * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate - */ - - LGraph.prototype.start = function(interval) { - if (this.status == LGraph.STATUS_RUNNING) { - return; - } - this.status = LGraph.STATUS_RUNNING; - - if (this.onPlayEvent) { - this.onPlayEvent(); - } - - this.sendEventToAllNodes("onStart"); - - //launch - this.starttime = LiteGraph.getTime(); - this.last_update_time = this.starttime; - interval = interval || 0; - var that = this; - - //execute once per frame - if ( interval == 0 && typeof window != "undefined" && window.requestAnimationFrame ) { - function on_frame() { - if (that.execution_timer_id != -1) { - return; - } - window.requestAnimationFrame(on_frame); - if(that.onBeforeStep) - that.onBeforeStep(); - that.runStep(1, !that.catch_errors); - if(that.onAfterStep) - that.onAfterStep(); - } - this.execution_timer_id = -1; - on_frame(); - } else { //execute every 'interval' ms - this.execution_timer_id = setInterval(function() { - //execute - if(that.onBeforeStep) - that.onBeforeStep(); - that.runStep(1, !that.catch_errors); - if(that.onAfterStep) - that.onAfterStep(); - }, interval); - } - }; - - /** - * Stops the execution loop of the graph - * @method stop execution - */ - - LGraph.prototype.stop = function() { - if (this.status == LGraph.STATUS_STOPPED) { - return; - } - - this.status = LGraph.STATUS_STOPPED; - - if (this.onStopEvent) { - this.onStopEvent(); - } - - if (this.execution_timer_id != null) { - if (this.execution_timer_id != -1) { - clearInterval(this.execution_timer_id); - } - this.execution_timer_id = null; - } - - this.sendEventToAllNodes("onStop"); - }; - - /** - * Run N steps (cycles) of the graph - * @method runStep - * @param {number} num number of steps to run, default is 1 - * @param {Boolean} do_not_catch_errors [optional] if you want to try/catch errors - * @param {number} limit max number of nodes to execute (used to execute from start to a node) - */ - - LGraph.prototype.runStep = function(num, do_not_catch_errors, limit ) { - num = num || 1; - - var start = LiteGraph.getTime(); - this.globaltime = 0.001 * (start - this.starttime); - - var nodes = this._nodes_executable - ? this._nodes_executable - : this._nodes; - if (!nodes) { - return; - } - - limit = limit || nodes.length; - - if (do_not_catch_errors) { - //iterations - for (var i = 0; i < num; i++) { - for (var j = 0; j < limit; ++j) { - var node = nodes[j]; - if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - //wrap node.onExecute(); - node.doExecute(); - } - } - - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - - if (this.onAfterExecute) { - this.onAfterExecute(); - } - } else { - try { - //iterations - for (var i = 0; i < num; i++) { - for (var j = 0; j < limit; ++j) { - var node = nodes[j]; - if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - node.onExecute(); - } - } - - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - - if (this.onAfterExecute) { - this.onAfterExecute(); - } - this.errors_in_execution = false; - } catch (err) { - this.errors_in_execution = true; - if (LiteGraph.throw_errors) { - throw err; - } - if (LiteGraph.debug) { - console.log("Error during execution: " + err); - } - this.stop(); - } - } - - var now = LiteGraph.getTime(); - var elapsed = now - start; - if (elapsed == 0) { - elapsed = 1; - } - this.execution_time = 0.001 * elapsed; - this.globaltime += 0.001 * elapsed; - this.iteration += 1; - this.elapsed_time = (now - this.last_update_time) * 0.001; - this.last_update_time = now; - this.nodes_executing = []; - this.nodes_actioning = []; - this.nodes_executedAction = []; - }; - - /** - * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than - * nodes with only inputs. - * @method updateExecutionOrder - */ - LGraph.prototype.updateExecutionOrder = function() { - this._nodes_in_order = this.computeExecutionOrder(false); - this._nodes_executable = []; - for (var i = 0; i < this._nodes_in_order.length; ++i) { - if (this._nodes_in_order[i].onExecute) { - this._nodes_executable.push(this._nodes_in_order[i]); - } - } - }; - - //This is more internal, it computes the executable nodes in order and returns it - LGraph.prototype.computeExecutionOrder = function( - only_onExecute, - set_level - ) { - var L = []; - var S = []; - var M = {}; - var visited_links = {}; //to avoid repeating links - var remaining_links = {}; //to a - - //search for the nodes without inputs (starting nodes) - for (var i = 0, l = this._nodes.length; i < l; ++i) { - var node = this._nodes[i]; - if (only_onExecute && !node.onExecute) { - continue; - } - - M[node.id] = node; //add to pending nodes - - var num = 0; //num of input connections - if (node.inputs) { - for (var j = 0, l2 = node.inputs.length; j < l2; j++) { - if (node.inputs[j] && node.inputs[j].link != null) { - num += 1; - } - } - } - - if (num == 0) { - //is a starting node - S.push(node); - if (set_level) { - node._level = 1; - } - } //num of input links - else { - if (set_level) { - node._level = 0; - } - remaining_links[node.id] = num; - } - } - - while (true) { - if (S.length == 0) { - break; - } - - //get an starting node - var node = S.shift(); - L.push(node); //add to ordered list - delete M[node.id]; //remove from the pending nodes - - if (!node.outputs) { - continue; - } - - //for every output - for (var i = 0; i < node.outputs.length; i++) { - var output = node.outputs[i]; - //not connected - if ( - output == null || - output.links == null || - output.links.length == 0 - ) { - continue; - } - - //for every connection - for (var j = 0; j < output.links.length; j++) { - var link_id = output.links[j]; - var link = this.links[link_id]; - if (!link) { - continue; - } - - //already visited link (ignore it) - if (visited_links[link.id]) { - continue; - } - - var target_node = this.getNodeById(link.target_id); - if (target_node == null) { - visited_links[link.id] = true; - continue; - } - - if ( - set_level && - (!target_node._level || - target_node._level <= node._level) - ) { - target_node._level = node._level + 1; - } - - visited_links[link.id] = true; //mark as visited - remaining_links[target_node.id] -= 1; //reduce the number of links remaining - if (remaining_links[target_node.id] == 0) { - S.push(target_node); - } //if no more links, then add to starters array - } - } - } - - //the remaining ones (loops) - for (var i in M) { - L.push(M[i]); - } - - if (L.length != this._nodes.length && LiteGraph.debug) { - console.warn("something went wrong, nodes missing"); - } - - var l = L.length; - - //save order number in the node - for (var i = 0; i < l; ++i) { - L[i].order = i; - } - - //sort now by priority - L = L.sort(function(A, B) { - var Ap = A.constructor.priority || A.priority || 0; - var Bp = B.constructor.priority || B.priority || 0; - if (Ap == Bp) { - //if same priority, sort by order - return A.order - B.order; - } - return Ap - Bp; //sort by priority - }); - - //save order number in the node, again... - for (var i = 0; i < l; ++i) { - L[i].order = i; - } - - return L; - }; - - /** - * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. - * It doesn't include the node itself - * @method getAncestors - * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution - */ - LGraph.prototype.getAncestors = function(node) { - var ancestors = []; - var pending = [node]; - var visited = {}; - - while (pending.length) { - var current = pending.shift(); - if (!current.inputs) { - continue; - } - if (!visited[current.id] && current != node) { - visited[current.id] = true; - ancestors.push(current); - } - - for (var i = 0; i < current.inputs.length; ++i) { - var input = current.getInputNode(i); - if (input && ancestors.indexOf(input) == -1) { - pending.push(input); - } - } - } - - ancestors.sort(function(a, b) { - return a.order - b.order; - }); - return ancestors; - }; - - /** - * Positions every node in a more readable manner - * @method arrange - */ - LGraph.prototype.arrange = function (margin, layout) { - margin = margin || 100; - - const nodes = this.computeExecutionOrder(false, true); - const columns = []; - for (let i = 0; i < nodes.length; ++i) { - const node = nodes[i]; - const col = node._level || 1; - if (!columns[col]) { - columns[col] = []; - } - columns[col].push(node); - } - - let x = margin; - - for (let i = 0; i < columns.length; ++i) { - const column = columns[i]; - if (!column) { - continue; - } - let max_size = 100; - let y = margin + LiteGraph.NODE_TITLE_HEIGHT; - for (let j = 0; j < column.length; ++j) { - const node = column[j]; - node.pos[0] = (layout == LiteGraph.VERTICAL_LAYOUT) ? y : x; - node.pos[1] = (layout == LiteGraph.VERTICAL_LAYOUT) ? x : y; - const max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0; - if (node.size[max_size_index] > max_size) { - max_size = node.size[max_size_index]; - } - const node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1; - y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT; - } - x += max_size + margin; - } - - this.setDirtyCanvas(true, true); - }; - - /** - * Returns the amount of time the graph has been running in milliseconds - * @method getTime - * @return {number} number of milliseconds the graph has been running - */ - LGraph.prototype.getTime = function() { - return this.globaltime; - }; - - /** - * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant - * @method getFixedTime - * @return {number} number of milliseconds the graph has been running - */ - - LGraph.prototype.getFixedTime = function() { - return this.fixedtime; - }; - - /** - * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct - * if the nodes are using graphical actions - * @method getElapsedTime - * @return {number} number of milliseconds it took the last cycle - */ - - LGraph.prototype.getElapsedTime = function() { - return this.elapsed_time; - }; - - /** - * Sends an event to all the nodes, useful to trigger stuff - * @method sendEventToAllNodes - * @param {String} eventname the name of the event (function to be called) - * @param {Array} params parameters in array format - */ - LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) { - mode = mode || LiteGraph.ALWAYS; - - var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; - if (!nodes) { - return; - } - - for (var j = 0, l = nodes.length; j < l; ++j) { - var node = nodes[j]; - - if ( - node.constructor === LiteGraph.Subgraph && - eventname != "onExecute" - ) { - if (node.mode == mode) { - node.sendEventToAllNodes(eventname, params, mode); - } - continue; - } - - if (!node[eventname] || node.mode != mode) { - continue; - } - if (params === undefined) { - node[eventname](); - } else if (params && params.constructor === Array) { - node[eventname].apply(node, params); - } else { - node[eventname](params); - } - } - }; - - LGraph.prototype.sendActionToCanvas = function(action, params) { - if (!this.list_of_graphcanvas) { - return; - } - - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var c = this.list_of_graphcanvas[i]; - if (c[action]) { - c[action].apply(c, params); - } - } - }; - - /** - * Adds a new node instance to this graph - * @method add - * @param {LGraphNode} node the instance of the node - */ - - LGraph.prototype.add = function(node, skip_compute_order) { - if (!node) { - return; - } - - //groups - if (node.constructor === LGraphGroup) { - this._groups.push(node); - this.setDirtyCanvas(true); - this.change(); - node.graph = this; - this._version++; - return; - } - - //nodes - if (node.id != -1 && this._nodes_by_id[node.id] != null) { - console.warn( - "LiteGraph: there is already a node with this ID, changing it" - ); - if (LiteGraph.use_uuids) { - node.id = LiteGraph.uuidv4(); - } - else { - node.id = ++this.last_node_id; - } - } - - if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) { - throw "LiteGraph: max number of nodes in a graph reached"; - } - - //give him an id - if (LiteGraph.use_uuids) { - if (node.id == null || node.id == -1) - node.id = LiteGraph.uuidv4(); - } - else { - if (node.id == null || node.id == -1) { - node.id = ++this.last_node_id; - } else if (this.last_node_id < node.id) { - this.last_node_id = node.id; - } - } - - node.graph = this; - this._version++; - - this._nodes.push(node); - this._nodes_by_id[node.id] = node; - - if (node.onAdded) { - node.onAdded(this); - } - - if (this.config.align_to_grid) { - node.alignToGrid(); - } - - if (!skip_compute_order) { - this.updateExecutionOrder(); - } - - if (this.onNodeAdded) { - this.onNodeAdded(node); - } - - this.setDirtyCanvas(true); - this.change(); - - return node; //to chain actions - }; - - /** - * Removes a node from the graph - * @method remove - * @param {LGraphNode} node the instance of the node - */ - - LGraph.prototype.remove = function(node) { - if (node.constructor === LiteGraph.LGraphGroup) { - var index = this._groups.indexOf(node); - if (index != -1) { - this._groups.splice(index, 1); - } - node.graph = null; - this._version++; - this.setDirtyCanvas(true, true); - this.change(); - return; - } - - if (this._nodes_by_id[node.id] == null) { - return; - } //not found - - if (node.ignore_remove) { - return; - } //cannot be removed - - this.beforeChange(); //sure? - almost sure is wrong - - //disconnect inputs - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - if (slot.link != null) { - node.disconnectInput(i); - } - } - } - - //disconnect outputs - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - if (slot.links != null && slot.links.length) { - node.disconnectOutput(i); - } - } - } - - //node.id = -1; //why? - - //callback - if (node.onRemoved) { - node.onRemoved(); - } - - node.graph = null; - this._version++; - - //remove from canvas render - if (this.list_of_graphcanvas) { - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var canvas = this.list_of_graphcanvas[i]; - if (canvas.selected_nodes[node.id]) { - delete canvas.selected_nodes[node.id]; - } - if (canvas.node_dragged == node) { - canvas.node_dragged = null; - } - } - } - - //remove from containers - var pos = this._nodes.indexOf(node); - if (pos != -1) { - this._nodes.splice(pos, 1); - } - delete this._nodes_by_id[node.id]; - - if (this.onNodeRemoved) { - this.onNodeRemoved(node); - } - - //close panels - this.sendActionToCanvas("checkPanels"); - - this.setDirtyCanvas(true, true); - this.afterChange(); //sure? - almost sure is wrong - this.change(); - - this.updateExecutionOrder(); - }; - - /** - * Returns a node by its id. - * @method getNodeById - * @param {Number} id - */ - - LGraph.prototype.getNodeById = function(id) { - if (id == null) { - return null; - } - return this._nodes_by_id[id]; - }; - - /** - * Returns a list of nodes that matches a class - * @method findNodesByClass - * @param {Class} classObject the class itself (not an string) - * @return {Array} a list with all the nodes of this type - */ - LGraph.prototype.findNodesByClass = function(classObject, result) { - result = result || []; - result.length = 0; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].constructor === classObject) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns a list of nodes that matches a type - * @method findNodesByType - * @param {String} type the name of the node type - * @return {Array} a list with all the nodes of this type - */ - LGraph.prototype.findNodesByType = function(type, result) { - var type = type.toLowerCase(); - result = result || []; - result.length = 0; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].type.toLowerCase() == type) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns the first node that matches a name in its title - * @method findNodeByTitle - * @param {String} name the name of the node to search - * @return {Node} the node or null - */ - LGraph.prototype.findNodeByTitle = function(title) { - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].title == title) { - return this._nodes[i]; - } - } - return null; - }; - - /** - * Returns a list of nodes that matches a name - * @method findNodesByTitle - * @param {String} name the name of the node to search - * @return {Array} a list with all the nodes with this name - */ - LGraph.prototype.findNodesByTitle = function(title) { - var result = []; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].title == title) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns the top-most node in this position of the canvas - * @method getNodeOnPos - * @param {number} x the x coordinate in canvas space - * @param {number} y the y coordinate in canvas space - * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph - * @return {LGraphNode} the node at this position or null - */ - LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) { - nodes_list = nodes_list || this._nodes; - var nRet = null; - for (var i = nodes_list.length - 1; i >= 0; i--) { - var n = nodes_list[i]; - var skip_title = n.constructor.title_mode == LiteGraph.NO_TITLE; - if (n.isPointInside(x, y, margin, skip_title)) { - // check for lesser interest nodes (TODO check for overlapping, use the top) - /*if (typeof n == "LGraphGroup"){ - nRet = n; - }else{*/ - return n; - /*}*/ - } - } - return nRet; - }; - - /** - * Returns the top-most group in that position - * @method getGroupOnPos - * @param {number} x the x coordinate in canvas space - * @param {number} y the y coordinate in canvas space - * @return {LGraphGroup} the group or null - */ - LGraph.prototype.getGroupOnPos = function(x, y) { - for (var i = this._groups.length - 1; i >= 0; i--) { - var g = this._groups[i]; - if (g.isPointInside(x, y, 2, true)) { - return g; - } - } - return null; - }; - - /** - * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution - * this replaces the ones using the old version with the new version - * @method checkNodeTypes - */ - LGraph.prototype.checkNodeTypes = function() { - var changes = false; - for (var i = 0; i < this._nodes.length; i++) { - var node = this._nodes[i]; - var ctor = LiteGraph.registered_node_types[node.type]; - if (node.constructor == ctor) { - continue; - } - console.log("node being replaced by newer version: " + node.type); - var newnode = LiteGraph.createNode(node.type); - changes = true; - this._nodes[i] = newnode; - newnode.configure(node.serialize()); - newnode.graph = this; - this._nodes_by_id[newnode.id] = newnode; - if (node.inputs) { - newnode.inputs = node.inputs.concat(); - } - if (node.outputs) { - newnode.outputs = node.outputs.concat(); - } - } - this.updateExecutionOrder(); - }; - - // ********** GLOBALS ***************** - - LGraph.prototype.onAction = function(action, param, options) { - this._input_nodes = this.findNodesByClass( - LiteGraph.GraphInput, - this._input_nodes - ); - for (var i = 0; i < this._input_nodes.length; ++i) { - var node = this._input_nodes[i]; - if (node.properties.name != action) { - continue; - } - //wrap node.onAction(action, param); - node.actionDo(action, param, options); - break; - } - }; - - LGraph.prototype.trigger = function(action, param) { - if (this.onTrigger) { - this.onTrigger(action, param); - } - }; - - /** - * Tell this graph it has a global graph input of this type - * @method addGlobalInput - * @param {String} name - * @param {String} type - * @param {*} value [optional] - */ - LGraph.prototype.addInput = function(name, type, value) { - var input = this.inputs[name]; - if (input) { - //already exist - return; - } - - this.beforeChange(); - this.inputs[name] = { name: name, type: type, value: value }; - this._version++; - this.afterChange(); - - if (this.onInputAdded) { - this.onInputAdded(name, type); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Assign a data to the global graph input - * @method setGlobalInputData - * @param {String} name - * @param {*} data - */ - LGraph.prototype.setInputData = function(name, data) { - var input = this.inputs[name]; - if (!input) { - return; - } - input.value = data; - }; - - /** - * Returns the current value of a global graph input - * @method getInputData - * @param {String} name - * @return {*} the data - */ - LGraph.prototype.getInputData = function(name) { - var input = this.inputs[name]; - if (!input) { - return null; - } - return input.value; - }; - - /** - * Changes the name of a global graph input - * @method renameInput - * @param {String} old_name - * @param {String} new_name - */ - LGraph.prototype.renameInput = function(old_name, name) { - if (name == old_name) { - return; - } - - if (!this.inputs[old_name]) { - return false; - } - - if (this.inputs[name]) { - console.error("there is already one input with that name"); - return false; - } - - this.inputs[name] = this.inputs[old_name]; - delete this.inputs[old_name]; - this._version++; - - if (this.onInputRenamed) { - this.onInputRenamed(old_name, name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Changes the type of a global graph input - * @method changeInputType - * @param {String} name - * @param {String} type - */ - LGraph.prototype.changeInputType = function(name, type) { - if (!this.inputs[name]) { - return false; - } - - if ( - this.inputs[name].type && - String(this.inputs[name].type).toLowerCase() == - String(type).toLowerCase() - ) { - return; - } - - this.inputs[name].type = type; - this._version++; - if (this.onInputTypeChanged) { - this.onInputTypeChanged(name, type); - } - }; - - /** - * Removes a global graph input - * @method removeInput - * @param {String} name - * @param {String} type - */ - LGraph.prototype.removeInput = function(name) { - if (!this.inputs[name]) { - return false; - } - - delete this.inputs[name]; - this._version++; - - if (this.onInputRemoved) { - this.onInputRemoved(name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return true; - }; - - /** - * Creates a global graph output - * @method addOutput - * @param {String} name - * @param {String} type - * @param {*} value - */ - LGraph.prototype.addOutput = function(name, type, value) { - this.outputs[name] = { name: name, type: type, value: value }; - this._version++; - - if (this.onOutputAdded) { - this.onOutputAdded(name, type); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Assign a data to the global output - * @method setOutputData - * @param {String} name - * @param {String} value - */ - LGraph.prototype.setOutputData = function(name, value) { - var output = this.outputs[name]; - if (!output) { - return; - } - output.value = value; - }; - - /** - * Returns the current value of a global graph output - * @method getOutputData - * @param {String} name - * @return {*} the data - */ - LGraph.prototype.getOutputData = function(name) { - var output = this.outputs[name]; - if (!output) { - return null; - } - return output.value; - }; - - /** - * Renames a global graph output - * @method renameOutput - * @param {String} old_name - * @param {String} new_name - */ - LGraph.prototype.renameOutput = function(old_name, name) { - if (!this.outputs[old_name]) { - return false; - } - - if (this.outputs[name]) { - console.error("there is already one output with that name"); - return false; - } - - this.outputs[name] = this.outputs[old_name]; - delete this.outputs[old_name]; - this._version++; - - if (this.onOutputRenamed) { - this.onOutputRenamed(old_name, name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Changes the type of a global graph output - * @method changeOutputType - * @param {String} name - * @param {String} type - */ - LGraph.prototype.changeOutputType = function(name, type) { - if (!this.outputs[name]) { - return false; - } - - if ( - this.outputs[name].type && - String(this.outputs[name].type).toLowerCase() == - String(type).toLowerCase() - ) { - return; - } - - this.outputs[name].type = type; - this._version++; - if (this.onOutputTypeChanged) { - this.onOutputTypeChanged(name, type); - } - }; - - /** - * Removes a global graph output - * @method removeOutput - * @param {String} name - */ - LGraph.prototype.removeOutput = function(name) { - if (!this.outputs[name]) { - return false; - } - delete this.outputs[name]; - this._version++; - - if (this.onOutputRemoved) { - this.onOutputRemoved(name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return true; - }; - - LGraph.prototype.triggerInput = function(name, value) { - var nodes = this.findNodesByTitle(name); - for (var i = 0; i < nodes.length; ++i) { - nodes[i].onTrigger(value); - } - }; - - LGraph.prototype.setCallback = function(name, func) { - var nodes = this.findNodesByTitle(name); - for (var i = 0; i < nodes.length; ++i) { - nodes[i].setTrigger(func); - } - }; - - //used for undo, called before any change is made to the graph - LGraph.prototype.beforeChange = function(info) { - if (this.onBeforeChange) { - this.onBeforeChange(this,info); - } - this.sendActionToCanvas("onBeforeChange", this); - }; - - //used to resend actions, called after any change is made to the graph - LGraph.prototype.afterChange = function(info) { - if (this.onAfterChange) { - this.onAfterChange(this,info); - } - this.sendActionToCanvas("onAfterChange", this); - }; - - LGraph.prototype.connectionChange = function(node, link_info) { - this.updateExecutionOrder(); - if (this.onConnectionChange) { - this.onConnectionChange(node); - } - this._version++; - this.sendActionToCanvas("onConnectionChange"); - }; - - /** - * returns if the graph is in live mode - * @method isLive - */ - - LGraph.prototype.isLive = function() { - if (!this.list_of_graphcanvas) { - return false; - } - - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var c = this.list_of_graphcanvas[i]; - if (c.live_mode) { - return true; - } - } - return false; - }; - - /** - * clears the triggered slot animation in all links (stop visual animation) - * @method clearTriggeredSlots - */ - LGraph.prototype.clearTriggeredSlots = function() { - for (var i in this.links) { - var link_info = this.links[i]; - if (!link_info) { - continue; - } - if (link_info._last_time) { - link_info._last_time = 0; - } - } - }; - - /* Called when something visually changed (not the graph!) */ - LGraph.prototype.change = function() { - if (LiteGraph.debug) { - console.log("Graph changed"); - } - this.sendActionToCanvas("setDirty", [true, true]); - if (this.on_change) { - this.on_change(this); - } - }; - - LGraph.prototype.setDirtyCanvas = function(fg, bg) { - this.sendActionToCanvas("setDirty", [fg, bg]); - }; - - /** - * Destroys a link - * @method removeLink - * @param {Number} link_id - */ - LGraph.prototype.removeLink = function(link_id) { - var link = this.links[link_id]; - if (!link) { - return; - } - var node = this.getNodeById(link.target_id); - if (node) { - node.disconnectInput(link.target_slot); - } - }; - - //save and recover app state *************************************** - /** - * Creates a Object containing all the info about this graph, it can be serialized - * @method serialize - * @return {Object} value of the node - */ - LGraph.prototype.serialize = function() { - var nodes_info = []; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - nodes_info.push(this._nodes[i].serialize()); - } - - //pack link info into a non-verbose format - var links = []; - for (var i in this.links) { - //links is an OBJECT - var link = this.links[i]; - if (!link.serialize) { - //weird bug I havent solved yet - console.warn( - "weird LLink bug, link info is not a LLink but a regular object" - ); - var link2 = new LLink(); - for (var j in link) { - link2[j] = link[j]; - } - this.links[i] = link2; - link = link2; - } - - links.push(link.serialize()); - } - - var groups_info = []; - for (var i = 0; i < this._groups.length; ++i) { - groups_info.push(this._groups[i].serialize()); - } - - var data = { - last_node_id: this.last_node_id, - last_link_id: this.last_link_id, - nodes: nodes_info, - links: links, - groups: groups_info, - config: this.config, - extra: this.extra, - version: LiteGraph.VERSION - }; - - if(this.onSerialize) - this.onSerialize(data); - - return data; - }; - - /** - * Configure a graph from a JSON string - * @method configure - * @param {String} str configure a graph from a JSON string - * @param {Boolean} returns if there was any error parsing - */ - LGraph.prototype.configure = function(data, keep_old) { - if (!data) { - return; - } - - if (!keep_old) { - this.clear(); - } - - var nodes = data.nodes; - - //decode links info (they are very verbose) - if (data.links && data.links.constructor === Array) { - var links = []; - for (var i = 0; i < data.links.length; ++i) { - var link_data = data.links[i]; - if(!link_data) //weird bug - { - console.warn("serialized graph link data contains errors, skipping."); - continue; - } - var link = new LLink(); - link.configure(link_data); - links[link.id] = link; - } - data.links = links; - } - - //copy all stored fields - for (var i in data) { - if(i == "nodes" || i == "groups" ) //links must be accepted - continue; - this[i] = data[i]; - } - - var error = false; - - //create nodes - this._nodes = []; - if (nodes) { - for (var i = 0, l = nodes.length; i < l; ++i) { - var n_info = nodes[i]; //stored info - var node = LiteGraph.createNode(n_info.type, n_info.title); - if (!node) { - if (LiteGraph.debug) { - console.log( - "Node not found or has errors: " + n_info.type - ); - } - - //in case of error we create a replacement node to avoid losing info - node = new LGraphNode(); - node.last_serialization = n_info; - node.has_errors = true; - error = true; - //continue; - } - - node.id = n_info.id; //id it or it will create a new id - this.add(node, true); //add before configure, otherwise configure cannot create links - } - - //configure nodes afterwards so they can reach each other - for (var i = 0, l = nodes.length; i < l; ++i) { - var n_info = nodes[i]; - var node = this.getNodeById(n_info.id); - if (node) { - node.configure(n_info); - } - } - } - - //groups - this._groups.length = 0; - if (data.groups) { - for (var i = 0; i < data.groups.length; ++i) { - var group = new LiteGraph.LGraphGroup(); - group.configure(data.groups[i]); - this.add(group); - } - } - - this.updateExecutionOrder(); - - this.extra = data.extra || {}; - - if(this.onConfigure) - this.onConfigure(data); - - this._version++; - this.setDirtyCanvas(true, true); - return error; - }; - - LGraph.prototype.load = function(url, callback) { - var that = this; - - //from file - if(url.constructor === File || url.constructor === Blob) - { - var reader = new FileReader(); - reader.addEventListener('load', function(event) { - var data = JSON.parse(event.target.result); - that.configure(data); - if(callback) - callback(); - }); - - reader.readAsText(url); - return; - } - - //is a string, then an URL - var req = new XMLHttpRequest(); - req.open("GET", url, true); - req.send(null); - req.onload = function(oEvent) { - if (req.status !== 200) { - console.error("Error loading graph:", req.status, req.response); - return; - } - var data = JSON.parse( req.response ); - that.configure(data); - if(callback) - callback(); - }; - req.onerror = function(err) { - console.error("Error loading graph:", err); - }; - }; - - LGraph.prototype.onNodeTrace = function(node, msg, color) { - //TODO - }; - - //this is the class in charge of storing link information - function LLink(id, type, origin_id, origin_slot, target_id, target_slot) { - this.id = id; - this.type = type; - this.origin_id = origin_id; - this.origin_slot = origin_slot; - this.target_id = target_id; - this.target_slot = target_slot; - - this._data = null; - this._pos = new Float32Array(2); //center - } - - LLink.prototype.configure = function(o) { - if (o.constructor === Array) { - this.id = o[0]; - this.origin_id = o[1]; - this.origin_slot = o[2]; - this.target_id = o[3]; - this.target_slot = o[4]; - this.type = o[5]; - } else { - this.id = o.id; - this.type = o.type; - this.origin_id = o.origin_id; - this.origin_slot = o.origin_slot; - this.target_id = o.target_id; - this.target_slot = o.target_slot; - } - }; - - LLink.prototype.serialize = function() { - return [ - this.id, - this.origin_id, - this.origin_slot, - this.target_id, - this.target_slot, - this.type - ]; - }; - - LiteGraph.LLink = LLink; - - // ************************************************************* - // Node CLASS ******* - // ************************************************************* - - /* - title: string - pos: [x,y] - size: [x,y] - - input|output: every connection - + { name:string, type:string, pos: [x,y]=Optional, direction: "input"|"output", links: Array }); - - general properties: - + clip_area: if you render outside the node, it will be clipped - + unsafe_execution: not allowed for safe execution - + skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected - + resizable: if set to false it wont be resizable with the mouse - + horizontal: slots are distributed horizontally - + widgets_start_y: widgets start at y distance from the top of the node - - flags object: - + collapsed: if it is collapsed - - supported callbacks: - + onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading) - + onRemoved: when removed from graph - + onStart: when the graph starts playing - + onStop: when the graph stops playing - + onDrawForeground: render the inside widgets inside the node - + onDrawBackground: render the background area inside the node (only in edit mode) - + onMouseDown - + onMouseMove - + onMouseUp - + onMouseEnter - + onMouseLeave - + onExecute: execute the node - + onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour) - + onGetInputs: returns an array of possible inputs - + onGetOutputs: returns an array of possible outputs - + onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h]) - + onDblClick: double clicked in the node - + onInputDblClick: input slot double clicked (can be used to automatically create a node connected) - + onOutputDblClick: output slot double clicked (can be used to automatically create a node connected) - + onConfigure: called after the node has been configured - + onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data) - + onSelected - + onDeselected - + onDropItem : DOM item dropped over the node - + onDropFile : file dropped over the node - + onConnectInput : if returns false the incoming connection will be canceled - + onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info ) - + onAction: action slot triggered - + getExtraMenuOptions: to add option to context menu -*/ - - /** - * Base Class for all the node type classes - * @class LGraphNode - * @param {String} name a name for the node - */ - - function LGraphNode(title) { - this._ctor(title); - } - - global.LGraphNode = LiteGraph.LGraphNode = LGraphNode; - - LGraphNode.prototype._ctor = function(title) { - this.title = title || "Unnamed"; - this.size = [LiteGraph.NODE_WIDTH, 60]; - this.graph = null; - - this._pos = new Float32Array(10, 10); - - Object.defineProperty(this, "pos", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() { - return this._pos; - }, - enumerable: true - }); - - if (LiteGraph.use_uuids) { - this.id = LiteGraph.uuidv4(); - } - else { - this.id = -1; //not know till not added - } - this.type = null; - - //inputs available: array of inputs - this.inputs = []; - this.outputs = []; - this.connections = []; - - //local data - this.properties = {}; //for the values - this.properties_info = []; //for the info - - this.flags = {}; - }; - - /** - * configure a node from an object containing the serialized info - * @method configure - */ - LGraphNode.prototype.configure = function(info) { - if (this.graph) { - this.graph._version++; - } - for (var j in info) { - if (j == "properties") { - //i don't want to clone properties, I want to reuse the old container - for (var k in info.properties) { - this.properties[k] = info.properties[k]; - if (this.onPropertyChanged) { - this.onPropertyChanged( k, info.properties[k] ); - } - } - continue; - } - - if (info[j] == null) { - continue; - } else if (typeof info[j] == "object") { - //object - if (this[j] && this[j].configure) { - this[j].configure(info[j]); - } else { - this[j] = LiteGraph.cloneObject(info[j], this[j]); - } - } //value - else { - this[j] = info[j]; - } - } - - if (!info.title) { - this.title = this.constructor.title; - } - - if (this.inputs) { - for (var i = 0; i < this.inputs.length; ++i) { - var input = this.inputs[i]; - var link_info = this.graph ? this.graph.links[input.link] : null; - if (this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated - - if( this.onInputAdded ) - this.onInputAdded(input); - - } - } - - if (this.outputs) { - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - if (!output.links) { - continue; - } - for (var j = 0; j < output.links.length; ++j) { - var link_info = this.graph ? this.graph.links[output.links[j]] : null; - if (this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated - } - - if( this.onOutputAdded ) - this.onOutputAdded(output); - } - } - - if( this.widgets ) - { - for (var i = 0; i < this.widgets.length; ++i) - { - var w = this.widgets[i]; - if(!w) - continue; - if(w.options && w.options.property && (this.properties[ w.options.property ] != undefined)) - w.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) ); - } - if (info.widgets_values) { - for (var i = 0; i < info.widgets_values.length; ++i) { - if (this.widgets[i]) { - this.widgets[i].value = info.widgets_values[i]; - } - } - } - } - - if (this.onConfigure) { - this.onConfigure(info); - } - }; - - /** - * serialize the content - * @method serialize - */ - - LGraphNode.prototype.serialize = function() { - //create serialization object - var o = { - id: this.id, - type: this.type, - pos: this.pos, - size: this.size, - flags: LiteGraph.cloneObject(this.flags), - order: this.order, - mode: this.mode - }; - - //special case for when there were errors - if (this.constructor === LGraphNode && this.last_serialization) { - return this.last_serialization; - } - - if (this.inputs) { - o.inputs = this.inputs; - } - - if (this.outputs) { - //clear outputs last data (because data in connections is never serialized but stored inside the outputs info) - for (var i = 0; i < this.outputs.length; i++) { - delete this.outputs[i]._data; - } - o.outputs = this.outputs; - } - - if (this.title && this.title != this.constructor.title) { - o.title = this.title; - } - - if (this.properties) { - o.properties = LiteGraph.cloneObject(this.properties); - } - - if (this.widgets && this.serialize_widgets) { - o.widgets_values = []; - for (var i = 0; i < this.widgets.length; ++i) { - if(this.widgets[i]) - o.widgets_values[i] = this.widgets[i].value; - else - o.widgets_values[i] = null; - } - } - - if (!o.type) { - o.type = this.constructor.type; - } - - if (this.color) { - o.color = this.color; - } - if (this.bgcolor) { - o.bgcolor = this.bgcolor; - } - if (this.boxcolor) { - o.boxcolor = this.boxcolor; - } - if (this.shape) { - o.shape = this.shape; - } - - if (this.onSerialize) { - if (this.onSerialize(o)) { - console.warn( - "node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter" - ); - } - } - - return o; - }; - - /* Creates a clone of this node */ - LGraphNode.prototype.clone = function() { - var node = LiteGraph.createNode(this.type); - if (!node) { - return null; - } - - //we clone it because serialize returns shared containers - var data = LiteGraph.cloneObject(this.serialize()); - - //remove links - if (data.inputs) { - for (var i = 0; i < data.inputs.length; ++i) { - data.inputs[i].link = null; - } - } - - if (data.outputs) { - for (var i = 0; i < data.outputs.length; ++i) { - if (data.outputs[i].links) { - data.outputs[i].links.length = 0; - } - } - } - - delete data["id"]; - - if (LiteGraph.use_uuids) { - data["id"] = LiteGraph.uuidv4() - } - - //remove links - node.configure(data); - - return node; - }; - - /** - * serialize and stringify - * @method toString - */ - - LGraphNode.prototype.toString = function() { - return JSON.stringify(this.serialize()); - }; - //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph - - /** - * get the title string - * @method getTitle - */ - - LGraphNode.prototype.getTitle = function() { - return this.title || this.constructor.title; - }; - - /** - * sets the value of a property - * @method setProperty - * @param {String} name - * @param {*} value - */ - LGraphNode.prototype.setProperty = function(name, value) { - if (!this.properties) { - this.properties = {}; - } - if( value === this.properties[name] ) - return; - var prev_value = this.properties[name]; - this.properties[name] = value; - if (this.onPropertyChanged) { - if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change - this.properties[name] = prev_value; - } - if(this.widgets) //widgets could be linked to properties - for(var i = 0; i < this.widgets.length; ++i) - { - var w = this.widgets[i]; - if(!w) - continue; - if(w.options.property == name) - { - w.value = value; - break; - } - } - }; - - // Execution ************************* - /** - * sets the output data - * @method setOutputData - * @param {number} slot - * @param {*} data - */ - LGraphNode.prototype.setOutputData = function(slot, data) { - if (!this.outputs) { - return; - } - - //this maybe slow and a niche case - //if(slot && slot.constructor === String) - // slot = this.findOutputSlot(slot); - - if (slot == -1 || slot >= this.outputs.length) { - return; - } - - var output_info = this.outputs[slot]; - if (!output_info) { - return; - } - - //store data in the output itself in case we want to debug - output_info._data = data; - - //if there are connections, pass the data to the connections - if (this.outputs[slot].links) { - for (var i = 0; i < this.outputs[slot].links.length; i++) { - var link_id = this.outputs[slot].links[i]; - var link = this.graph.links[link_id]; - if(link) - link.data = data; - } - } - }; - - /** - * sets the output data type, useful when you want to be able to overwrite the data type - * @method setOutputDataType - * @param {number} slot - * @param {String} datatype - */ - LGraphNode.prototype.setOutputDataType = function(slot, type) { - if (!this.outputs) { - return; - } - if (slot == -1 || slot >= this.outputs.length) { - return; - } - var output_info = this.outputs[slot]; - if (!output_info) { - return; - } - //store data in the output itself in case we want to debug - output_info.type = type; - - //if there are connections, pass the data to the connections - if (this.outputs[slot].links) { - for (var i = 0; i < this.outputs[slot].links.length; i++) { - var link_id = this.outputs[slot].links[i]; - this.graph.links[link_id].type = type; - } - } - }; - - /** - * Retrieves the input data (data traveling through the connection) from one slot - * @method getInputData - * @param {number} slot - * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link - * @return {*} data or if it is not connected returns undefined - */ - LGraphNode.prototype.getInputData = function(slot, force_update) { - if (!this.inputs) { - return; - } //undefined; - - if (slot >= this.inputs.length || this.inputs[slot].link == null) { - return; - } - - var link_id = this.inputs[slot].link; - var link = this.graph.links[link_id]; - if (!link) { - //bug: weird case but it happens sometimes - return null; - } - - if (!force_update) { - return link.data; - } - - //special case: used to extract data from the incoming connection before the graph has been executed - var node = this.graph.getNodeById(link.origin_id); - if (!node) { - return link.data; - } - - if (node.updateOutputData) { - node.updateOutputData(link.origin_slot); - } else if (node.onExecute) { - node.onExecute(); - } - - return link.data; - }; - - /** - * Retrieves the input data type (in case this supports multiple input types) - * @method getInputDataType - * @param {number} slot - * @return {String} datatype in string format - */ - LGraphNode.prototype.getInputDataType = function(slot) { - if (!this.inputs) { - return null; - } //undefined; - - if (slot >= this.inputs.length || this.inputs[slot].link == null) { - return null; - } - var link_id = this.inputs[slot].link; - var link = this.graph.links[link_id]; - if (!link) { - //bug: weird case but it happens sometimes - return null; - } - var node = this.graph.getNodeById(link.origin_id); - if (!node) { - return link.type; - } - var output_info = node.outputs[link.origin_slot]; - if (output_info) { - return output_info.type; - } - return null; - }; - - /** - * Retrieves the input data from one slot using its name instead of slot number - * @method getInputDataByName - * @param {String} slot_name - * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link - * @return {*} data or if it is not connected returns null - */ - LGraphNode.prototype.getInputDataByName = function( - slot_name, - force_update - ) { - var slot = this.findInputSlot(slot_name); - if (slot == -1) { - return null; - } - return this.getInputData(slot, force_update); - }; - - /** - * tells you if there is a connection in one input slot - * @method isInputConnected - * @param {number} slot - * @return {boolean} - */ - LGraphNode.prototype.isInputConnected = function(slot) { - if (!this.inputs) { - return false; - } - return slot < this.inputs.length && this.inputs[slot].link != null; - }; - - /** - * tells you info about an input connection (which node, type, etc) - * @method getInputInfo - * @param {number} slot - * @return {Object} object or null { link: id, name: string, type: string or 0 } - */ - LGraphNode.prototype.getInputInfo = function(slot) { - if (!this.inputs) { - return null; - } - if (slot < this.inputs.length) { - return this.inputs[slot]; - } - return null; - }; - - /** - * Returns the link info in the connection of an input slot - * @method getInputLink - * @param {number} slot - * @return {LLink} object or null - */ - LGraphNode.prototype.getInputLink = function(slot) { - if (!this.inputs) { - return null; - } - if (slot < this.inputs.length) { - var slot_info = this.inputs[slot]; - return this.graph.links[ slot_info.link ]; - } - return null; - }; - - /** - * returns the node connected in the input slot - * @method getInputNode - * @param {number} slot - * @return {LGraphNode} node or null - */ - LGraphNode.prototype.getInputNode = function(slot) { - if (!this.inputs) { - return null; - } - if (slot >= this.inputs.length) { - return null; - } - var input = this.inputs[slot]; - if (!input || input.link === null) { - return null; - } - var link_info = this.graph.links[input.link]; - if (!link_info) { - return null; - } - return this.graph.getNodeById(link_info.origin_id); - }; - - /** - * returns the value of an input with this name, otherwise checks if there is a property with that name - * @method getInputOrProperty - * @param {string} name - * @return {*} value - */ - LGraphNode.prototype.getInputOrProperty = function(name) { - if (!this.inputs || !this.inputs.length) { - return this.properties ? this.properties[name] : null; - } - - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input_info = this.inputs[i]; - if (name == input_info.name && input_info.link != null) { - var link = this.graph.links[input_info.link]; - if (link) { - return link.data; - } - } - } - return this.properties[name]; - }; - - /** - * tells you the last output data that went in that slot - * @method getOutputData - * @param {number} slot - * @return {Object} object or null - */ - LGraphNode.prototype.getOutputData = function(slot) { - if (!this.outputs) { - return null; - } - if (slot >= this.outputs.length) { - return null; - } - - var info = this.outputs[slot]; - return info._data; - }; - - /** - * tells you info about an output connection (which node, type, etc) - * @method getOutputInfo - * @param {number} slot - * @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } - */ - LGraphNode.prototype.getOutputInfo = function(slot) { - if (!this.outputs) { - return null; - } - if (slot < this.outputs.length) { - return this.outputs[slot]; - } - return null; - }; - - /** - * tells you if there is a connection in one output slot - * @method isOutputConnected - * @param {number} slot - * @return {boolean} - */ - LGraphNode.prototype.isOutputConnected = function(slot) { - if (!this.outputs) { - return false; - } - return ( - slot < this.outputs.length && - this.outputs[slot].links && - this.outputs[slot].links.length - ); - }; - - /** - * tells you if there is any connection in the output slots - * @method isAnyOutputConnected - * @return {boolean} - */ - LGraphNode.prototype.isAnyOutputConnected = function() { - if (!this.outputs) { - return false; - } - for (var i = 0; i < this.outputs.length; ++i) { - if (this.outputs[i].links && this.outputs[i].links.length) { - return true; - } - } - return false; - }; - - /** - * retrieves all the nodes connected to this output slot - * @method getOutputNodes - * @param {number} slot - * @return {array} - */ - LGraphNode.prototype.getOutputNodes = function(slot) { - if (!this.outputs || this.outputs.length == 0) { - return null; - } - - if (slot >= this.outputs.length) { - return null; - } - - var output = this.outputs[slot]; - if (!output.links || output.links.length == 0) { - return null; - } - - var r = []; - for (var i = 0; i < output.links.length; i++) { - var link_id = output.links[i]; - var link = this.graph.links[link_id]; - if (link) { - var target_node = this.graph.getNodeById(link.target_id); - if (target_node) { - r.push(target_node); - } - } - } - return r; - }; - - LGraphNode.prototype.addOnTriggerInput = function(){ - var trigS = this.findInputSlot("onTrigger"); - if (trigS == -1){ //!trigS || - var input = this.addInput("onTrigger", LiteGraph.EVENT, {optional: true, nameLocked: true}); - return this.findInputSlot("onTrigger"); - } - return trigS; - } - - LGraphNode.prototype.addOnExecutedOutput = function(){ - var trigS = this.findOutputSlot("onExecuted"); - if (trigS == -1){ //!trigS || - var output = this.addOutput("onExecuted", LiteGraph.ACTION, {optional: true, nameLocked: true}); - return this.findOutputSlot("onExecuted"); - } - return trigS; - } - - LGraphNode.prototype.onAfterExecuteNode = function(param, options){ - var trigS = this.findOutputSlot("onExecuted"); - if (trigS != -1){ - - //console.debug(this.id+":"+this.order+" triggering slot onAfterExecute"); - //console.debug(param); - //console.debug(options); - this.triggerSlot(trigS, param, null, options); - - } - } - - LGraphNode.prototype.changeMode = function(modeTo){ - switch(modeTo){ - case LiteGraph.ON_EVENT: - // this.addOnExecutedOutput(); - break; - - case LiteGraph.ON_TRIGGER: - this.addOnTriggerInput(); - this.addOnExecutedOutput(); - break; - - case LiteGraph.NEVER: - break; - - case LiteGraph.ALWAYS: - break; - - case LiteGraph.ON_REQUEST: - break; - - default: - return false; - break; - } - this.mode = modeTo; - return true; - }; - - /** - * Triggers the node code execution, place a boolean/counter to mark the node as being executed - * @method execute - * @param {*} param - * @param {*} options - */ - LGraphNode.prototype.doExecute = function(param, options) { - options = options || {}; - if (this.onExecute){ - - // enable this to give the event an ID - if (!options.action_call) options.action_call = this.id+"_exec_"+Math.floor(Math.random()*9999); - - this.graph.nodes_executing[this.id] = true; //.push(this.id); - - this.onExecute(param, options); - - this.graph.nodes_executing[this.id] = false; //.pop(); - - // save execution/action ref - this.exec_version = this.graph.iteration; - if(options && options.action_call){ - this.action_call = options.action_call; // if (param) - this.graph.nodes_executedAction[this.id] = options.action_call; - } - } - this.execute_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event - if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); // callback - }; - - /** - * Triggers an action, wrapped by logics to control execution flow - * @method actionDo - * @param {String} action name - * @param {*} param - */ - LGraphNode.prototype.actionDo = function(action, param, options) { - options = options || {}; - if (this.onAction){ - - // enable this to give the event an ID - if (!options.action_call) options.action_call = this.id+"_"+(action?action:"action")+"_"+Math.floor(Math.random()*9999); - - this.graph.nodes_actioning[this.id] = (action?action:"actioning"); //.push(this.id); - - this.onAction(action, param, options); - - this.graph.nodes_actioning[this.id] = false; //.pop(); - - // save execution/action ref - if(options && options.action_call){ - this.action_call = options.action_call; // if (param) - this.graph.nodes_executedAction[this.id] = options.action_call; - } - } - this.action_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event - if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); - }; - - /** - * Triggers an event in this node, this will trigger any output with the same name - * @method trigger - * @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all - * @param {*} param - */ - LGraphNode.prototype.trigger = function(action, param, options) { - if (!this.outputs || !this.outputs.length) { - return; - } - - if (this.graph) - this.graph._last_trigger_time = LiteGraph.getTime(); - - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) ) - continue; - this.triggerSlot(i, param, null, options); - } - }; - - /** - * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes - * @method triggerSlot - * @param {Number} slot the index of the output slot - * @param {*} param - * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot - */ - LGraphNode.prototype.triggerSlot = function(slot, param, link_id, options) { - options = options || {}; - if (!this.outputs) { - return; - } - - if(slot == null) - { - console.error("slot must be a number"); - return; - } - - if(slot.constructor !== Number) - console.warn("slot must be a number, use node.trigger('name') if you want to use a string"); - - var output = this.outputs[slot]; - if (!output) { - return; - } - - var links = output.links; - if (!links || !links.length) { - return; - } - - if (this.graph) { - this.graph._last_trigger_time = LiteGraph.getTime(); - } - - //for every link attached here - for (var k = 0; k < links.length; ++k) { - var id = links[k]; - if (link_id != null && link_id != id) { - //to skip links - continue; - } - var link_info = this.graph.links[links[k]]; - if (!link_info) { - //not connected - continue; - } - link_info._last_time = LiteGraph.getTime(); - var node = this.graph.getNodeById(link_info.target_id); - if (!node) { - //node not found? - continue; - } - - //used to mark events in graph - var target_connection = node.inputs[link_info.target_slot]; - - if (node.mode === LiteGraph.ON_TRIGGER) - { - // generate unique trigger ID if not present - if (!options.action_call) options.action_call = this.id+"_trigg_"+Math.floor(Math.random()*9999); - if (node.onExecute) { - // -- wrapping node.onExecute(param); -- - node.doExecute(param, options); - } - } - else if (node.onAction) { - // generate unique action ID if not present - if (!options.action_call) options.action_call = this.id+"_act_"+Math.floor(Math.random()*9999); - //pass the action name - var target_connection = node.inputs[link_info.target_slot]; - // wrap node.onAction(target_connection.name, param); - node.actionDo(target_connection.name, param, options); - } - } - }; - - /** - * clears the trigger slot animation - * @method clearTriggeredSlot - * @param {Number} slot the index of the output slot - * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot - */ - LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) { - if (!this.outputs) { - return; - } - - var output = this.outputs[slot]; - if (!output) { - return; - } - - var links = output.links; - if (!links || !links.length) { - return; - } - - //for every link attached here - for (var k = 0; k < links.length; ++k) { - var id = links[k]; - if (link_id != null && link_id != id) { - //to skip links - continue; - } - var link_info = this.graph.links[links[k]]; - if (!link_info) { - //not connected - continue; - } - link_info._last_time = 0; - } - }; - - /** - * changes node size and triggers callback - * @method setSize - * @param {vec2} size - */ - LGraphNode.prototype.setSize = function(size) - { - this.size = size; - if(this.onResize) - this.onResize(this.size); - } - - /** - * add a new property to this node - * @method addProperty - * @param {string} name - * @param {*} default_value - * @param {string} type string defining the output type ("vec3","number",...) - * @param {Object} extra_info this can be used to have special properties of the property (like values, etc) - */ - LGraphNode.prototype.addProperty = function( - name, - default_value, - type, - extra_info - ) { - var o = { name: name, type: type, default_value: default_value }; - if (extra_info) { - for (var i in extra_info) { - o[i] = extra_info[i]; - } - } - if (!this.properties_info) { - this.properties_info = []; - } - this.properties_info.push(o); - if (!this.properties) { - this.properties = {}; - } - this.properties[name] = default_value; - return o; - }; - - //connections - - /** - * add a new output slot to use in this node - * @method addOutput - * @param {string} name - * @param {string} type string defining the output type ("vec3","number",...) - * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc) - */ - LGraphNode.prototype.addOutput = function(name, type, extra_info) { - var output = { name: name, type: type, links: null }; - if (extra_info) { - for (var i in extra_info) { - output[i] = extra_info[i]; - } - } - - if (!this.outputs) { - this.outputs = []; - } - this.outputs.push(output); - if (this.onOutputAdded) { - this.onOutputAdded(output); - } - - if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,type,true); - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - return output; - }; - - /** - * add a new output slot to use in this node - * @method addOutputs - * @param {Array} array of triplets like [[name,type,extra_info],[...]] - */ - LGraphNode.prototype.addOutputs = function(array) { - for (var i = 0; i < array.length; ++i) { - var info = array[i]; - var o = { name: info[0], type: info[1], link: null }; - if (array[2]) { - for (var j in info[2]) { - o[j] = info[2][j]; - } - } - - if (!this.outputs) { - this.outputs = []; - } - this.outputs.push(o); - if (this.onOutputAdded) { - this.onOutputAdded(o); - } - - if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true); - - } - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - }; - - /** - * remove an existing output slot - * @method removeOutput - * @param {number} slot - */ - LGraphNode.prototype.removeOutput = function(slot) { - this.disconnectOutput(slot); - this.outputs.splice(slot, 1); - for (var i = slot; i < this.outputs.length; ++i) { - if (!this.outputs[i] || !this.outputs[i].links) { - continue; - } - var links = this.outputs[i].links; - for (var j = 0; j < links.length; ++j) { - var link = this.graph.links[links[j]]; - if (!link) { - continue; - } - link.origin_slot -= 1; - } - } - - this.setSize( this.computeSize() ); - if (this.onOutputRemoved) { - this.onOutputRemoved(slot); - } - this.setDirtyCanvas(true, true); - }; - - /** - * add a new input slot to use in this node - * @method addInput - * @param {string} name - * @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0 - * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc) - */ - LGraphNode.prototype.addInput = function(name, type, extra_info) { - type = type || 0; - var input = { name: name, type: type, link: null }; - if (extra_info) { - for (var i in extra_info) { - input[i] = extra_info[i]; - } - } - - if (!this.inputs) { - this.inputs = []; - } - - this.inputs.push(input); - this.setSize( this.computeSize() ); - - if (this.onInputAdded) { - this.onInputAdded(input); - } - - LiteGraph.registerNodeAndSlotType(this,type); - - this.setDirtyCanvas(true, true); - return input; - }; - - /** - * add several new input slots in this node - * @method addInputs - * @param {Array} array of triplets like [[name,type,extra_info],[...]] - */ - LGraphNode.prototype.addInputs = function(array) { - for (var i = 0; i < array.length; ++i) { - var info = array[i]; - var o = { name: info[0], type: info[1], link: null }; - if (array[2]) { - for (var j in info[2]) { - o[j] = info[2][j]; - } - } - - if (!this.inputs) { - this.inputs = []; - } - this.inputs.push(o); - if (this.onInputAdded) { - this.onInputAdded(o); - } - - LiteGraph.registerNodeAndSlotType(this,info[1]); - } - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - }; - - /** - * remove an existing input slot - * @method removeInput - * @param {number} slot - */ - LGraphNode.prototype.removeInput = function(slot) { - this.disconnectInput(slot); - var slot_info = this.inputs.splice(slot, 1); - for (var i = slot; i < this.inputs.length; ++i) { - if (!this.inputs[i]) { - continue; - } - var link = this.graph.links[this.inputs[i].link]; - if (!link) { - continue; - } - link.target_slot -= 1; - } - this.setSize( this.computeSize() ); - if (this.onInputRemoved) { - this.onInputRemoved(slot, slot_info[0] ); - } - this.setDirtyCanvas(true, true); - }; - - /** - * add an special connection to this node (used for special kinds of graphs) - * @method addConnection - * @param {string} name - * @param {string} type string defining the input type ("vec3","number",...) - * @param {[x,y]} pos position of the connection inside the node - * @param {string} direction if is input or output - */ - LGraphNode.prototype.addConnection = function(name, type, pos, direction) { - var o = { - name: name, - type: type, - pos: pos, - direction: direction, - links: null - }; - this.connections.push(o); - return o; - }; - - /** - * computes the minimum size of a node according to its inputs and output slots - * @method computeSize - * @param {vec2} minHeight - * @return {vec2} the total size - */ - LGraphNode.prototype.computeSize = function(out) { - if (this.constructor.size) { - return this.constructor.size.concat(); - } - - var rows = Math.max( - this.inputs ? this.inputs.length : 1, - this.outputs ? this.outputs.length : 1 - ); - var size = out || new Float32Array([0, 0]); - rows = Math.max(rows, 1); - var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size - - var title_width = compute_text_size(this.title); - var input_width = 0; - var output_width = 0; - - if (this.inputs) { - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input = this.inputs[i]; - var text = input.label || input.name || ""; - var text_width = compute_text_size(text); - if (input_width < text_width) { - input_width = text_width; - } - } - } - - if (this.outputs) { - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - var text = output.label || output.name || ""; - var text_width = compute_text_size(text); - if (output_width < text_width) { - output_width = text_width; - } - } - } - - size[0] = Math.max(input_width + output_width + 10, title_width); - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH); - if (this.widgets && this.widgets.length) { - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5); - } - - size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT; - - var widgets_height = 0; - if (this.widgets && this.widgets.length) { - for (var i = 0, l = this.widgets.length; i < l; ++i) { - if (this.widgets[i].computeSize) - widgets_height += this.widgets[i].computeSize(size[0])[1] + 4; - else - widgets_height += LiteGraph.NODE_WIDGET_HEIGHT + 4; - } - widgets_height += 8; - } - - //compute height using widgets height - if( this.widgets_up ) - size[1] = Math.max( size[1], widgets_height ); - else if( this.widgets_start_y != null ) - size[1] = Math.max( size[1], widgets_height + this.widgets_start_y ); - else - size[1] += widgets_height; - - function compute_text_size(text) { - if (!text) { - return 0; - } - return font_size * text.length * 0.6; - } - - if ( - this.constructor.min_height && - size[1] < this.constructor.min_height - ) { - size[1] = this.constructor.min_height; - } - - size[1] += 6; //margin - - return size; - }; - - LGraphNode.prototype.inResizeCorner = function(canvasX, canvasY) { - var rows = this.outputs ? this.outputs.length : 1; - var outputs_offset = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT; - return isInsideRectangle(canvasX, - canvasY, - this.pos[0] + this.size[0] - 15, - this.pos[1] + Math.max(this.size[1] - 15, outputs_offset), - 20, - 20 - ); - } - - /** - * returns all the info available about a property of this node. - * - * @method getPropertyInfo - * @param {String} property name of the property - * @return {Object} the object with all the available info - */ - LGraphNode.prototype.getPropertyInfo = function( property ) - { - var info = null; - - //there are several ways to define info about a property - //legacy mode - if (this.properties_info) { - for (var i = 0; i < this.properties_info.length; ++i) { - if (this.properties_info[i].name == property) { - info = this.properties_info[i]; - break; - } - } - } - //litescene mode using the constructor - if(this.constructor["@" + property]) - info = this.constructor["@" + property]; - - if(this.constructor.widgets_info && this.constructor.widgets_info[property]) - info = this.constructor.widgets_info[property]; - - //litescene mode using the constructor - if (!info && this.onGetPropertyInfo) { - info = this.onGetPropertyInfo(property); - } - - if (!info) - info = {}; - if(!info.type) - info.type = typeof this.properties[property]; - if(info.widget == "combo") - info.type = "enum"; - - return info; - } - - /** - * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties - * - * @method addWidget - * @param {String} type the widget type (could be "number","string","combo" - * @param {String} name the text to show on the widget - * @param {String} value the default value - * @param {Function|String} callback function to call when it changes (optionally, it can be the name of the property to modify) - * @param {Object} options the object that contains special properties of this widget - * @return {Object} the created widget object - */ - LGraphNode.prototype.addWidget = function( type, name, value, callback, options ) - { - if (!this.widgets) { - this.widgets = []; - } - - if(!options && callback && callback.constructor === Object) - { - options = callback; - callback = null; - } - - if(options && options.constructor === String) //options can be the property name - options = { property: options }; - - if(callback && callback.constructor === String) //callback can be the property name - { - if(!options) - options = {}; - options.property = callback; - callback = null; - } - - if(callback && callback.constructor !== Function) - { - console.warn("addWidget: callback must be a function"); - callback = null; - } - - var w = { - type: type.toLowerCase(), - name: name, - value: value, - callback: callback, - options: options || {} - }; - - if (w.options.y !== undefined) { - w.y = w.options.y; - } - - if (!callback && !w.options.callback && !w.options.property) { - console.warn("LiteGraph addWidget(...) without a callback or property assigned"); - } - if (type == "combo" && !w.options.values) { - throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"; - } - this.widgets.push(w); - this.setSize( this.computeSize() ); - return w; - }; - - LGraphNode.prototype.addCustomWidget = function(custom_widget) { - if (!this.widgets) { - this.widgets = []; - } - this.widgets.push(custom_widget); - return custom_widget; - }; - - /** - * returns the bounding of the object, used for rendering purposes - * @method getBounding - * @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage - * @param compute_outer {boolean?} [optional] set to true to include the shadow and connection points in the bounding calculation - * @return {Float32Array[4]} the bounding box in format of [topleft_cornerx, topleft_cornery, width, height] - */ - LGraphNode.prototype.getBounding = function(out, compute_outer) { - out = out || new Float32Array(4); - const nodePos = this.pos; - const isCollapsed = this.flags.collapsed; - const nodeSize = this.size; - - let left_offset = 0; - // 1 offset due to how nodes are rendered - let right_offset = 1 ; - let top_offset = 0; - let bottom_offset = 0; - - if (compute_outer) { - // 4 offset for collapsed node connection points - left_offset = 4; - // 6 offset for right shadow and collapsed node connection points - right_offset = 6 + left_offset; - // 4 offset for collapsed nodes top connection points - top_offset = 4; - // 5 offset for bottom shadow and collapsed node connection points - bottom_offset = 5 + top_offset; - } - - out[0] = nodePos[0] - left_offset; - out[1] = nodePos[1] - LiteGraph.NODE_TITLE_HEIGHT - top_offset; - out[2] = isCollapsed ? - (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + right_offset : - nodeSize[0] + right_offset; - out[3] = isCollapsed ? - LiteGraph.NODE_TITLE_HEIGHT + bottom_offset : - nodeSize[1] + LiteGraph.NODE_TITLE_HEIGHT + bottom_offset; - - if (this.onBounding) { - this.onBounding(out); - } - return out; - }; - - /** - * checks if a point is inside the shape of a node - * @method isPointInside - * @param {number} x - * @param {number} y - * @return {boolean} - */ - LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) { - margin = margin || 0; - - var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT; - if (skip_title) { - margin_top = 0; - } - if (this.flags && this.flags.collapsed) { - //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) - if ( - isInsideRectangle( - x, - y, - this.pos[0] - margin, - this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, - (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + - 2 * margin, - LiteGraph.NODE_TITLE_HEIGHT + 2 * margin - ) - ) { - return true; - } - } else if ( - this.pos[0] - 4 - margin < x && - this.pos[0] + this.size[0] + 4 + margin > x && - this.pos[1] - margin_top - margin < y && - this.pos[1] + this.size[1] + margin > y - ) { - return true; - } - return false; - }; - - /** - * checks if a point is inside a node slot, and returns info about which slot - * @method getSlotInPosition - * @param {number} x - * @param {number} y - * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] } - */ - LGraphNode.prototype.getSlotInPosition = function(x, y) { - //search for inputs - var link_pos = new Float32Array(2); - if (this.inputs) { - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input = this.inputs[i]; - this.getConnectionPos(true, i, link_pos); - if ( - isInsideRectangle( - x, - y, - link_pos[0] - 10, - link_pos[1] - 5, - 20, - 10 - ) - ) { - return { input: input, slot: i, link_pos: link_pos }; - } - } - } - - if (this.outputs) { - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - this.getConnectionPos(false, i, link_pos); - if ( - isInsideRectangle( - x, - y, - link_pos[0] - 10, - link_pos[1] - 5, - 20, - 10 - ) - ) { - return { output: output, slot: i, link_pos: link_pos }; - } - } - } - - return null; - }; - - /** - * returns the input slot with a given name (used for dynamic slots), -1 if not found - * @method findInputSlot - * @param {string} name the name of the slot - * @param {boolean} returnObj if the obj itself wanted - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findInputSlot = function(name, returnObj) { - if (!this.inputs) { - return -1; - } - for (var i = 0, l = this.inputs.length; i < l; ++i) { - if (name == this.inputs[i].name) { - return !returnObj ? i : this.inputs[i]; - } - } - return -1; - }; - - /** - * returns the output slot with a given name (used for dynamic slots), -1 if not found - * @method findOutputSlot - * @param {string} name the name of the slot - * @param {boolean} returnObj if the obj itself wanted - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findOutputSlot = function(name, returnObj) { - returnObj = returnObj || false; - if (!this.outputs) { - return -1; - } - for (var i = 0, l = this.outputs.length; i < l; ++i) { - if (name == this.outputs[i].name) { - return !returnObj ? i : this.outputs[i]; - } - } - return -1; - }; - - // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options - - /** - * returns the first free input slot - * @method findInputSlotFree - * @param {object} options - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findInputSlotFree = function(optsIn) { - var optsIn = optsIn || {}; - var optsDef = {returnObj: false - ,typesNotAccepted: [] - }; - var opts = Object.assign(optsDef,optsIn); - if (!this.inputs) { - return -1; - } - for (var i = 0, l = this.inputs.length; i < l; ++i) { - if (this.inputs[i].link && this.inputs[i].link != null) { - continue; - } - if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.inputs[i].type)){ - continue; - } - return !opts.returnObj ? i : this.inputs[i]; - } - return -1; - }; - - /** - * returns the first output slot free - * @method findOutputSlotFree - * @param {object} options - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findOutputSlotFree = function(optsIn) { - var optsIn = optsIn || {}; - var optsDef = { returnObj: false - ,typesNotAccepted: [] - }; - var opts = Object.assign(optsDef,optsIn); - if (!this.outputs) { - return -1; - } - for (var i = 0, l = this.outputs.length; i < l; ++i) { - if (this.outputs[i].links && this.outputs[i].links != null) { - continue; - } - if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.outputs[i].type)){ - continue; - } - return !opts.returnObj ? i : this.outputs[i]; - } - return -1; - }; - - /** - * findSlotByType for INPUTS - */ - LGraphNode.prototype.findInputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { - return this.findSlotByType(true, type, returnObj, preferFreeSlot, doNotUseOccupied); - }; - - /** - * findSlotByType for OUTPUTS - */ - LGraphNode.prototype.findOutputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { - return this.findSlotByType(false, type, returnObj, preferFreeSlot, doNotUseOccupied); - }; - - /** - * returns the output (or input) slot with a given type, -1 if not found - * @method findSlotByType - * @param {boolean} input uise inputs instead of outputs - * @param {string} type the type of the slot - * @param {boolean} returnObj if the obj itself wanted - * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway) - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findSlotByType = function(input, type, returnObj, preferFreeSlot, doNotUseOccupied) { - input = input || false; - returnObj = returnObj || false; - preferFreeSlot = preferFreeSlot || false; - doNotUseOccupied = doNotUseOccupied || false; - var aSlots = input ? this.inputs : this.outputs; - if (!aSlots) { - return -1; - } - // !! empty string type is considered 0, * !! - if (type == "" || type == "*") type = 0; - for (var i = 0, l = aSlots.length; i < l; ++i) { - var tFound = false; - var aSource = (type+"").toLowerCase().split(","); - var aDest = aSlots[i].type=="0"||aSlots[i].type=="*"?"0":aSlots[i].type; - aDest = (aDest+"").toLowerCase().split(","); - for(var sI=0;sI= 0 && target_slot !== null){ - //console.debug("CONNbyTYPE type "+target_slotType+" for "+target_slot) - return this.connect(slot, target_node, target_slot); - }else{ - //console.log("type "+target_slotType+" not found or not free?") - if (opts.createEventInCase && target_slotType == LiteGraph.EVENT){ - // WILL CREATE THE onTrigger IN SLOT - //console.debug("connect WILL CREATE THE onTrigger "+target_slotType+" to "+target_node); - return this.connect(slot, target_node, -1); - } - // connect to the first general output slot if not found a specific type and - if (opts.generalTypeInCase){ - var target_slot = target_node.findInputSlotByType(0, false, true, true); - //console.debug("connect TO a general type (*, 0), if not found the specific type ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); - if (target_slot >= 0){ - return this.connect(slot, target_node, target_slot); - } - } - // connect to the first free input slot if not found a specific type and this output is general - if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == "*" || target_slotType == "")){ - var target_slot = target_node.findInputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); - //console.debug("connect TO TheFirstFREE ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); - if (target_slot >= 0){ - return this.connect(slot, target_node, target_slot); - } - } - - console.debug("no way to connect type: ",target_slotType," to targetNODE ",target_node); - //TODO filter - - return null; - } - } - - /** - * connect this node input to the output of another node BY TYPE - * @method connectByType - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} node the target node - * @param {string} target_type the output slot type of the target node - * @return {Object} the link_info is created, otherwise null - */ - LGraphNode.prototype.connectByTypeOutput = function(slot, source_node, source_slotType, optsIn) { - var optsIn = optsIn || {}; - var optsDef = { createEventInCase: true - ,firstFreeIfInputGeneralInCase: true - ,generalTypeInCase: true - }; - var opts = Object.assign(optsDef,optsIn); - if (source_node && source_node.constructor === Number) { - source_node = this.graph.getNodeById(source_node); - } - var source_slot = source_node.findOutputSlotByType(source_slotType, false, true); - if (source_slot >= 0 && source_slot !== null){ - //console.debug("CONNbyTYPE OUT! type "+source_slotType+" for "+source_slot) - return source_node.connect(source_slot, this, slot); - }else{ - - // connect to the first general output slot if not found a specific type and - if (opts.generalTypeInCase){ - var source_slot = source_node.findOutputSlotByType(0, false, true, true); - if (source_slot >= 0){ - return source_node.connect(source_slot, this, slot); - } - } - - if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){ - // WILL CREATE THE onExecuted OUT SLOT - if (LiteGraph.do_add_triggers_slots){ - var source_slot = source_node.addOnExecutedOutput(); - return source_node.connect(source_slot, this, slot); - } - } - // connect to the first free output slot if not found a specific type and this input is general - if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == "*" || source_slotType == "")){ - var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); - if (source_slot >= 0){ - return source_node.connect(source_slot, this, slot); - } - } - - console.debug("no way to connect byOUT type: ",source_slotType," to sourceNODE ",source_node); - //TODO filter - - //console.log("type OUT! "+source_slotType+" not found or not free?") - return null; - } - } - - /** - * connect this node output to the input of another node - * @method connect - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} node the target node - * @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) - * @return {Object} the link_info is created, otherwise null - */ - LGraphNode.prototype.connect = function(slot, target_node, target_slot) { - target_slot = target_slot || 0; - - if (!this.graph) { - //could be connected before adding it to a graph - console.log( - "Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them." - ); //due to link ids being associated with graphs - return null; - } - - //seek for the output slot - if (slot.constructor === String) { - slot = this.findOutputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return null; - } - } else if (!this.outputs || slot >= this.outputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return null; - } - - if (target_node && target_node.constructor === Number) { - target_node = this.graph.getNodeById(target_node); - } - if (!target_node) { - throw "target node is null"; - } - - //avoid loopback - if (target_node == this) { - return null; - } - - //you can specify the slot by name - if (target_slot.constructor === String) { - target_slot = target_node.findInputSlot(target_slot); - if (target_slot == -1) { - if (LiteGraph.debug) { - console.log( - "Connect: Error, no slot of name " + target_slot - ); - } - return null; - } - } else if (target_slot === LiteGraph.EVENT) { - - if (LiteGraph.do_add_triggers_slots){ - //search for first slot with event? :: NO this is done outside - //console.log("Connect: Creating triggerEvent"); - // force mode - target_node.changeMode(LiteGraph.ON_TRIGGER); - target_slot = target_node.findInputSlot("onTrigger"); - }else{ - return null; // -- break -- - } - } else if ( - !target_node.inputs || - target_slot >= target_node.inputs.length - ) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return null; - } - - var changed = false; - - var input = target_node.inputs[target_slot]; - var link_info = null; - var output = this.outputs[slot]; - - if (!this.outputs[slot]){ - /*console.debug("Invalid slot passed: "+slot); - console.debug(this.outputs);*/ - return null; - } - - // allow target node to change slot - if (target_node.onBeforeConnectInput) { - // This way node can choose another slot (or make a new one?) - target_slot = target_node.onBeforeConnectInput(target_slot); //callback - } - - //check target_slot and check connection types - if (target_slot===false || target_slot===null || !LiteGraph.isValidConnection(output.type, input.type)) - { - this.setDirtyCanvas(false, true); - if(changed) - this.graph.connectionChange(this, link_info); - return null; - }else{ - //console.debug("valid connection",output.type, input.type); - } - - //allows nodes to block connection, callback - if (target_node.onConnectInput) { - if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) { - return null; - } - } - if (this.onConnectOutput) { // callback - if ( this.onConnectOutput(slot, input.type, input, target_node, target_slot) === false ) { - return null; - } - } - - //if there is something already plugged there, disconnect - if (target_node.inputs[target_slot] && target_node.inputs[target_slot].link != null) { - this.graph.beforeChange(); - target_node.disconnectInput(target_slot, {doProcessChange: false}); - changed = true; - } - if (output.links !== null && output.links.length){ - switch(output.type){ - case LiteGraph.EVENT: - if (!LiteGraph.allow_multi_output_for_events){ - this.graph.beforeChange(); - this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false}); - changed = true; - } - break; - default: - break; - } - } - - var nextId - if (LiteGraph.use_uuids) - nextId = LiteGraph.uuidv4(); - else - nextId = ++this.graph.last_link_id; - - //create link class - link_info = new LLink( - nextId, - input.type || output.type, - this.id, - slot, - target_node.id, - target_slot - ); - - //add to graph links list - this.graph.links[link_info.id] = link_info; - - //connect in output - if (output.links == null) { - output.links = []; - } - output.links.push(link_info.id); - //connect in input - target_node.inputs[target_slot].link = link_info.id; - if (this.graph) { - this.graph._version++; - } - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - true, - link_info, - output - ); - } //link_info has been created now, so its updated - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - target_slot, - true, - link_info, - input - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - target_slot, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot, - target_node, - target_slot - ); - } - - this.setDirtyCanvas(false, true); - this.graph.afterChange(); - this.graph.connectionChange(this, link_info); - - return link_info; - }; - - /** - * disconnect one output to an specific node - * @method disconnectOutput - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] - * @return {boolean} if it was disconnected successfully - */ - LGraphNode.prototype.disconnectOutput = function(slot, target_node) { - if (slot.constructor === String) { - slot = this.findOutputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return false; - } - } else if (!this.outputs || slot >= this.outputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return false; - } - - //get output slot - var output = this.outputs[slot]; - if (!output || !output.links || output.links.length == 0) { - return false; - } - - //one of the output links in this slot - if (target_node) { - if (target_node.constructor === Number) { - target_node = this.graph.getNodeById(target_node); - } - if (!target_node) { - throw "Target Node not found"; - } - - for (var i = 0, l = output.links.length; i < l; i++) { - var link_id = output.links[i]; - var link_info = this.graph.links[link_id]; - - //is the link we are searching for... - if (link_info.target_id == target_node.id) { - output.links.splice(i, 1); //remove here - var input = target_node.inputs[link_info.target_slot]; - input.link = null; //remove there - delete this.graph.links[link_id]; //remove the link from the links pool - if (this.graph) { - this.graph._version++; - } - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - link_info.target_slot, - false, - link_info, - input - ); - } //link_info hasn't been modified so its ok - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - break; - } - } - } //all the links in this output slot - else { - for (var i = 0, l = output.links.length; i < l; i++) { - var link_id = output.links[i]; - var link_info = this.graph.links[link_id]; - if (!link_info) { - //bug: it happens sometimes - continue; - } - - var target_node = this.graph.getNodeById(link_info.target_id); - var input = null; - if (this.graph) { - this.graph._version++; - } - if (target_node) { - input = target_node.inputs[link_info.target_slot]; - input.link = null; //remove other side link - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - link_info.target_slot, - false, - link_info, - input - ); - } //link_info hasn't been modified so its ok - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - } - delete this.graph.links[link_id]; //remove the link from the links pool - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - } - output.links = null; - } - - this.setDirtyCanvas(false, true); - this.graph.connectionChange(this); - return true; - }; - - /** - * disconnect one input - * @method disconnectInput - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @return {boolean} if it was disconnected successfully - */ - LGraphNode.prototype.disconnectInput = function(slot) { - //seek for the output slot - if (slot.constructor === String) { - slot = this.findInputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return false; - } - } else if (!this.inputs || slot >= this.inputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return false; - } - - var input = this.inputs[slot]; - if (!input) { - return false; - } - - var link_id = this.inputs[slot].link; - if(link_id != null) - { - this.inputs[slot].link = null; - - //remove other side - var link_info = this.graph.links[link_id]; - if (link_info) { - var target_node = this.graph.getNodeById(link_info.origin_id); - if (!target_node) { - return false; - } - - var output = target_node.outputs[link_info.origin_slot]; - if (!output || !output.links || output.links.length == 0) { - return false; - } - - //search in the inputs list for this link - for (var i = 0, l = output.links.length; i < l; i++) { - if (output.links[i] == link_id) { - output.links.splice(i, 1); - break; - } - } - - delete this.graph.links[link_id]; //remove from the pool - if (this.graph) { - this.graph._version++; - } - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.INPUT, - slot, - false, - link_info, - input - ); - } - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.OUTPUT, - i, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - target_node, - i - ); - this.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot); - } - } - } //link != null - - this.setDirtyCanvas(false, true); - if(this.graph) - this.graph.connectionChange(this); - return true; - }; - - /** - * returns the center of a connection point in canvas coords - * @method getConnectionPos - * @param {boolean} is_input true if if a input slot, false if it is an output - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {vec2} out [optional] a place to store the output, to free garbage - * @return {[x,y]} the position - **/ - LGraphNode.prototype.getConnectionPos = function( - is_input, - slot_number, - out - ) { - out = out || new Float32Array(2); - var num_slots = 0; - if (is_input && this.inputs) { - num_slots = this.inputs.length; - } - if (!is_input && this.outputs) { - num_slots = this.outputs.length; - } - - var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5; - - if (this.flags.collapsed) { - var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH; - if (this.horizontal) { - out[0] = this.pos[0] + w * 0.5; - if (is_input) { - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - } else { - out[1] = this.pos[1]; - } - } else { - if (is_input) { - out[0] = this.pos[0]; - } else { - out[0] = this.pos[0] + w; - } - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5; - } - return out; - } - - //weird feature that never got finished - if (is_input && slot_number == -1) { - out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; - out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; - return out; - } - - //hard-coded pos - if ( - is_input && - num_slots > slot_number && - this.inputs[slot_number].pos - ) { - out[0] = this.pos[0] + this.inputs[slot_number].pos[0]; - out[1] = this.pos[1] + this.inputs[slot_number].pos[1]; - return out; - } else if ( - !is_input && - num_slots > slot_number && - this.outputs[slot_number].pos - ) { - out[0] = this.pos[0] + this.outputs[slot_number].pos[0]; - out[1] = this.pos[1] + this.outputs[slot_number].pos[1]; - return out; - } - - //horizontal distributed slots - if (this.horizontal) { - out[0] = - this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots); - if (is_input) { - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - } else { - out[1] = this.pos[1] + this.size[1]; - } - return out; - } - - //default vertical slots - if (is_input) { - out[0] = this.pos[0] + offset; - } else { - out[0] = this.pos[0] + this.size[0] + 1 - offset; - } - out[1] = - this.pos[1] + - (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + - (this.constructor.slot_start_y || 0); - return out; - }; - - /* Force align to grid */ - LGraphNode.prototype.alignToGrid = function() { - this.pos[0] = - LiteGraph.CANVAS_GRID_SIZE * - Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE); - this.pos[1] = - LiteGraph.CANVAS_GRID_SIZE * - Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE); - }; - - /* Console output */ - LGraphNode.prototype.trace = function(msg) { - if (!this.console) { - this.console = []; - } - - this.console.push(msg); - if (this.console.length > LGraphNode.MAX_CONSOLE) { - this.console.shift(); - } - - if(this.graph.onNodeTrace) - this.graph.onNodeTrace(this, msg); - }; - - /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ - LGraphNode.prototype.setDirtyCanvas = function( - dirty_foreground, - dirty_background - ) { - if (!this.graph) { - return; - } - this.graph.sendActionToCanvas("setDirty", [ - dirty_foreground, - dirty_background - ]); - }; - - LGraphNode.prototype.loadImage = function(url) { - var img = new Image(); - img.src = LiteGraph.node_images_path + url; - img.ready = false; - - var that = this; - img.onload = function() { - this.ready = true; - that.setDirtyCanvas(true); - }; - return img; - }; - - //safe LGraphNode action execution (not sure if safe) - /* -LGraphNode.prototype.executeAction = function(action) -{ - if(action == "") return false; - - if( action.indexOf(";") != -1 || action.indexOf("}") != -1) - { - this.trace("Error: Action contains unsafe characters"); - return false; - } - - var tokens = action.split("("); - var func_name = tokens[0]; - if( typeof(this[func_name]) != "function") - { - this.trace("Error: Action not found on node: " + func_name); - return false; - } - - var code = action; - - try - { - var _foo = eval; - eval = null; - (new Function("with(this) { " + code + "}")).call(this); - eval = _foo; - } - catch (err) - { - this.trace("Error executing action {" + action + "} :" + err); - return false; - } - - return true; -} -*/ - - /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ - LGraphNode.prototype.captureInput = function(v) { - if (!this.graph || !this.graph.list_of_graphcanvas) { - return; - } - - var list = this.graph.list_of_graphcanvas; - - for (var i = 0; i < list.length; ++i) { - var c = list[i]; - //releasing somebody elses capture?! - if (!v && c.node_capturing_input != this) { - continue; - } - - //change - c.node_capturing_input = v ? this : null; - } - }; - - /** - * Collapse the node to make it smaller on the canvas - * @method collapse - **/ - LGraphNode.prototype.collapse = function(force) { - this.graph._version++; - if (this.constructor.collapsable === false && !force) { - return; - } - if (!this.flags.collapsed) { - this.flags.collapsed = true; - } else { - this.flags.collapsed = false; - } - this.setDirtyCanvas(true, true); - }; - - /** - * Forces the node to do not move or realign on Z - * @method pin - **/ - - LGraphNode.prototype.pin = function(v) { - this.graph._version++; - if (v === undefined) { - this.flags.pinned = !this.flags.pinned; - } else { - this.flags.pinned = v; - } - }; - - LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) { - return [ - (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0], - (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1] - ]; - }; - - function LGraphGroup(title) { - this._ctor(title); - } - - global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup; - - LGraphGroup.prototype._ctor = function(title) { - this.title = title || "Group"; - this.font_size = 24; - this.color = LGraphCanvas.node_colors.pale_blue - ? LGraphCanvas.node_colors.pale_blue.groupcolor - : "#AAA"; - this._bounding = new Float32Array([10, 10, 140, 80]); - this._pos = this._bounding.subarray(0, 2); - this._size = this._bounding.subarray(2, 4); - this._nodes = []; - this.graph = null; - - Object.defineProperty(this, "pos", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() { - return this._pos; - }, - enumerable: true - }); - - Object.defineProperty(this, "size", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._size[0] = Math.max(140, v[0]); - this._size[1] = Math.max(80, v[1]); - }, - get: function() { - return this._size; - }, - enumerable: true - }); - }; - - LGraphGroup.prototype.configure = function(o) { - this.title = o.title; - this._bounding.set(o.bounding); - this.color = o.color; - if (o.font_size) { - this.font_size = o.font_size; - } - }; - - LGraphGroup.prototype.serialize = function() { - var b = this._bounding; - return { - title: this.title, - bounding: [ - Math.round(b[0]), - Math.round(b[1]), - Math.round(b[2]), - Math.round(b[3]) - ], - color: this.color, - font_size: this.font_size - }; - }; - - LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) { - this._pos[0] += deltax; - this._pos[1] += deltay; - if (ignore_nodes) { - return; - } - for (var i = 0; i < this._nodes.length; ++i) { - var node = this._nodes[i]; - node.pos[0] += deltax; - node.pos[1] += deltay; - } - }; - - LGraphGroup.prototype.recomputeInsideNodes = function() { - this._nodes.length = 0; - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); - - for (var i = 0; i < nodes.length; ++i) { - var node = nodes[i]; - node.getBounding(node_bounding); - if (!overlapBounding(this._bounding, node_bounding)) { - continue; - } //out of the visible area - this._nodes.push(node); - } - }; - - LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside; - LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas; - - //**************************************** - - //Scale and Offset - function DragAndScale(element, skip_events) { - this.offset = new Float32Array([0, 0]); - this.scale = 1; - this.max_scale = 10; - this.min_scale = 0.1; - this.onredraw = null; - this.enabled = true; - this.last_mouse = [0, 0]; - this.element = null; - this.visible_area = new Float32Array(4); - - if (element) { - this.element = element; - if (!skip_events) { - this.bindEvents(element); - } - } - } - - LiteGraph.DragAndScale = DragAndScale; - - DragAndScale.prototype.bindEvents = function(element) { - this.last_mouse = new Float32Array(2); - - this._binded_mouse_callback = this.onMouse.bind(this); - - LiteGraph.pointerListenerAdd(element,"down", this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(element,"move", this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(element,"up", this._binded_mouse_callback); - - element.addEventListener( - "mousewheel", - this._binded_mouse_callback, - false - ); - element.addEventListener("wheel", this._binded_mouse_callback, false); - }; - - DragAndScale.prototype.computeVisibleArea = function( viewport ) { - if (!this.element) { - this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0; - return; - } - var width = this.element.width; - var height = this.element.height; - var startx = -this.offset[0]; - var starty = -this.offset[1]; - if( viewport ) - { - startx += viewport[0] / this.scale; - starty += viewport[1] / this.scale; - width = viewport[2]; - height = viewport[3]; - } - var endx = startx + width / this.scale; - var endy = starty + height / this.scale; - this.visible_area[0] = startx; - this.visible_area[1] = starty; - this.visible_area[2] = endx - startx; - this.visible_area[3] = endy - starty; - }; - - DragAndScale.prototype.onMouse = function(e) { - if (!this.enabled) { - return; - } - - var canvas = this.element; - var rect = canvas.getBoundingClientRect(); - var x = e.clientX - rect.left; - var y = e.clientY - rect.top; - e.canvasx = x; - e.canvasy = y; - e.dragging = this.dragging; - - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - - //console.log("pointerevents: DragAndScale onMouse "+e.type+" "+is_inside); - - var ignore = false; - if (this.onmouse) { - ignore = this.onmouse(e); - } - - if (e.type == LiteGraph.pointerevents_method+"down" && is_inside) { - this.dragging = true; - LiteGraph.pointerListenerRemove(canvas,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(document,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(document,"up",this._binded_mouse_callback); - } else if (e.type == LiteGraph.pointerevents_method+"move") { - if (!ignore) { - var deltax = x - this.last_mouse[0]; - var deltay = y - this.last_mouse[1]; - if (this.dragging) { - this.mouseDrag(deltax, deltay); - } - } - } else if (e.type == LiteGraph.pointerevents_method+"up") { - this.dragging = false; - LiteGraph.pointerListenerRemove(document,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerRemove(document,"up",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(canvas,"move",this._binded_mouse_callback); - } else if ( is_inside && - (e.type == "mousewheel" || - e.type == "wheel" || - e.type == "DOMMouseScroll") - ) { - e.eventType = "mousewheel"; - if (e.type == "wheel") { - e.wheel = -e.deltaY; - } else { - e.wheel = - e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; - } - - //from stack overflow - e.delta = e.wheelDelta - ? e.wheelDelta / 40 - : e.deltaY - ? -e.deltaY / 3 - : 0; - this.changeDeltaScale(1.0 + e.delta * 0.05); - } - - this.last_mouse[0] = x; - this.last_mouse[1] = y; - - if(is_inside) - { - e.preventDefault(); - e.stopPropagation(); - return false; - } - }; - - DragAndScale.prototype.toCanvasContext = function(ctx) { - ctx.scale(this.scale, this.scale); - ctx.translate(this.offset[0], this.offset[1]); - }; - - DragAndScale.prototype.convertOffsetToCanvas = function(pos) { - //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]]; - return [ - (pos[0] + this.offset[0]) * this.scale, - (pos[1] + this.offset[1]) * this.scale - ]; - }; - - DragAndScale.prototype.convertCanvasToOffset = function(pos, out) { - out = out || [0, 0]; - out[0] = pos[0] / this.scale - this.offset[0]; - out[1] = pos[1] / this.scale - this.offset[1]; - return out; - }; - - DragAndScale.prototype.mouseDrag = function(x, y) { - this.offset[0] += x / this.scale; - this.offset[1] += y / this.scale; - - if (this.onredraw) { - this.onredraw(this); - } - }; - - DragAndScale.prototype.changeScale = function(value, zooming_center) { - if (value < this.min_scale) { - value = this.min_scale; - } else if (value > this.max_scale) { - value = this.max_scale; - } - - if (value == this.scale) { - return; - } - - if (!this.element) { - return; - } - - var rect = this.element.getBoundingClientRect(); - if (!rect) { - return; - } - - zooming_center = zooming_center || [ - rect.width * 0.5, - rect.height * 0.5 - ]; - var center = this.convertCanvasToOffset(zooming_center); - this.scale = value; - if (Math.abs(this.scale - 1) < 0.01) { - this.scale = 1; - } - - var new_center = this.convertCanvasToOffset(zooming_center); - var delta_offset = [ - new_center[0] - center[0], - new_center[1] - center[1] - ]; - - this.offset[0] += delta_offset[0]; - this.offset[1] += delta_offset[1]; - - if (this.onredraw) { - this.onredraw(this); - } - }; - - DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) { - this.changeScale(this.scale * value, zooming_center); - }; - - DragAndScale.prototype.reset = function() { - this.scale = 1; - this.offset[0] = 0; - this.offset[1] = 0; - }; - - //********************************************************************************* - // LGraphCanvas: LGraph renderer CLASS - //********************************************************************************* - - /** - * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. - * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked - * - * @class LGraphCanvas - * @constructor - * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) - * @param {LGraph} graph [optional] - * @param {Object} options [optional] { skip_rendering, autoresize, viewport } - */ - function LGraphCanvas(canvas, graph, options) { - this.options = options = options || {}; - - //if(graph === undefined) - // throw ("No graph assigned"); - this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE; - - if (canvas && canvas.constructor === String) { - canvas = document.querySelector(canvas); - } - - this.ds = new DragAndScale(); - this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much - - this.title_text_font = "" + LiteGraph.NODE_TEXT_SIZE + "px Arial"; - this.inner_text_font = - "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial"; - this.node_title_color = LiteGraph.NODE_TITLE_COLOR; - this.default_link_color = LiteGraph.LINK_COLOR; - this.default_connection_color = { - input_off: "#778", - input_on: "#7F7", //"#BBD" - output_off: "#778", - output_on: "#7F7" //"#BBD" - }; - this.default_connection_color_byType = { - /*number: "#7F7", - string: "#77F", - boolean: "#F77",*/ - } - this.default_connection_color_byTypeOff = { - /*number: "#474", - string: "#447", - boolean: "#744",*/ - }; - - this.highquality_render = true; - this.use_gradients = false; //set to true to render titlebar with gradients - this.editor_alpha = 1; //used for transition - this.pause_rendering = false; - this.clear_background = true; - this.clear_background_color = "#222"; - - this.read_only = false; //if set to true users cannot modify the graph - this.render_only_selected = true; - this.live_mode = false; - this.show_info = true; - this.allow_dragcanvas = true; - this.allow_dragnodes = true; - this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc - this.multi_select = false; //allow selecting multi nodes without pressing extra keys - this.allow_searchbox = true; - this.allow_reconnect_links = true; //allows to change a connection with having to redo it again - this.align_to_grid = false; //snap to grid - - this.drag_mode = false; - this.dragging_rectangle = null; - - this.filter = null; //allows to filter to only accept some type of nodes in a graph - - this.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything - this.always_render_background = false; - this.render_shadows = true; - this.render_canvas_border = true; - this.render_connections_shadows = false; //too much cpu - this.render_connections_border = true; - this.render_curved_connections = false; - this.render_connection_arrows = false; - this.render_collapsed_slots = true; - this.render_execution_order = false; - this.render_title_colored = true; - this.render_link_tooltip = true; - - this.links_render_mode = LiteGraph.SPLINE_LINK; - - this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle - this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle - this.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD - - //to personalize the search box - this.onSearchBox = null; - this.onSearchBoxSelection = null; - - //callbacks - this.onMouse = null; - this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform - this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform - this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs) - this.onDrawLinkTooltip = null; //called when rendering a tooltip - this.onNodeMoved = null; //called after moving a node - this.onSelectionChange = null; //called if the selection changes - this.onConnectingChange = null; //called before any link changes - this.onBeforeChange = null; //called before modifying the graph - this.onAfterChange = null; //called after modifying the graph - - this.connections_width = 3; - this.round_radius = 8; - - this.current_node = null; - this.node_widget = null; //used for widgets - this.over_link_center = null; - this.last_mouse_position = [0, 0]; - this.visible_area = this.ds.visible_area; - this.visible_links = []; - - this.viewport = options.viewport || null; //to constraint render area to a portion of the canvas - - //link canvas and graph - if (graph) { - graph.attachCanvas(this); - } - - this.setCanvas(canvas,options.skip_events); - this.clear(); - - if (!options.skip_render) { - this.startRendering(); - } - - this.autoresize = options.autoresize; - } - - global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas; - - LGraphCanvas.DEFAULT_BACKGROUND_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; - - LGraphCanvas.link_type_colors = { - "-1": LiteGraph.EVENT_LINK_COLOR, - number: "#AAA", - node: "#DCA" - }; - LGraphCanvas.gradients = {}; //cache of gradients - - /** - * clears all the data inside - * - * @method clear - */ - LGraphCanvas.prototype.clear = function() { - this.frame = 0; - this.last_draw_time = 0; - this.render_time = 0; - this.fps = 0; - - //this.scale = 1; - //this.offset = [0,0]; - - this.dragging_rectangle = null; - - this.selected_nodes = {}; - this.selected_group = null; - - this.visible_nodes = []; - this.node_dragged = null; - this.node_over = null; - this.node_capturing_input = null; - this.connecting_node = null; - this.highlighted_links = {}; - - this.dragging_canvas = false; - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.dirty_area = null; - - this.node_in_panel = null; - this.node_widget = null; - - this.last_mouse = [0, 0]; - this.last_mouseclick = 0; - this.pointer_is_down = false; - this.pointer_is_double = false; - this.visible_area.set([0, 0, 0, 0]); - - if (this.onClear) { - this.onClear(); - } - }; - - /** - * assigns a graph, you can reassign graphs to the same canvas - * - * @method setGraph - * @param {LGraph} graph - */ - LGraphCanvas.prototype.setGraph = function(graph, skip_clear) { - if (this.graph == graph) { - return; - } - - if (!skip_clear) { - this.clear(); - } - - if (!graph && this.graph) { - this.graph.detachCanvas(this); - return; - } - - graph.attachCanvas(this); - - //remove the graph stack in case a subgraph was open - if (this._graph_stack) - this._graph_stack = null; - - this.setDirty(true, true); - }; - - /** - * returns the top level graph (in case there are subgraphs open on the canvas) - * - * @method getTopGraph - * @return {LGraph} graph - */ - LGraphCanvas.prototype.getTopGraph = function() - { - if(this._graph_stack.length) - return this._graph_stack[0]; - return this.graph; - } - - /** - * opens a graph contained inside a node in the current graph - * - * @method openSubgraph - * @param {LGraph} graph - */ - LGraphCanvas.prototype.openSubgraph = function(graph) { - if (!graph) { - throw "graph cannot be null"; - } - - if (this.graph == graph) { - throw "graph cannot be the same"; - } - - this.clear(); - - if (this.graph) { - if (!this._graph_stack) { - this._graph_stack = []; - } - this._graph_stack.push(this.graph); - } - - graph.attachCanvas(this); - this.checkPanels(); - this.setDirty(true, true); - }; - - /** - * closes a subgraph contained inside a node - * - * @method closeSubgraph - * @param {LGraph} assigns a graph - */ - LGraphCanvas.prototype.closeSubgraph = function() { - if (!this._graph_stack || this._graph_stack.length == 0) { - return; - } - var subgraph_node = this.graph._subgraph_node; - var graph = this._graph_stack.pop(); - this.selected_nodes = {}; - this.highlighted_links = {}; - graph.attachCanvas(this); - this.setDirty(true, true); - if (subgraph_node) { - this.centerOnNode(subgraph_node); - this.selectNodes([subgraph_node]); - } - // when close sub graph back to offset [0, 0] scale 1 - this.ds.offset = [0, 0] - this.ds.scale = 1 - }; - - /** - * returns the visually active graph (in case there are more in the stack) - * @method getCurrentGraph - * @return {LGraph} the active graph - */ - LGraphCanvas.prototype.getCurrentGraph = function() { - return this.graph; - }; - - /** - * assigns a canvas - * - * @method setCanvas - * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) - */ - LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) { - var that = this; - - if (canvas) { - if (canvas.constructor === String) { - canvas = document.getElementById(canvas); - if (!canvas) { - throw "Error creating LiteGraph canvas: Canvas not found"; - } - } - } - - if (canvas === this.canvas) { - return; - } - - if (!canvas && this.canvas) { - //maybe detach events from old_canvas - if (!skip_events) { - this.unbindEvents(); - } - } - - this.canvas = canvas; - this.ds.element = canvas; - - if (!canvas) { - return; - } - - //this.canvas.tabindex = "1000"; - canvas.className += " lgraphcanvas"; - canvas.data = this; - canvas.tabindex = "1"; //to allow key events - - //bg canvas: used for non changing stuff - this.bgcanvas = null; - if (!this.bgcanvas) { - this.bgcanvas = document.createElement("canvas"); - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - } - - if (canvas.getContext == null) { - if (canvas.localName != "canvas") { - throw "Element supplied for LGraphCanvas must be a element, you passed a " + - canvas.localName; - } - throw "This browser doesn't support Canvas"; - } - - var ctx = (this.ctx = canvas.getContext("2d")); - if (ctx == null) { - if (!canvas.webgl_enabled) { - console.warn( - "This canvas seems to be WebGL, enabling WebGL renderer" - ); - } - this.enableWebGL(); - } - - //input: (move and up could be unbinded) - // why here? this._mousemove_callback = this.processMouseMove.bind(this); - // why here? this._mouseup_callback = this.processMouseUp.bind(this); - - if (!skip_events) { - this.bindEvents(); - } - }; - - //used in some events to capture them - LGraphCanvas.prototype._doNothing = function doNothing(e) { - //console.log("pointerevents: _doNothing "+e.type); - e.preventDefault(); - return false; - }; - LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { - e.preventDefault(); - return true; - }; - - /** - * binds mouse, keyboard, touch and drag events to the canvas - * @method bindEvents - **/ - LGraphCanvas.prototype.bindEvents = function() { - if (this._events_binded) { - console.warn("LGraphCanvas: events already binded"); - return; - } - - //console.log("pointerevents: bindEvents"); - - var canvas = this.canvas; - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; //hack used when moving canvas between windows - - this._mousedown_callback = this.processMouseDown.bind(this); - this._mousewheel_callback = this.processMouseWheel.bind(this); - // why mousemove and mouseup were not binded here? - this._mousemove_callback = this.processMouseMove.bind(this); - this._mouseup_callback = this.processMouseUp.bind(this); - - //touch events -- TODO IMPLEMENT - //this._touch_callback = this.touchHandler.bind(this); - - LiteGraph.pointerListenerAdd(canvas,"down", this._mousedown_callback, true); //down do not need to store the binded - canvas.addEventListener("mousewheel", this._mousewheel_callback, false); - - LiteGraph.pointerListenerAdd(canvas,"up", this._mouseup_callback, true); // CHECK: ??? binded or not - LiteGraph.pointerListenerAdd(canvas,"move", this._mousemove_callback); - - canvas.addEventListener("contextmenu", this._doNothing); - canvas.addEventListener( - "DOMMouseScroll", - this._mousewheel_callback, - false - ); - - //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents - /*if( 'touchstart' in document.documentElement ) - { - canvas.addEventListener("touchstart", this._touch_callback, true); - canvas.addEventListener("touchmove", this._touch_callback, true); - canvas.addEventListener("touchend", this._touch_callback, true); - canvas.addEventListener("touchcancel", this._touch_callback, true); - }*/ - - //Keyboard ****************** - this._key_callback = this.processKey.bind(this); - - canvas.addEventListener("keydown", this._key_callback, true); - document.addEventListener("keyup", this._key_callback, true); //in document, otherwise it doesn't fire keyup - - //Dropping Stuff over nodes ************************************ - this._ondrop_callback = this.processDrop.bind(this); - - canvas.addEventListener("dragover", this._doNothing, false); - canvas.addEventListener("dragend", this._doNothing, false); - canvas.addEventListener("drop", this._ondrop_callback, false); - canvas.addEventListener("dragenter", this._doReturnTrue, false); - - this._events_binded = true; - }; - - /** - * unbinds mouse events from the canvas - * @method unbindEvents - **/ - LGraphCanvas.prototype.unbindEvents = function() { - if (!this._events_binded) { - console.warn("LGraphCanvas: no events binded"); - return; - } - - //console.log("pointerevents: unbindEvents"); - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - - LiteGraph.pointerListenerRemove(this.canvas,"move", this._mousedown_callback); - LiteGraph.pointerListenerRemove(this.canvas,"up", this._mousedown_callback); - LiteGraph.pointerListenerRemove(this.canvas,"down", this._mousedown_callback); - this.canvas.removeEventListener( - "mousewheel", - this._mousewheel_callback - ); - this.canvas.removeEventListener( - "DOMMouseScroll", - this._mousewheel_callback - ); - this.canvas.removeEventListener("keydown", this._key_callback); - document.removeEventListener("keyup", this._key_callback); - this.canvas.removeEventListener("contextmenu", this._doNothing); - this.canvas.removeEventListener("drop", this._ondrop_callback); - this.canvas.removeEventListener("dragenter", this._doReturnTrue); - - //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents - /*this.canvas.removeEventListener("touchstart", this._touch_callback ); - this.canvas.removeEventListener("touchmove", this._touch_callback ); - this.canvas.removeEventListener("touchend", this._touch_callback ); - this.canvas.removeEventListener("touchcancel", this._touch_callback );*/ - - this._mousedown_callback = null; - this._mousewheel_callback = null; - this._key_callback = null; - this._ondrop_callback = null; - - this._events_binded = false; - }; - - LGraphCanvas.getFileExtension = function(url) { - var question = url.indexOf("?"); - if (question != -1) { - url = url.substr(0, question); - } - var point = url.lastIndexOf("."); - if (point == -1) { - return ""; - } - return url.substr(point + 1).toLowerCase(); - }; - - /** - * this function allows to render the canvas using WebGL instead of Canvas2D - * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL - * @method enableWebGL - **/ - LGraphCanvas.prototype.enableWebGL = function() { - if (typeof GL === "undefined") { - throw "litegl.js must be included to use a WebGL canvas"; - } - if (typeof enableWebGLCanvas === "undefined") { - throw "webglCanvas.js must be included to use this feature"; - } - - this.gl = this.ctx = enableWebGLCanvas(this.canvas); - this.ctx.webgl = true; - this.bgcanvas = this.canvas; - this.bgctx = this.gl; - this.canvas.webgl_enabled = true; - - /* - GL.create({ canvas: this.bgcanvas }); - this.bgctx = enableWebGLCanvas( this.bgcanvas ); - window.gl = this.gl; - */ - }; - - /** - * marks as dirty the canvas, this way it will be rendered again - * - * @class LGraphCanvas - * @method setDirty - * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes) - * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires) - */ - LGraphCanvas.prototype.setDirty = function(fgcanvas, bgcanvas) { - if (fgcanvas) { - this.dirty_canvas = true; - } - if (bgcanvas) { - this.dirty_bgcanvas = true; - } - }; - - /** - * Used to attach the canvas in a popup - * - * @method getCanvasWindow - * @return {window} returns the window where the canvas is attached (the DOM root node) - */ - LGraphCanvas.prototype.getCanvasWindow = function() { - if (!this.canvas) { - return window; - } - var doc = this.canvas.ownerDocument; - return doc.defaultView || doc.parentWindow; - }; - - /** - * starts rendering the content of the canvas when needed - * - * @method startRendering - */ - LGraphCanvas.prototype.startRendering = function() { - if (this.is_rendering) { - return; - } //already rendering - - this.is_rendering = true; - renderFrame.call(this); - - function renderFrame() { - if (!this.pause_rendering) { - this.draw(); - } - - var window = this.getCanvasWindow(); - if (this.is_rendering) { - window.requestAnimationFrame(renderFrame.bind(this)); - } - } - }; - - /** - * stops rendering the content of the canvas (to save resources) - * - * @method stopRendering - */ - LGraphCanvas.prototype.stopRendering = function() { - this.is_rendering = false; - /* - if(this.rendering_timer_id) - { - clearInterval(this.rendering_timer_id); - this.rendering_timer_id = null; - } - */ - }; - - /* LiteGraphCanvas input */ - - //used to block future mouse events (because of im gui) - LGraphCanvas.prototype.blockClick = function() - { - this.block_click = true; - this.last_mouseclick = 0; - } - - LGraphCanvas.prototype.processMouseDown = function(e) { - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - if (!this.graph) { - return; - } - - this.adjustMouseEvent(e); - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - LGraphCanvas.active_canvas = this; - var that = this; - - var x = e.clientX; - var y = e.clientY; - //console.log(y,this.viewport); - //console.log("pointerevents: processMouseDown pointerId:"+e.pointerId+" which:"+e.which+" isPrimary:"+e.isPrimary+" :: x y "+x+" "+y); - - this.ds.viewport = this.viewport; - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - - //move mouse move event to the window in case it drags outside of the canvas - if(!this.options.skip_events) - { - LiteGraph.pointerListenerRemove(this.canvas,"move", this._mousemove_callback); - LiteGraph.pointerListenerAdd(ref_window.document,"move", this._mousemove_callback,true); //catch for the entire window - LiteGraph.pointerListenerAdd(ref_window.document,"up", this._mouseup_callback,true); - } - - if(!is_inside){ - return; - } - - var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes, 5 ); - var skip_dragging = false; - var skip_action = false; - var now = LiteGraph.getTime(); - var is_primary = (e.isPrimary === undefined || !e.isPrimary); - var is_double_click = (now - this.last_mouseclick < 300); - this.mouse[0] = e.clientX; - this.mouse[1] = e.clientY; - this.graph_mouse[0] = e.canvasX; - this.graph_mouse[1] = e.canvasY; - this.last_click_position = [this.mouse[0],this.mouse[1]]; - - if (this.pointer_is_down && is_primary ){ - this.pointer_is_double = true; - //console.log("pointerevents: pointer_is_double start"); - }else{ - this.pointer_is_double = false; - } - this.pointer_is_down = true; - - - this.canvas.focus(); - - LiteGraph.closeAllContextMenus(ref_window); - - if (this.onMouse) - { - if (this.onMouse(e) == true) - return; - } - - //left button mouse / single finger - if (e.which == 1 && !this.pointer_is_double) - { - if (e.ctrlKey) - { - this.dragging_rectangle = new Float32Array(4); - this.dragging_rectangle[0] = e.canvasX; - this.dragging_rectangle[1] = e.canvasY; - this.dragging_rectangle[2] = 1; - this.dragging_rectangle[3] = 1; - skip_action = true; - } - - // clone node ALT dragging - if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && node && this.allow_interaction && !skip_action && !this.read_only) - { - if (cloned = node.clone()){ - cloned.pos[0] += 5; - cloned.pos[1] += 5; - this.graph.add(cloned,false,{doCalcSize: false}); - node = cloned; - skip_action = true; - if (!block_drag_node) { - if (this.allow_dragnodes) { - this.graph.beforeChange(); - this.node_dragged = node; - } - if (!this.selected_nodes[node.id]) { - this.processNodeSelected(node, e); - } - } - } - } - - var clicking_canvas_bg = false; - - //when clicked on top of a node - //and it is not interactive - if (node && (this.allow_interaction || node.flags.allow_interaction) && !skip_action && !this.read_only) { - if (!this.live_mode && !node.flags.pinned) { - this.bringToFront(node); - } //if it wasn't selected? - - //not dragging mouse to connect two slots - if ( this.allow_interaction && !this.connecting_node && !node.flags.collapsed && !this.live_mode ) { - //Search for corner for resize - if ( !skip_action && - node.resizable !== false && node.inResizeCorner(e.canvasX, e.canvasY) - ) { - this.graph.beforeChange(); - this.resizing_node = node; - this.canvas.style.cursor = "se-resize"; - skip_action = true; - } else { - //search for outputs - if (node.outputs) { - for ( var i = 0, l = node.outputs.length; i < l; ++i ) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - link_pos[0] - 15, - link_pos[1] - 10, - 30, - 20 - ) - ) { - this.connecting_node = node; - this.connecting_output = output; - this.connecting_output.slot_index = i; - this.connecting_pos = node.getConnectionPos( false, i ); - this.connecting_slot = i; - - if (LiteGraph.shift_click_do_break_link_from){ - if (e.shiftKey) { - node.disconnectOutput(i); - } - } - - if (is_double_click) { - if (node.onOutputDblClick) { - node.onOutputDblClick(i, e); - } - } else { - if (node.onOutputClick) { - node.onOutputClick(i, e); - } - } - - skip_action = true; - break; - } - } - } - - //search for inputs - if (node.inputs) { - for ( var i = 0, l = node.inputs.length; i < l; ++i ) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - link_pos[0] - 15, - link_pos[1] - 10, - 30, - 20 - ) - ) { - if (is_double_click) { - if (node.onInputDblClick) { - node.onInputDblClick(i, e); - } - } else { - if (node.onInputClick) { - node.onInputClick(i, e); - } - } - - if (input.link !== null) { - var link_info = this.graph.links[ - input.link - ]; //before disconnecting - if (LiteGraph.click_do_break_link_to){ - node.disconnectInput(i); - this.dirty_bgcanvas = true; - skip_action = true; - }else{ - // do same action as has not node ? - } - - if ( - this.allow_reconnect_links || - //this.move_destination_link_without_shift || - e.shiftKey - ) { - if (!LiteGraph.click_do_break_link_to){ - node.disconnectInput(i); - } - this.connecting_node = this.graph._nodes_by_id[ - link_info.origin_id - ]; - this.connecting_slot = - link_info.origin_slot; - this.connecting_output = this.connecting_node.outputs[ - this.connecting_slot - ]; - this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot ); - - this.dirty_bgcanvas = true; - skip_action = true; - } - - - }else{ - // has not node - } - - if (!skip_action){ - // connect from in to out, from to to from - this.connecting_node = node; - this.connecting_input = input; - this.connecting_input.slot_index = i; - this.connecting_pos = node.getConnectionPos( true, i ); - this.connecting_slot = i; - - this.dirty_bgcanvas = true; - skip_action = true; - } - } - } - } - } //not resizing - } - - //it wasn't clicked on the links boxes - if (!skip_action) { - var block_drag_node = false; - if(node && node.flags && node.flags.pinned) { - block_drag_node = true; - } - var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; - - //widgets - var widget = this.processNodeWidgets( node, this.graph_mouse, e ); - if (widget) { - block_drag_node = true; - this.node_widget = [node, widget]; - } - - //double clicking - if (this.allow_interaction && is_double_click && this.selected_nodes[node.id]) { - //double click node - if (node.onDblClick) { - node.onDblClick( e, pos, this ); - } - this.processNodeDblClicked(node); - block_drag_node = true; - } - - //if do not capture mouse - if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) { - block_drag_node = true; - } else { - //open subgraph button - if(node.subgraph && !node.skip_subgraph_button) - { - if ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) { - var that = this; - setTimeout(function() { - that.openSubgraph(node.subgraph); - }, 10); - } - } - - if (this.live_mode) { - clicking_canvas_bg = true; - block_drag_node = true; - } - } - - if (!block_drag_node) { - if (this.allow_dragnodes) { - this.graph.beforeChange(); - this.node_dragged = node; - } - this.processNodeSelected(node, e); - } else { // double-click - /** - * Don't call the function if the block is already selected. - * Otherwise, it could cause the block to be unselected while its panel is open. - */ - if (!node.is_selected) this.processNodeSelected(node, e); - } - - this.dirty_canvas = true; - } - } //clicked outside of nodes - else { - if (!skip_action){ - //search for link connector - if(!this.read_only) { - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var center = link._pos; - if ( - !center || - e.canvasX < center[0] - 4 || - e.canvasX > center[0] + 4 || - e.canvasY < center[1] - 4 || - e.canvasY > center[1] + 4 - ) { - continue; - } - //link clicked - this.showLinkMenu(link, e); - this.over_link_center = null; //clear tooltip - break; - } - } - - this.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY ); - this.selected_group_resizing = false; - if (this.selected_group && !this.read_only ) { - if (e.ctrlKey) { - this.dragging_rectangle = null; - } - - var dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] ); - if (dist * this.ds.scale < 10) { - this.selected_group_resizing = true; - } else { - this.selected_group.recomputeInsideNodes(); - } - } - - if (is_double_click && !this.read_only && this.allow_searchbox) { - this.showSearchBox(e); - e.preventDefault(); - e.stopPropagation(); - } - - clicking_canvas_bg = true; - } - } - - if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) { - //console.log("pointerevents: dragging_canvas start"); - this.dragging_canvas = true; - } - - } else if (e.which == 2) { - //middle button - - if (LiteGraph.middle_click_slot_add_default_node){ - if (node && this.allow_interaction && !skip_action && !this.read_only){ - //not dragging mouse to connect two slots - if ( - !this.connecting_node && - !node.flags.collapsed && - !this.live_mode - ) { - var mClikSlot = false; - var mClikSlot_index = false; - var mClikSlot_isOut = false; - //search for outputs - if (node.outputs) { - for ( var i = 0, l = node.outputs.length; i < l; ++i ) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); - if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { - mClikSlot = output; - mClikSlot_index = i; - mClikSlot_isOut = true; - break; - } - } - } - - //search for inputs - if (node.inputs) { - for ( var i = 0, l = node.inputs.length; i < l; ++i ) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { - mClikSlot = input; - mClikSlot_index = i; - mClikSlot_isOut = false; - break; - } - } - } - //console.log("middleClickSlots? "+mClikSlot+" & "+(mClikSlot_index!==false)); - if (mClikSlot && mClikSlot_index!==false){ - - var alphaPosY = 0.5-((mClikSlot_index+1)/((mClikSlot_isOut?node.outputs.length:node.inputs.length))); - var node_bounding = node.getBounding(); - // estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes - var posRef = [ (!mClikSlot_isOut?node_bounding[0]:node_bounding[0]+node_bounding[2])// + node_bounding[0]/this.canvas.width*150 - ,e.canvasY-80// + node_bounding[0]/this.canvas.width*66 // vertical "derive" - ]; - var nodeCreated = this.createDefaultNodeForSlot({ nodeFrom: !mClikSlot_isOut?null:node - ,slotFrom: !mClikSlot_isOut?null:mClikSlot_index - ,nodeTo: !mClikSlot_isOut?node:null - ,slotTo: !mClikSlot_isOut?mClikSlot_index:null - ,position: posRef //,e: e - ,nodeType: "AUTO" //nodeNewType - ,posAdd:[!mClikSlot_isOut?-30:30, -alphaPosY*130] //-alphaPosY*30] - ,posSizeFix:[!mClikSlot_isOut?-1:0, 0] //-alphaPosY*2*/ - }); - skip_action = true; - } - } - } - } - - if (!skip_action && this.allow_dragcanvas) { - //console.log("pointerevents: dragging_canvas start from middle button"); - this.dragging_canvas = true; - } - - - } else if (e.which == 3 || this.pointer_is_double) { - - //right button - if (this.allow_interaction && !skip_action && !this.read_only){ - - // is it hover a node ? - if (node){ - if(Object.keys(this.selected_nodes).length - && (this.selected_nodes[node.id] || e.shiftKey || e.ctrlKey || e.metaKey) - ){ - // is multiselected or using shift to include the now node - if (!this.selected_nodes[node.id]) this.selectNodes([node],true); // add this if not present - }else{ - // update selection - this.selectNodes([node]); - } - } - - // show menu on this node - this.processContextMenu(node, e); - } - - } - - //TODO - //if(this.node_selected != prev_selected) - // this.onNodeSelectionChange(this.node_selected); - - this.last_mouse[0] = e.clientX; - this.last_mouse[1] = e.clientY; - this.last_mouseclick = LiteGraph.getTime(); - this.last_mouse_dragging = true; - - /* - if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - this.graph.change(); - - //this is to ensure to defocus(blur) if a text input element is on focus - if ( - !ref_window.document.activeElement || - (ref_window.document.activeElement.nodeName.toLowerCase() != - "input" && - ref_window.document.activeElement.nodeName.toLowerCase() != - "textarea") - ) { - e.preventDefault(); - } - e.stopPropagation(); - - if (this.onMouseDown) { - this.onMouseDown(e); - } - - return false; - }; - - /** - * Called when a mouse move event has to be processed - * @method processMouseMove - **/ - LGraphCanvas.prototype.processMouseMove = function(e) { - if (this.autoresize) { - this.resize(); - } - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - if (!this.graph) { - return; - } - - LGraphCanvas.active_canvas = this; - this.adjustMouseEvent(e); - var mouse = [e.clientX, e.clientY]; - this.mouse[0] = mouse[0]; - this.mouse[1] = mouse[1]; - var delta = [ - mouse[0] - this.last_mouse[0], - mouse[1] - this.last_mouse[1] - ]; - this.last_mouse = mouse; - this.graph_mouse[0] = e.canvasX; - this.graph_mouse[1] = e.canvasY; - - //console.log("pointerevents: processMouseMove "+e.pointerId+" "+e.isPrimary); - - if(this.block_click) - { - //console.log("pointerevents: processMouseMove block_click"); - e.preventDefault(); - return false; - } - - e.dragging = this.last_mouse_dragging; - - if (this.node_widget) { - this.processNodeWidgets( - this.node_widget[0], - this.graph_mouse, - e, - this.node_widget[1] - ); - this.dirty_canvas = true; - } - - //get node over - var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes); - - if (this.dragging_rectangle) - { - this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]; - this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]; - this.dirty_canvas = true; - } - else if (this.selected_group && !this.read_only) - { - //moving/resizing a group - if (this.selected_group_resizing) { - this.selected_group.size = [ - e.canvasX - this.selected_group.pos[0], - e.canvasY - this.selected_group.pos[1] - ]; - } else { - var deltax = delta[0] / this.ds.scale; - var deltay = delta[1] / this.ds.scale; - this.selected_group.move(deltax, deltay, e.ctrlKey); - if (this.selected_group._nodes.length) { - this.dirty_canvas = true; - } - } - this.dirty_bgcanvas = true; - } else if (this.dragging_canvas) { - ////console.log("pointerevents: processMouseMove is dragging_canvas"); - this.ds.offset[0] += delta[0] / this.ds.scale; - this.ds.offset[1] += delta[1] / this.ds.scale; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) { - if (this.connecting_node) { - this.dirty_canvas = true; - } - - //remove mouseover flag - for (var i = 0, l = this.graph._nodes.length; i < l; ++i) { - if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) { - //mouse leave - this.graph._nodes[i].mouseOver = false; - if (this.node_over && this.node_over.onMouseLeave) { - this.node_over.onMouseLeave(e); - } - this.node_over = null; - this.dirty_canvas = true; - } - } - - //mouse over a node - if (node) { - - if(node.redraw_on_mouse) - this.dirty_canvas = true; - - //this.canvas.style.cursor = "move"; - if (!node.mouseOver) { - //mouse enter - node.mouseOver = true; - this.node_over = node; - this.dirty_canvas = true; - - if (node.onMouseEnter) { - node.onMouseEnter(e); - } - } - - //in case the node wants to do something - if (node.onMouseMove) { - node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ); - } - - //if dragging a link - if (this.connecting_node) { - - if (this.connecting_output){ - - var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput - - //on top of input - if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { - //mouse on top of the corner box, don't know what to do - } else { - //check if I have a slot below de mouse - var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos ); - if (slot != -1 && node.inputs[slot]) { - var slot_type = node.inputs[slot].type; - if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) { - this._highlight_input = pos; - this._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS - } - } else { - this._highlight_input = null; - this._highlight_input_slot = null; // XXX CHECK THIS - } - } - - }else if(this.connecting_input){ - - var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput - - //on top of output - if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { - //mouse on top of the corner box, don't know what to do - } else { - //check if I have a slot below de mouse - var slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY, pos ); - if (slot != -1 && node.outputs[slot]) { - var slot_type = node.outputs[slot].type; - if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) { - this._highlight_output = pos; - } - } else { - this._highlight_output = null; - } - } - } - } - - //Search for corner - if (this.canvas) { - if (node.inResizeCorner(e.canvasX, e.canvasY)) { - this.canvas.style.cursor = "se-resize"; - } else { - this.canvas.style.cursor = "crosshair"; - } - } - } else { //not over a node - - //search for link connector - var over_link = null; - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var center = link._pos; - if ( - !center || - e.canvasX < center[0] - 4 || - e.canvasX > center[0] + 4 || - e.canvasY < center[1] - 4 || - e.canvasY > center[1] + 4 - ) { - continue; - } - over_link = link; - break; - } - if( over_link != this.over_link_center ) - { - this.over_link_center = over_link; - this.dirty_canvas = true; - } - - if (this.canvas) { - this.canvas.style.cursor = ""; - } - } //end - - //send event to node if capturing input (used with widgets that allow drag outside of the area of the node) - if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) { - this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this); - } - - //node being dragged - if (this.node_dragged && !this.live_mode) { - //console.log("draggin!",this.selected_nodes); - for (var i in this.selected_nodes) { - var n = this.selected_nodes[i]; - n.pos[0] += delta[0] / this.ds.scale; - n.pos[1] += delta[1] / this.ds.scale; - if (!n.is_selected) this.processNodeSelected(n, e); /* - * Don't call the function if the block is already selected. - * Otherwise, it could cause the block to be unselected while dragging. - */ - } - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - - if (this.resizing_node && !this.live_mode) { - //convert mouse to node space - var desired_size = [ e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1] ]; - var min_size = this.resizing_node.computeSize(); - desired_size[0] = Math.max( min_size[0], desired_size[0] ); - desired_size[1] = Math.max( min_size[1], desired_size[1] ); - this.resizing_node.setSize( desired_size ); - - this.canvas.style.cursor = "se-resize"; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - } - - e.preventDefault(); - return false; - }; - - /** - * Called when a mouse up event has to be processed - * @method processMouseUp - **/ - LGraphCanvas.prototype.processMouseUp = function(e) { - - var is_primary = ( e.isPrimary === undefined || e.isPrimary ); - - //early exit for extra pointer - if(!is_primary){ - /*e.stopPropagation(); - e.preventDefault();*/ - //console.log("pointerevents: processMouseUp pointerN_stop "+e.pointerId+" "+e.isPrimary); - return false; - } - - //console.log("pointerevents: processMouseUp "+e.pointerId+" "+e.isPrimary+" :: "+e.clientX+" "+e.clientY); - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - if (!this.graph) - return; - - var window = this.getCanvasWindow(); - var document = window.document; - LGraphCanvas.active_canvas = this; - - //restore the mousemove event back to the canvas - if(!this.options.skip_events) - { - //console.log("pointerevents: processMouseUp adjustEventListener"); - LiteGraph.pointerListenerRemove(document,"move", this._mousemove_callback,true); - LiteGraph.pointerListenerAdd(this.canvas,"move", this._mousemove_callback,true); - LiteGraph.pointerListenerRemove(document,"up", this._mouseup_callback,true); - } - - this.adjustMouseEvent(e); - var now = LiteGraph.getTime(); - e.click_time = now - this.last_mouseclick; - this.last_mouse_dragging = false; - this.last_click_position = null; - - if(this.block_click) - { - //console.log("pointerevents: processMouseUp block_clicks"); - this.block_click = false; //used to avoid sending twice a click in a immediate button - } - - //console.log("pointerevents: processMouseUp which: "+e.which); - - if (e.which == 1) { - - if( this.node_widget ) - { - this.processNodeWidgets( this.node_widget[0], this.graph_mouse, e ); - } - - //left button - this.node_widget = null; - - if (this.selected_group) { - var diffx = - this.selected_group.pos[0] - - Math.round(this.selected_group.pos[0]); - var diffy = - this.selected_group.pos[1] - - Math.round(this.selected_group.pos[1]); - this.selected_group.move(diffx, diffy, e.ctrlKey); - this.selected_group.pos[0] = Math.round( - this.selected_group.pos[0] - ); - this.selected_group.pos[1] = Math.round( - this.selected_group.pos[1] - ); - if (this.selected_group._nodes.length) { - this.dirty_canvas = true; - } - this.selected_group = null; - } - this.selected_group_resizing = false; - - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - - if (this.dragging_rectangle) { - if (this.graph) { - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); - - //compute bounding and flip if left to right - var w = Math.abs(this.dragging_rectangle[2]); - var h = Math.abs(this.dragging_rectangle[3]); - var startx = - this.dragging_rectangle[2] < 0 - ? this.dragging_rectangle[0] - w - : this.dragging_rectangle[0]; - var starty = - this.dragging_rectangle[3] < 0 - ? this.dragging_rectangle[1] - h - : this.dragging_rectangle[1]; - this.dragging_rectangle[0] = startx; - this.dragging_rectangle[1] = starty; - this.dragging_rectangle[2] = w; - this.dragging_rectangle[3] = h; - - // test dragging rect size, if minimun simulate a click - if (!node || (w > 10 && h > 10 )){ - //test against all nodes (not visible because the rectangle maybe start outside - var to_select = []; - for (var i = 0; i < nodes.length; ++i) { - var nodeX = nodes[i]; - nodeX.getBounding(node_bounding); - if ( - !overlapBounding( - this.dragging_rectangle, - node_bounding - ) - ) { - continue; - } //out of the visible area - to_select.push(nodeX); - } - if (to_select.length) { - this.selectNodes(to_select,e.shiftKey); // add to selection with shift - } - }else{ - // will select of update selection - this.selectNodes([node],e.shiftKey||e.ctrlKey); // add to selection add to selection with ctrlKey or shiftKey - } - - } - this.dragging_rectangle = null; - } else if (this.connecting_node) { - //dragging a connection - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - - var connInOrOut = this.connecting_output || this.connecting_input; - var connType = connInOrOut.type; - - //node below mouse - if (node) { - - /* no need to condition on event type.. just another type - if ( - connType == LiteGraph.EVENT && - this.isOverNodeBox(node, e.canvasX, e.canvasY) - ) { - - this.connecting_node.connect( - this.connecting_slot, - node, - LiteGraph.EVENT - ); - - } else {*/ - - //slot below mouse? connect - - if (this.connecting_output){ - - var slot = this.isOverNodeInput( - node, - e.canvasX, - e.canvasY - ); - if (slot != -1) { - this.connecting_node.connect(this.connecting_slot, node, slot); - } else { - //not on top of an input - // look for a good slot - this.connecting_node.connectByType(this.connecting_slot,node,connType); - } - - }else if (this.connecting_input){ - - var slot = this.isOverNodeOutput( - node, - e.canvasX, - e.canvasY - ); - - if (slot != -1) { - node.connect(slot, this.connecting_node, this.connecting_slot); // this is inverted has output-input nature like - } else { - //not on top of an input - // look for a good slot - this.connecting_node.connectByTypeOutput(this.connecting_slot,node,connType); - } - - } - - - //} - - }else{ - - // add menu when releasing link in empty space - if (LiteGraph.release_link_on_empty_shows_menu){ - if (e.shiftKey && this.allow_searchbox){ - if(this.connecting_output){ - this.showSearchBox(e,{node_from: this.connecting_node, slot_from: this.connecting_output, type_filter_in: this.connecting_output.type}); - }else if(this.connecting_input){ - this.showSearchBox(e,{node_to: this.connecting_node, slot_from: this.connecting_input, type_filter_out: this.connecting_input.type}); - } - }else{ - if(this.connecting_output){ - this.showConnectionMenu({nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: e}); - }else if(this.connecting_input){ - this.showConnectionMenu({nodeTo: this.connecting_node, slotTo: this.connecting_input, e: e}); - } - } - } - } - - this.connecting_output = null; - this.connecting_input = null; - this.connecting_pos = null; - this.connecting_node = null; - this.connecting_slot = -1; - } //not dragging connection - else if (this.resizing_node) { - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.graph.afterChange(this.resizing_node); - this.resizing_node = null; - } else if (this.node_dragged) { - //node being dragged? - var node = this.node_dragged; - if ( - node && - e.click_time < 300 && - isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT ) - ) { - node.collapse(); - } - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); - this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); - if (this.graph.config.align_to_grid || this.align_to_grid ) { - this.node_dragged.alignToGrid(); - } - if( this.onNodeMoved ) - this.onNodeMoved( this.node_dragged ); - this.graph.afterChange(this.node_dragged); - this.node_dragged = null; - } //no node being dragged - else { - //get node over - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - - if (!node && e.click_time < 300) { - this.deselectAllNodes(); - } - - this.dirty_canvas = true; - this.dragging_canvas = false; - - if (this.node_over && this.node_over.onMouseUp) { - this.node_over.onMouseUp( e, [ e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1] ], this ); - } - if ( - this.node_capturing_input && - this.node_capturing_input.onMouseUp - ) { - this.node_capturing_input.onMouseUp(e, [ - e.canvasX - this.node_capturing_input.pos[0], - e.canvasY - this.node_capturing_input.pos[1] - ]); - } - } - } else if (e.which == 2) { - //middle button - //trace("middle"); - this.dirty_canvas = true; - this.dragging_canvas = false; - } else if (e.which == 3) { - //right button - //trace("right"); - this.dirty_canvas = true; - this.dragging_canvas = false; - } - - /* - if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - if (is_primary) - { - this.pointer_is_down = false; - this.pointer_is_double = false; - } - - this.graph.change(); - - //console.log("pointerevents: processMouseUp stopPropagation"); - e.stopPropagation(); - e.preventDefault(); - return false; - }; - - /** - * Called when a mouse wheel event has to be processed - * @method processMouseWheel - **/ - LGraphCanvas.prototype.processMouseWheel = function(e) { - if (!this.graph || !this.allow_dragcanvas) { - return; - } - - var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; - - this.adjustMouseEvent(e); - - var x = e.clientX; - var y = e.clientY; - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - if(!is_inside) - return; - - var scale = this.ds.scale; - - if (delta > 0) { - scale *= 1.1; - } else if (delta < 0) { - scale *= 1 / 1.1; - } - - //this.setZoom( scale, [ e.clientX, e.clientY ] ); - this.ds.changeScale(scale, [e.clientX, e.clientY]); - - this.graph.change(); - - e.preventDefault(); - return false; // prevent default - }; - - /** - * returns true if a position (in graph space) is on top of a node little corner box - * @method isOverNodeBox - **/ - LGraphCanvas.prototype.isOverNodeBox = function(node, canvasx, canvasy) { - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - if ( - isInsideRectangle( - canvasx, - canvasy, - node.pos[0] + 2, - node.pos[1] + 2 - title_height, - title_height - 4, - title_height - 4 - ) - ) { - return true; - } - return false; - }; - - /** - * returns the INDEX if a position (in graph space) is on top of a node input slot - * @method isOverNodeInput - **/ - LGraphCanvas.prototype.isOverNodeInput = function( - node, - canvasx, - canvasy, - slot_pos - ) { - if (node.inputs) { - for (var i = 0, l = node.inputs.length; i < l; ++i) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - var is_inside = false; - if (node.horizontal) { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 5, - link_pos[1] - 10, - 10, - 20 - ); - } else { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 10, - link_pos[1] - 5, - 40, - 10 - ); - } - if (is_inside) { - if (slot_pos) { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; - } - return i; - } - } - } - return -1; - }; - - /** - * returns the INDEX if a position (in graph space) is on top of a node output slot - * @method isOverNodeOuput - **/ - LGraphCanvas.prototype.isOverNodeOutput = function( - node, - canvasx, - canvasy, - slot_pos - ) { - if (node.outputs) { - for (var i = 0, l = node.outputs.length; i < l; ++i) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); - var is_inside = false; - if (node.horizontal) { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 5, - link_pos[1] - 10, - 10, - 20 - ); - } else { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 10, - link_pos[1] - 5, - 40, - 10 - ); - } - if (is_inside) { - if (slot_pos) { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; - } - return i; - } - } - } - return -1; - }; - - /** - * process a key event - * @method processKey - **/ - LGraphCanvas.prototype.processKey = function(e) { - if (!this.graph) { - return; - } - - var block_default = false; - //console.log(e); //debug - - if (e.target.localName == "input") { - return; - } - - if (e.type == "keydown") { - if (e.keyCode == 32) { - //space - this.dragging_canvas = true; - block_default = true; - } - - if (e.keyCode == 27) { - //esc - if(this.node_panel) this.node_panel.close(); - if(this.options_panel) this.options_panel.close(); - block_default = true; - } - - //select all Control A - if (e.keyCode == 65 && e.ctrlKey) { - this.selectNodes(); - block_default = true; - } - - if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) { - //copy - if (this.selected_nodes) { - this.copyToClipboard(); - block_default = true; - } - } - - if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) { - //paste - this.pasteFromClipboard(e.shiftKey); - } - - //delete or backspace - if (e.keyCode == 46 || e.keyCode == 8) { - if ( - e.target.localName != "input" && - e.target.localName != "textarea" - ) { - this.deleteSelectedNodes(); - block_default = true; - } - } - - //collapse - //... - - //TODO - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyDown) { - this.selected_nodes[i].onKeyDown(e); - } - } - } - } else if (e.type == "keyup") { - if (e.keyCode == 32) { - // space - this.dragging_canvas = false; - } - - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyUp) { - this.selected_nodes[i].onKeyUp(e); - } - } - } - } - - this.graph.change(); - - if (block_default) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; - } - }; - - LGraphCanvas.prototype.copyToClipboard = function(nodes) { - var clipboard_info = { - nodes: [], - links: [] - }; - var index = 0; - var selected_nodes_array = []; - if (!nodes) nodes = this.selected_nodes; - for (var i in nodes) { - var node = nodes[i]; - if (node.clonable === false) - continue; - node._relative_id = index; - selected_nodes_array.push(node); - index += 1; - } - - for (var i = 0; i < selected_nodes_array.length; ++i) { - var node = selected_nodes_array[i]; - var cloned = node.clone(); - if(!cloned) - { - console.warn("node type not found: " + node.type ); - continue; - } - clipboard_info.nodes.push(cloned.serialize()); - if (node.inputs && node.inputs.length) { - for (var j = 0; j < node.inputs.length; ++j) { - var input = node.inputs[j]; - if (!input || input.link == null) { - continue; - } - var link_info = this.graph.links[input.link]; - if (!link_info) { - continue; - } - var target_node = this.graph.getNodeById( - link_info.origin_id - ); - if (!target_node) { - continue; - } - clipboard_info.links.push([ - target_node._relative_id, - link_info.origin_slot, //j, - node._relative_id, - link_info.target_slot, - target_node.id - ]); - } - } - } - localStorage.setItem( - "litegrapheditor_clipboard", - JSON.stringify(clipboard_info) - ); - }; - - LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) { - // if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior - if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { - return; - } - var data = localStorage.getItem("litegrapheditor_clipboard"); - if (!data) { - return; - } - - this.graph.beforeChange(); - - //create nodes - var clipboard_info = JSON.parse(data); - // calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos - var posMin = false; - var posMinIndexes = false; - for (var i = 0; i < clipboard_info.nodes.length; ++i) { - if (posMin){ - if(posMin[0]>clipboard_info.nodes[i].pos[0]){ - posMin[0] = clipboard_info.nodes[i].pos[0]; - posMinIndexes[0] = i; - } - if(posMin[1]>clipboard_info.nodes[i].pos[1]){ - posMin[1] = clipboard_info.nodes[i].pos[1]; - posMinIndexes[1] = i; - } - } - else{ - posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]]; - posMinIndexes = [i, i]; - } - } - var nodes = []; - for (var i = 0; i < clipboard_info.nodes.length; ++i) { - var node_data = clipboard_info.nodes[i]; - var node = LiteGraph.createNode(node_data.type); - if (node) { - node.configure(node_data); - - //paste in last known mouse position - node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5; - node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5; - - this.graph.add(node,{doProcessChange:false}); - - nodes.push(node); - } - } - - //create links - for (var i = 0; i < clipboard_info.links.length; ++i) { - var link_info = clipboard_info.links[i]; - var origin_node = undefined; - var origin_node_relative_id = link_info[0]; - if (origin_node_relative_id != null) { - origin_node = nodes[origin_node_relative_id]; - } else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { - var origin_node_id = link_info[4]; - if (origin_node_id) { - origin_node = this.graph.getNodeById(origin_node_id); - } - } - var target_node = nodes[link_info[2]]; - if( origin_node && target_node ) - origin_node.connect(link_info[1], target_node, link_info[3]); - else - console.warn("Warning, nodes missing on pasting"); - } - - this.selectNodes(nodes); - - this.graph.afterChange(); - }; - - /** - * process a item drop event on top the canvas - * @method processDrop - **/ - LGraphCanvas.prototype.processDrop = function(e) { - e.preventDefault(); - this.adjustMouseEvent(e); - var x = e.clientX; - var y = e.clientY; - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - if(!is_inside){ - return; - // --- BREAK --- - } - - var pos = [e.canvasX, e.canvasY]; - - - var node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null; - - if (!node) { - var r = null; - if (this.onDropItem) { - r = this.onDropItem(event); - } - if (!r) { - this.checkDropItem(e); - } - return; - } - - if (node.onDropFile || node.onDropData) { - var files = e.dataTransfer.files; - if (files && files.length) { - for (var i = 0; i < files.length; i++) { - var file = e.dataTransfer.files[0]; - var filename = file.name; - var ext = LGraphCanvas.getFileExtension(filename); - //console.log(file); - - if (node.onDropFile) { - node.onDropFile(file); - } - - if (node.onDropData) { - //prepare reader - var reader = new FileReader(); - reader.onload = function(event) { - //console.log(event.target); - var data = event.target.result; - node.onDropData(data, filename, file); - }; - - //read data - var type = file.type.split("/")[0]; - if (type == "text" || type == "") { - reader.readAsText(file); - } else if (type == "image") { - reader.readAsDataURL(file); - } else { - reader.readAsArrayBuffer(file); - } - } - } - } - } - - if (node.onDropItem) { - if (node.onDropItem(event)) { - return true; - } - } - - if (this.onDropItem) { - return this.onDropItem(event); - } - - return false; - }; - - //called if the graph doesn't have a default drop item behaviour - LGraphCanvas.prototype.checkDropItem = function(e) { - if (e.dataTransfer.files.length) { - var file = e.dataTransfer.files[0]; - var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase(); - var nodetype = LiteGraph.node_types_by_file_extension[ext]; - if (nodetype) { - this.graph.beforeChange(); - var node = LiteGraph.createNode(nodetype.type); - node.pos = [e.canvasX, e.canvasY]; - this.graph.add(node); - if (node.onDropFile) { - node.onDropFile(file); - } - this.graph.afterChange(); - } - } - }; - - LGraphCanvas.prototype.processNodeDblClicked = function(n) { - if (this.onShowNodePanel) { - this.onShowNodePanel(n); - } - - if (this.onNodeDblClicked) { - this.onNodeDblClicked(n); - } - - this.setDirty(true); - }; - - LGraphCanvas.prototype.processNodeSelected = function(node, e) { - this.selectNode(node, e && (e.shiftKey || e.ctrlKey || this.multi_select)); - if (this.onNodeSelected) { - this.onNodeSelected(node); - } - }; - - /** - * selects a given node (or adds it to the current selection) - * @method selectNode - **/ - LGraphCanvas.prototype.selectNode = function( - node, - add_to_current_selection - ) { - if (node == null) { - this.deselectAllNodes(); - } else { - this.selectNodes([node], add_to_current_selection); - } - }; - - /** - * selects several nodes (or adds them to the current selection) - * @method selectNodes - **/ - LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) - { - if (!add_to_current_selection) { - this.deselectAllNodes(); - } - - nodes = nodes || this.graph._nodes; - if (typeof nodes == "string") nodes = [nodes]; - for (var i in nodes) { - var node = nodes[i]; - if (node.is_selected) { - this.deselectNode(node); - continue; - } - - if (!node.is_selected && node.onSelected) { - node.onSelected(); - } - node.is_selected = true; - this.selected_nodes[node.id] = node; - - if (node.inputs) { - for (var j = 0; j < node.inputs.length; ++j) { - this.highlighted_links[node.inputs[j].link] = true; - } - } - if (node.outputs) { - for (var j = 0; j < node.outputs.length; ++j) { - var out = node.outputs[j]; - if (out.links) { - for (var k = 0; k < out.links.length; ++k) { - this.highlighted_links[out.links[k]] = true; - } - } - } - } - } - - if( this.onSelectionChange ) - this.onSelectionChange( this.selected_nodes ); - - this.setDirty(true); - }; - - /** - * removes a node from the current selection - * @method deselectNode - **/ - LGraphCanvas.prototype.deselectNode = function(node) { - if (!node.is_selected) { - return; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - - //remove highlighted - if (node.inputs) { - for (var i = 0; i < node.inputs.length; ++i) { - delete this.highlighted_links[node.inputs[i].link]; - } - } - if (node.outputs) { - for (var i = 0; i < node.outputs.length; ++i) { - var out = node.outputs[i]; - if (out.links) { - for (var j = 0; j < out.links.length; ++j) { - delete this.highlighted_links[out.links[j]]; - } - } - } - } - }; - - /** - * removes all nodes from the current selection - * @method deselectAllNodes - **/ - LGraphCanvas.prototype.deselectAllNodes = function() { - if (!this.graph) { - return; - } - var nodes = this.graph._nodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var node = nodes[i]; - if (!node.is_selected) { - continue; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - if( this.onSelectionChange ) - this.onSelectionChange( this.selected_nodes ); - this.setDirty(true); - }; - - /** - * deletes all nodes in the current selection from the graph - * @method deleteSelectedNodes - **/ - LGraphCanvas.prototype.deleteSelectedNodes = function() { - - this.graph.beforeChange(); - - for (var i in this.selected_nodes) { - var node = this.selected_nodes[i]; - - if(node.block_delete) - continue; - - //autoconnect when possible (very basic, only takes into account first input-output) - if(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) - { - var input_link = node.graph.links[ node.inputs[0].link ]; - var output_link = node.graph.links[ node.outputs[0].links[0] ]; - var input_node = node.getInputNode(0); - var output_node = node.getOutputNodes(0)[0]; - if(input_node && output_node) - input_node.connect( input_link.origin_slot, output_node, output_link.target_slot ); - } - this.graph.remove(node); - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - this.setDirty(true); - this.graph.afterChange(); - }; - - /** - * centers the camera on a given node - * @method centerOnNode - **/ - LGraphCanvas.prototype.centerOnNode = function(node) { - this.ds.offset[0] = - -node.pos[0] - - node.size[0] * 0.5 + - (this.canvas.width * 0.5) / this.ds.scale; - this.ds.offset[1] = - -node.pos[1] - - node.size[1] * 0.5 + - (this.canvas.height * 0.5) / this.ds.scale; - this.setDirty(true, true); - }; - - /** - * adds some useful properties to a mouse event, like the position in graph coordinates - * @method adjustMouseEvent - **/ - LGraphCanvas.prototype.adjustMouseEvent = function(e) { - var clientX_rel = 0; - var clientY_rel = 0; - - if (this.canvas) { - var b = this.canvas.getBoundingClientRect(); - clientX_rel = e.clientX - b.left; - clientY_rel = e.clientY - b.top; - } else { - clientX_rel = e.clientX; - clientY_rel = e.clientY; - } - - e.deltaX = clientX_rel - this.last_mouse_position[0]; - e.deltaY = clientY_rel- this.last_mouse_position[1]; - - this.last_mouse_position[0] = clientX_rel; - this.last_mouse_position[1] = clientY_rel; - - e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0]; - e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1]; - - //console.log("pointerevents: adjustMouseEvent "+e.clientX+":"+e.clientY+" "+clientX_rel+":"+clientY_rel+" "+e.canvasX+":"+e.canvasY); - }; - - /** - * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom - * @method setZoom - **/ - LGraphCanvas.prototype.setZoom = function(value, zooming_center) { - this.ds.changeScale(value, zooming_center); - /* - if(!zooming_center && this.canvas) - zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5]; - - var center = this.convertOffsetToCanvas( zooming_center ); - - this.ds.scale = value; - - if(this.scale > this.max_zoom) - this.scale = this.max_zoom; - else if(this.scale < this.min_zoom) - this.scale = this.min_zoom; - - var new_center = this.convertOffsetToCanvas( zooming_center ); - var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]]; - - this.offset[0] += delta_offset[0]; - this.offset[1] += delta_offset[1]; - */ - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - }; - - /** - * converts a coordinate from graph coordinates to canvas2D coordinates - * @method convertOffsetToCanvas - **/ - LGraphCanvas.prototype.convertOffsetToCanvas = function(pos, out) { - return this.ds.convertOffsetToCanvas(pos, out); - }; - - /** - * converts a coordinate from Canvas2D coordinates to graph space - * @method convertCanvasToOffset - **/ - LGraphCanvas.prototype.convertCanvasToOffset = function(pos, out) { - return this.ds.convertCanvasToOffset(pos, out); - }; - - //converts event coordinates from canvas2D to graph coordinates - LGraphCanvas.prototype.convertEventToCanvasOffset = function(e) { - var rect = this.canvas.getBoundingClientRect(); - return this.convertCanvasToOffset([ - e.clientX - rect.left, - e.clientY - rect.top - ]); - }; - - /** - * brings a node to front (above all other nodes) - * @method bringToFront - **/ - LGraphCanvas.prototype.bringToFront = function(node) { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } - - this.graph._nodes.splice(i, 1); - this.graph._nodes.push(node); - }; - - /** - * sends a node to the back (below all other nodes) - * @method sendToBack - **/ - LGraphCanvas.prototype.sendToBack = function(node) { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } - - this.graph._nodes.splice(i, 1); - this.graph._nodes.unshift(node); - }; - - /* Interaction */ - - /* LGraphCanvas render */ - var temp = new Float32Array(4); - - /** - * checks which nodes are visible (inside the camera area) - * @method computeVisibleNodes - **/ - LGraphCanvas.prototype.computeVisibleNodes = function(nodes, out) { - var visible_nodes = out || []; - visible_nodes.length = 0; - nodes = nodes || this.graph._nodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var n = nodes[i]; - - //skip rendering nodes in live mode - if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) { - continue; - } - - if (!overlapBounding(this.visible_area, n.getBounding(temp, true))) { - continue; - } //out of the visible area - - visible_nodes.push(n); - } - return visible_nodes; - }; - - /** - * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) - * @method draw - **/ - LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { - if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) { - return; - } - - //fps counting - var now = LiteGraph.getTime(); - this.render_time = (now - this.last_draw_time) * 0.001; - this.last_draw_time = now; - - if (this.graph) { - this.ds.computeVisibleArea(this.viewport); - } - - if ( - this.dirty_bgcanvas || - force_bgcanvas || - this.always_render_background || - (this.graph && - this.graph._last_trigger_time && - now - this.graph._last_trigger_time < 1000) - ) { - this.drawBackCanvas(); - } - - if (this.dirty_canvas || force_canvas) { - this.drawFrontCanvas(); - } - - this.fps = this.render_time ? 1.0 / this.render_time : 0; - this.frame += 1; - }; - - /** - * draws the front canvas (the one containing all the nodes) - * @method drawFrontCanvas - **/ - LGraphCanvas.prototype.drawFrontCanvas = function() { - this.dirty_canvas = false; - - if (!this.ctx) { - this.ctx = this.bgcanvas.getContext("2d"); - } - var ctx = this.ctx; - if (!ctx) { - //maybe is using webgl... - return; - } - - var canvas = this.canvas; - if ( ctx.start2D && !this.viewport ) { - ctx.start2D(); - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - - //clip dirty area if there is one, otherwise work in full canvas - var area = this.viewport || this.dirty_area; - if (area) { - ctx.save(); - ctx.beginPath(); - ctx.rect( area[0],area[1],area[2],area[3] ); - ctx.clip(); - } - - //clear - //canvas.width = canvas.width; - if (this.clear_background) { - if(area) - ctx.clearRect( area[0],area[1],area[2],area[3] ); - else - ctx.clearRect(0, 0, canvas.width, canvas.height); - } - - //draw bg canvas - if (this.bgcanvas == this.canvas) { - this.drawBackCanvas(); - } else { - ctx.drawImage( this.bgcanvas, 0, 0 ); - } - - //rendering - if (this.onRender) { - this.onRender(canvas, ctx); - } - - //info widget - if (this.show_info) { - this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0 ); - } - - if (this.graph) { - //apply transformations - ctx.save(); - this.ds.toCanvasContext(ctx); - - //draw nodes - var drawn_nodes = 0; - var visible_nodes = this.computeVisibleNodes( - null, - this.visible_nodes - ); - - for (var i = 0; i < visible_nodes.length; ++i) { - var node = visible_nodes[i]; - - //transform coords system - ctx.save(); - ctx.translate(node.pos[0], node.pos[1]); - - //Draw - this.drawNode(node, ctx); - drawn_nodes += 1; - - //Restore - ctx.restore(); - } - - //on top (debug) - if (this.render_execution_order) { - this.drawExecutionOrder(ctx); - } - - //connections ontop? - if (this.graph.config.links_ontop) { - if (!this.live_mode) { - this.drawConnections(ctx); - } - } - - //current connection (the one being dragged by the mouse) - if (this.connecting_pos != null) { - ctx.lineWidth = this.connections_width; - var link_color = null; - - var connInOrOut = this.connecting_output || this.connecting_input; - - var connType = connInOrOut.type; - var connDir = connInOrOut.dir; - if(connDir == null) - { - if (this.connecting_output) - connDir = this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT; - else - connDir = this.connecting_node.horizontal ? LiteGraph.UP : LiteGraph.LEFT; - } - var connShape = connInOrOut.shape; - - switch (connType) { - case LiteGraph.EVENT: - link_color = LiteGraph.EVENT_LINK_COLOR; - break; - default: - link_color = LiteGraph.CONNECTING_LINK_COLOR; - } - - //the connection being dragged by the mouse - this.renderLink( - ctx, - this.connecting_pos, - [this.graph_mouse[0], this.graph_mouse[1]], - null, - false, - null, - link_color, - connDir, - LiteGraph.CENTER - ); - - ctx.beginPath(); - if ( - connType === LiteGraph.EVENT || - connShape === LiteGraph.BOX_SHAPE - ) { - ctx.rect( - this.connecting_pos[0] - 6 + 0.5, - this.connecting_pos[1] - 5 + 0.5, - 14, - 10 - ); - ctx.fill(); - ctx.beginPath(); - ctx.rect( - this.graph_mouse[0] - 6 + 0.5, - this.graph_mouse[1] - 5 + 0.5, - 14, - 10 - ); - } else if (connShape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5); - ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5); - ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5); - ctx.closePath(); - } - else { - ctx.arc( - this.connecting_pos[0], - this.connecting_pos[1], - 4, - 0, - Math.PI * 2 - ); - ctx.fill(); - ctx.beginPath(); - ctx.arc( - this.graph_mouse[0], - this.graph_mouse[1], - 4, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - - ctx.fillStyle = "#ffcc00"; - if (this._highlight_input) { - ctx.beginPath(); - var shape = this._highlight_input_slot.shape; - if (shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5); - ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5); - ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5); - ctx.closePath(); - } else { - ctx.arc( - this._highlight_input[0], - this._highlight_input[1], - 6, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - } - if (this._highlight_output) { - ctx.beginPath(); - if (shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5); - ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5); - ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5); - ctx.closePath(); - } else { - ctx.arc( - this._highlight_output[0], - this._highlight_output[1], - 6, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - } - } - - //the selection rectangle - if (this.dragging_rectangle) { - ctx.strokeStyle = "#FFF"; - ctx.strokeRect( - this.dragging_rectangle[0], - this.dragging_rectangle[1], - this.dragging_rectangle[2], - this.dragging_rectangle[3] - ); - } - - //on top of link center - if(this.over_link_center && this.render_link_tooltip) - this.drawLinkTooltip( ctx, this.over_link_center ); - else - if(this.onDrawLinkTooltip) //to remove - this.onDrawLinkTooltip(ctx,null); - - //custom info - if (this.onDrawForeground) { - this.onDrawForeground(ctx, this.visible_rect); - } - - ctx.restore(); - } - - //draws panel in the corner - if (this._graph_stack && this._graph_stack.length) { - this.drawSubgraphPanel( ctx ); - } - - - if (this.onDrawOverlay) { - this.onDrawOverlay(ctx); - } - - if (area){ - ctx.restore(); - } - - if (ctx.finish2D) { - //this is a function I use in webgl renderer - ctx.finish2D(); - } - }; - - /** - * draws the panel in the corner that shows subgraph properties - * @method drawSubgraphPanel - **/ - LGraphCanvas.prototype.drawSubgraphPanel = function (ctx) { - var subgraph = this.graph; - var subnode = subgraph._subgraph_node; - if (!subnode) { - console.warn("subgraph without subnode"); - return; - } - this.drawSubgraphPanelLeft(subgraph, subnode, ctx) - this.drawSubgraphPanelRight(subgraph, subnode, ctx) - } - - LGraphCanvas.prototype.drawSubgraphPanelLeft = function (subgraph, subnode, ctx) { - var num = subnode.inputs ? subnode.inputs.length : 0; - var w = 200; - var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); - - ctx.fillStyle = "#111"; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]); - ctx.fill(); - ctx.globalAlpha = 1; - - ctx.fillStyle = "#888"; - ctx.font = "14px Arial"; - ctx.textAlign = "left"; - ctx.fillText("Graph Inputs", 20, 34); - // var pos = this.mouse; - - if (this.drawButton(w - 20, 20, 20, 20, "X", "#151515")) { - this.closeSubgraph(); - return; - } - - var y = 50; - ctx.font = "14px Arial"; - if (subnode.inputs) - for (var i = 0; i < subnode.inputs.length; ++i) { - var input = subnode.inputs[i]; - if (input.not_subgraph_input) - continue; - - //input button clicked - if (this.drawButton(20, y + 2, w - 20, h - 2)) { - var type = subnode.constructor.input_node_type || "graph/input"; - this.graph.beforeChange(); - var newnode = LiteGraph.createNode(type); - if (newnode) { - subgraph.add(newnode); - this.block_click = false; - this.last_click_position = null; - this.selectNodes([newnode]); - this.node_dragged = newnode; - this.dragging_canvas = false; - newnode.setProperty("name", input.name); - newnode.setProperty("type", input.type); - this.node_dragged.pos[0] = this.graph_mouse[0] - 5; - this.node_dragged.pos[1] = this.graph_mouse[1] - 5; - this.graph.afterChange(); - } - else - console.error("graph input node not found:", type); - } - ctx.fillStyle = "#9C9"; - ctx.beginPath(); - ctx.arc(w - 16, y + h * 0.5, 5, 0, 2 * Math.PI); - ctx.fill(); - ctx.fillStyle = "#AAA"; - ctx.fillText(input.name, 30, y + h * 0.75); - // var tw = ctx.measureText(input.name); - ctx.fillStyle = "#777"; - ctx.fillText(input.type, 130, y + h * 0.75); - y += h; - } - //add + button - if (this.drawButton(20, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { - this.showSubgraphPropertiesDialog(subnode); - } - } - LGraphCanvas.prototype.drawSubgraphPanelRight = function (subgraph, subnode, ctx) { - var num = subnode.outputs ? subnode.outputs.length : 0; - var canvas_w = this.bgcanvas.width - var w = 200; - var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); - - ctx.fillStyle = "#111"; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]); - ctx.fill(); - ctx.globalAlpha = 1; - - ctx.fillStyle = "#888"; - ctx.font = "14px Arial"; - ctx.textAlign = "left"; - var title_text = "Graph Outputs" - var tw = ctx.measureText(title_text).width - ctx.fillText(title_text, (canvas_w - tw) - 20, 34); - // var pos = this.mouse; - if (this.drawButton(canvas_w - w, 20, 20, 20, "X", "#151515")) { - this.closeSubgraph(); - return; - } - - var y = 50; - ctx.font = "14px Arial"; - if (subnode.outputs) - for (var i = 0; i < subnode.outputs.length; ++i) { - var output = subnode.outputs[i]; - if (output.not_subgraph_input) - continue; - - //output button clicked - if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2)) { - var type = subnode.constructor.output_node_type || "graph/output"; - this.graph.beforeChange(); - var newnode = LiteGraph.createNode(type); - if (newnode) { - subgraph.add(newnode); - this.block_click = false; - this.last_click_position = null; - this.selectNodes([newnode]); - this.node_dragged = newnode; - this.dragging_canvas = false; - newnode.setProperty("name", output.name); - newnode.setProperty("type", output.type); - this.node_dragged.pos[0] = this.graph_mouse[0] - 5; - this.node_dragged.pos[1] = this.graph_mouse[1] - 5; - this.graph.afterChange(); - } - else - console.error("graph input node not found:", type); - } - ctx.fillStyle = "#9C9"; - ctx.beginPath(); - ctx.arc(canvas_w - w + 16, y + h * 0.5, 5, 0, 2 * Math.PI); - ctx.fill(); - ctx.fillStyle = "#AAA"; - ctx.fillText(output.name, canvas_w - w + 30, y + h * 0.75); - // var tw = ctx.measureText(input.name); - ctx.fillStyle = "#777"; - ctx.fillText(output.type, canvas_w - w + 130, y + h * 0.75); - y += h; - } - //add + button - if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { - this.showSubgraphPropertiesDialogRight(subnode); - } - } - //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm - LGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor ) - { - var ctx = this.ctx; - bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR; - hovercolor = hovercolor || "#555"; - textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR; - var pos = this.ds.convertOffsetToCanvas(this.graph_mouse); - var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - pos = this.last_click_position ? [this.last_click_position[0], this.last_click_position[1]] : null; - if(pos) { - var rect = this.canvas.getBoundingClientRect(); - pos[0] -= rect.left; - pos[1] -= rect.top; - } - var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - - ctx.fillStyle = hover ? hovercolor : bgcolor; - if(clicked) - ctx.fillStyle = "#AAA"; - ctx.beginPath(); - ctx.roundRect(x,y,w,h,[4] ); - ctx.fill(); - - if(text != null) - { - if(text.constructor == String) - { - ctx.fillStyle = textcolor; - ctx.textAlign = "center"; - ctx.font = ((h * 0.65)|0) + "px Arial"; - ctx.fillText( text, x + w * 0.5,y + h * 0.75 ); - ctx.textAlign = "left"; - } - } - - var was_clicked = clicked && !this.block_click; - if(clicked) - this.blockClick(); - return was_clicked; - } - - LGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click ) - { - var pos = this.mouse; - var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - pos = this.last_click_position; - var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - var was_clicked = clicked && !this.block_click; - if(clicked && hold_click) - this.blockClick(); - return was_clicked; - } - - /** - * draws some useful stats in the corner of the canvas - * @method renderInfo - **/ - LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { - x = x || 10; - y = y || this.canvas.offsetHeight - 80; - - ctx.save(); - ctx.translate(x, y); - - ctx.font = "10px Arial"; - ctx.fillStyle = "#888"; - ctx.textAlign = "left"; - if (this.graph) { - ctx.fillText( "T: " + this.graph.globaltime.toFixed(2) + "s", 5, 13 * 1 ); - ctx.fillText("I: " + this.graph.iteration, 5, 13 * 2 ); - ctx.fillText("N: " + this.graph._nodes.length + " [" + this.visible_nodes.length + "]", 5, 13 * 3 ); - ctx.fillText("V: " + this.graph._version, 5, 13 * 4); - ctx.fillText("FPS:" + this.fps.toFixed(2), 5, 13 * 5); - } else { - ctx.fillText("No graph selected", 5, 13 * 1); - } - ctx.restore(); - }; - - /** - * draws the back canvas (the one containing the background and the connections) - * @method drawBackCanvas - **/ - LGraphCanvas.prototype.drawBackCanvas = function() { - var canvas = this.bgcanvas; - if ( - canvas.width != this.canvas.width || - canvas.height != this.canvas.height - ) { - canvas.width = this.canvas.width; - canvas.height = this.canvas.height; - } - - if (!this.bgctx) { - this.bgctx = this.bgcanvas.getContext("2d"); - } - var ctx = this.bgctx; - if (ctx.start) { - ctx.start(); - } - - var viewport = this.viewport || [0,0,ctx.canvas.width,ctx.canvas.height]; - - //clear - if (this.clear_background) { - ctx.clearRect( viewport[0], viewport[1], viewport[2], viewport[3] ); - } - - //show subgraph stack header - if (this._graph_stack && this._graph_stack.length) { - ctx.save(); - var parent_graph = this._graph_stack[this._graph_stack.length - 1]; - var subgraph_node = this.graph._subgraph_node; - ctx.strokeStyle = subgraph_node.bgcolor; - ctx.lineWidth = 10; - ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2); - ctx.lineWidth = 1; - ctx.font = "40px Arial"; - ctx.textAlign = "center"; - ctx.fillStyle = subgraph_node.bgcolor || "#AAA"; - var title = ""; - for (var i = 1; i < this._graph_stack.length; ++i) { - title += - this._graph_stack[i]._subgraph_node.getTitle() + " >> "; - } - ctx.fillText( - title + subgraph_node.getTitle(), - canvas.width * 0.5, - 40 - ); - ctx.restore(); - } - - var bg_already_painted = false; - if (this.onRenderBackground) { - bg_already_painted = this.onRenderBackground(canvas, ctx); - } - - //reset in case of error - if ( !this.viewport ) - { - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - this.visible_links.length = 0; - - if (this.graph) { - //apply transformations - ctx.save(); - this.ds.toCanvasContext(ctx); - - //render BG - if ( this.ds.scale < 1.5 && !bg_already_painted && this.clear_background_color ) - { - ctx.fillStyle = this.clear_background_color; - ctx.fillRect( - this.visible_area[0], - this.visible_area[1], - this.visible_area[2], - this.visible_area[3] - ); - } - - if ( - this.background_image && - this.ds.scale > 0.5 && - !bg_already_painted - ) { - if (this.zoom_modify_alpha) { - ctx.globalAlpha = - (1.0 - 0.5 / this.ds.scale) * this.editor_alpha; - } else { - ctx.globalAlpha = this.editor_alpha; - } - ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = - if ( - !this._bg_img || - this._bg_img.name != this.background_image - ) { - this._bg_img = new Image(); - this._bg_img.name = this.background_image; - this._bg_img.src = this.background_image; - var that = this; - this._bg_img.onload = function() { - that.draw(true, true); - }; - } - - var pattern = null; - if (this._pattern == null && this._bg_img.width > 0) { - pattern = ctx.createPattern(this._bg_img, "repeat"); - this._pattern_img = this._bg_img; - this._pattern = pattern; - } else { - pattern = this._pattern; - } - if (pattern) { - ctx.fillStyle = pattern; - ctx.fillRect( - this.visible_area[0], - this.visible_area[1], - this.visible_area[2], - this.visible_area[3] - ); - ctx.fillStyle = "transparent"; - } - - ctx.globalAlpha = 1.0; - ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled - } - - //groups - if (this.graph._groups.length && !this.live_mode) { - this.drawGroups(canvas, ctx); - } - - if (this.onDrawBackground) { - this.onDrawBackground(ctx, this.visible_area); - } - if (this.onBackgroundRender) { - //LEGACY - console.error( - "WARNING! onBackgroundRender deprecated, now is named onDrawBackground " - ); - this.onBackgroundRender = null; - } - - //DEBUG: show clipping area - //ctx.fillStyle = "red"; - //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20); - - //bg - if (this.render_canvas_border) { - ctx.strokeStyle = "#235"; - ctx.strokeRect(0, 0, canvas.width, canvas.height); - } - - if (this.render_connections_shadows) { - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = 6; - } else { - ctx.shadowColor = "rgba(0,0,0,0)"; - } - - //draw connections - if (!this.live_mode) { - this.drawConnections(ctx); - } - - ctx.shadowColor = "rgba(0,0,0,0)"; - - //restore state - ctx.restore(); - } - - if (ctx.finish) { - ctx.finish(); - } - - this.dirty_bgcanvas = false; - this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas - }; - - var temp_vec2 = new Float32Array(2); - - /** - * draws the given node inside the canvas - * @method drawNode - **/ - LGraphCanvas.prototype.drawNode = function(node, ctx) { - var glow = false; - this.current_node = node; - - var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR; - var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; - - //shadow and glow - if (node.mouseOver) { - glow = true; - } - - var low_quality = this.ds.scale < 0.6; //zoomed out - - //only render if it forces it to do it - if (this.live_mode) { - if (!node.flags.collapsed) { - ctx.shadowColor = "transparent"; - if (node.onDrawForeground) { - node.onDrawForeground(ctx, this, this.canvas); - } - } - return; - } - - var editor_alpha = this.editor_alpha; - ctx.globalAlpha = editor_alpha; - - if (this.render_shadows && !low_quality) { - ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; - ctx.shadowOffsetX = 2 * this.ds.scale; - ctx.shadowOffsetY = 2 * this.ds.scale; - ctx.shadowBlur = 3 * this.ds.scale; - } else { - ctx.shadowColor = "transparent"; - } - - //custom draw collapsed method (draw after shadows because they are affected) - if ( - node.flags.collapsed && - node.onDrawCollapsed && - node.onDrawCollapsed(ctx, this) == true - ) { - return; - } - - //clip if required (mask) - var shape = node._shape || LiteGraph.BOX_SHAPE; - var size = temp_vec2; - temp_vec2.set(node.size); - var horizontal = node.horizontal; // || node.flags.horizontal; - - if (node.flags.collapsed) { - ctx.font = this.inner_text_font; - var title = node.getTitle ? node.getTitle() : node.title; - if (title != null) { - node._collapsed_width = Math.min( - node.size[0], - ctx.measureText(title).width + - LiteGraph.NODE_TITLE_HEIGHT * 2 - ); //LiteGraph.NODE_COLLAPSED_WIDTH; - size[0] = node._collapsed_width; - size[1] = 0; - } - } - - if (node.clip_area) { - //Start clipping - ctx.save(); - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) { - ctx.rect(0, 0, size[0], size[1]); - } else if (shape == LiteGraph.ROUND_SHAPE) { - ctx.roundRect(0, 0, size[0], size[1], [10]); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5, - 0, - Math.PI * 2 - ); - } - ctx.clip(); - } - - //draw shape - if (node.has_errors) { - bgcolor = "red"; - } - this.drawNodeShape( - node, - ctx, - size, - color, - bgcolor, - node.is_selected, - node.mouseOver - ); - ctx.shadowColor = "transparent"; - - //draw foreground - if (node.onDrawForeground) { - node.onDrawForeground(ctx, this, this.canvas); - } - - //connection slots - ctx.textAlign = horizontal ? "center" : "left"; - ctx.font = this.inner_text_font; - - var render_text = !low_quality; - - var out_slot = this.connecting_output; - var in_slot = this.connecting_input; - ctx.lineWidth = 1; - - var max_y = 0; - var slot_pos = new Float32Array(2); //to reuse - - //render inputs and outputs - if (!node.flags.collapsed) { - //input connection slots - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - - var slot_type = slot.type; - var slot_shape = slot.shape; - - ctx.globalAlpha = editor_alpha; - //change opacity of incompatible slots when dragging a connection - if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) { - ctx.globalAlpha = 0.4 * editor_alpha; - } - - ctx.fillStyle = - slot.link != null - ? slot.color_on || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.input_on - : slot.color_off || - this.default_connection_color_byTypeOff[slot_type] || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.input_off; - - var pos = node.getConnectionPos(true, i, slot_pos); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; - if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; - } - - ctx.beginPath(); - - if (slot_type == "array"){ - slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead? - } - - var doStroke = true; - - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - if (horizontal) { - ctx.rect( - pos[0] - 5 + 0.5, - pos[1] - 8 + 0.5, - 10, - 14 - ); - } else { - ctx.rect( - pos[0] - 6 + 0.5, - pos[1] - 5 + 0.5, - 14, - 10 - ); - } - } else if (slot_shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); - ctx.closePath(); - } else if (slot_shape === LiteGraph.GRID_SHAPE) { - ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); - ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); - ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); - doStroke = false; - } else { - if(low_quality) - ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster - else - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - ctx.fill(); - - //render name - if (render_text) { - var text = slot.label != null ? slot.label : slot.name; - if (text) { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.UP) { - ctx.fillText(text, pos[0], pos[1] - 10); - } else { - ctx.fillText(text, pos[0] + 10, pos[1] + 5); - } - } - } - } - } - - //output connection slots - - ctx.textAlign = horizontal ? "center" : "right"; - ctx.strokeStyle = "black"; - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - - var slot_type = slot.type; - var slot_shape = slot.shape; - - //change opacity of incompatible slots when dragging a connection - if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) { - ctx.globalAlpha = 0.4 * editor_alpha; - } - - var pos = node.getConnectionPos(false, i, slot_pos); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; - if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; - } - - ctx.fillStyle = - slot.links && slot.links.length - ? slot.color_on || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.output_on - : slot.color_off || - this.default_connection_color_byTypeOff[slot_type] || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.output_off; - ctx.beginPath(); - //ctx.rect( node.size[0] - 14,i*14,10,10); - - if (slot_type == "array"){ - slot_shape = LiteGraph.GRID_SHAPE; - } - - var doStroke = true; - - if ( - slot_type === LiteGraph.EVENT || - slot_shape === LiteGraph.BOX_SHAPE - ) { - if (horizontal) { - ctx.rect( - pos[0] - 5 + 0.5, - pos[1] - 8 + 0.5, - 10, - 14 - ); - } else { - ctx.rect( - pos[0] - 6 + 0.5, - pos[1] - 5 + 0.5, - 14, - 10 - ); - } - } else if (slot_shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); - ctx.closePath(); - } else if (slot_shape === LiteGraph.GRID_SHAPE) { - ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); - ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); - ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); - doStroke = false; - } else { - if(low_quality) - ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); - else - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - - //trigger - //if(slot.node_id != null && slot.slot == -1) - // ctx.fillStyle = "#F85"; - - //if(slot.links != null && slot.links.length) - ctx.fill(); - if(!low_quality && doStroke) - ctx.stroke(); - - //render output name - if (render_text) { - var text = slot.label != null ? slot.label : slot.name; - if (text) { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.DOWN) { - ctx.fillText(text, pos[0], pos[1] - 8); - } else { - ctx.fillText(text, pos[0] - 10, pos[1] + 5); - } - } - } - } - } - - ctx.textAlign = "left"; - ctx.globalAlpha = 1; - - if (node.widgets) { - var widgets_y = max_y; - if (horizontal || node.widgets_up) { - widgets_y = 2; - } - if( node.widgets_start_y != null ) - widgets_y = node.widgets_start_y; - this.drawNodeWidgets( - node, - widgets_y, - ctx, - this.node_widget && this.node_widget[0] == node - ? this.node_widget[1] - : null - ); - } - } else if (this.render_collapsed_slots) { - //if collapsed - var input_slot = null; - var output_slot = null; - - //get first connected slot to render - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - if (slot.link == null) { - continue; - } - input_slot = slot; - break; - } - } - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - if (!slot.links || !slot.links.length) { - continue; - } - output_slot = slot; - } - } - - if (input_slot) { - var x = 0; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { - x = node._collapsed_width * 0.5; - y = -LiteGraph.NODE_TITLE_HEIGHT; - } - ctx.fillStyle = "#686"; - ctx.beginPath(); - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - ctx.rect(x - 7 + 0.5, y - 4, 14, 8); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(x + 8, y); - ctx.lineTo(x + -4, y - 4); - ctx.lineTo(x + -4, y + 4); - ctx.closePath(); - } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); - } - ctx.fill(); - } - - if (output_slot) { - var x = node._collapsed_width; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { - x = node._collapsed_width * 0.5; - y = 0; - } - ctx.fillStyle = "#686"; - ctx.strokeStyle = "black"; - ctx.beginPath(); - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - ctx.rect(x - 7 + 0.5, y - 4, 14, 8); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(x + 6, y); - ctx.lineTo(x - 6, y - 4); - ctx.lineTo(x - 6, y + 4); - ctx.closePath(); - } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); - } - ctx.fill(); - //ctx.stroke(); - } - } - - if (node.clip_area) { - ctx.restore(); - } - - ctx.globalAlpha = 1.0; - }; - - //used by this.over_link_center - LGraphCanvas.prototype.drawLinkTooltip = function( ctx, link ) - { - var pos = link._pos; - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.arc( pos[0], pos[1], 3, 0, Math.PI * 2 ); - ctx.fill(); - - if(link.data == null) - return; - - if(this.onDrawLinkTooltip) - if( this.onDrawLinkTooltip(ctx,link,this) == true ) - return; - - var data = link.data; - var text = null; - - if( data.constructor === Number ) - text = data.toFixed(2); - else if( data.constructor === String ) - text = "\"" + data + "\""; - else if( data.constructor === Boolean ) - text = String(data); - else if (data.toToolTip) - text = data.toToolTip(); - else - text = "[" + data.constructor.name + "]"; - - if(text == null) - return; - text = text.substr(0,30); //avoid weird - - ctx.font = "14px Courier New"; - var info = ctx.measureText(text); - var w = info.width + 20; - var h = 24; - ctx.shadowColor = "black"; - ctx.shadowOffsetX = 2; - ctx.shadowOffsetY = 2; - ctx.shadowBlur = 3; - ctx.fillStyle = "#454"; - ctx.beginPath(); - ctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h, [3]); - ctx.moveTo( pos[0] - 10, pos[1] - 15 ); - ctx.lineTo( pos[0] + 10, pos[1] - 15 ); - ctx.lineTo( pos[0], pos[1] - 5 ); - ctx.fill(); - ctx.shadowColor = "transparent"; - ctx.textAlign = "center"; - ctx.fillStyle = "#CEC"; - ctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3); - } - - /** - * draws the shape of the given node in the canvas - * @method drawNodeShape - **/ - var tmp_area = new Float32Array(4); - - LGraphCanvas.prototype.drawNodeShape = function( - node, - ctx, - size, - fgcolor, - bgcolor, - selected, - mouse_over - ) { - //bg rect - ctx.strokeStyle = fgcolor; - ctx.fillStyle = bgcolor; - - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - var low_quality = this.ds.scale < 0.5; - - //render node area depending on shape - var shape = - node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; - - var title_mode = node.constructor.title_mode; - - var render_title = true; - if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) { - render_title = false; - } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) { - render_title = true; - } - - var area = tmp_area; - area[0] = 0; //x - area[1] = render_title ? -title_height : 0; //y - area[2] = size[0] + 1; //w - area[3] = render_title ? size[1] + title_height : size[1]; //h - - var old_alpha = ctx.globalAlpha; - - //full node shape - //if(node.flags.collapsed) - { - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.fillRect(area[0], area[1], area[2], area[3]); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - shape == LiteGraph.CARD_SHAPE - ) { - ctx.roundRect( - area[0], - area[1], - area[2], - area[3], - shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius] - ); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - - //separator - if(!node.flags.collapsed && render_title) - { - ctx.shadowColor = "transparent"; - ctx.fillStyle = "rgba(0,0,0,0.2)"; - ctx.fillRect(0, -1, area[2], 2); - } - } - ctx.shadowColor = "transparent"; - - if (node.onDrawBackground) { - node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse ); - } - - //title bg (remember, it is rendered ABOVE the node) - if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) { - //title bar - if (node.onDrawTitleBar) { - node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor ); - } else if ( - title_mode != LiteGraph.TRANSPARENT_TITLE && - (node.constructor.title_color || this.render_title_colored) - ) { - var title_color = node.constructor.title_color || fgcolor; - - if (node.flags.collapsed) { - ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; - } - - //* gradient test - if (this.use_gradients) { - var grad = LGraphCanvas.gradients[title_color]; - if (!grad) { - grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0); - grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException - grad.addColorStop(1, "#000"); - } - ctx.fillStyle = grad; - } else { - ctx.fillStyle = title_color; - } - - //ctx.globalAlpha = 0.5 * old_alpha; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.rect(0, -title_height, size[0] + 1, title_height); - } else if ( shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) { - ctx.roundRect( - 0, - -title_height, - size[0] + 1, - title_height, - node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0] - ); - } - ctx.fill(); - ctx.shadowColor = "transparent"; - } - - var colState = false; - if (LiteGraph.node_box_coloured_by_mode){ - if(LiteGraph.NODE_MODES_COLORS[node.mode]){ - colState = LiteGraph.NODE_MODES_COLORS[node.mode]; - } - } - if (LiteGraph.node_box_coloured_when_on){ - colState = node.action_triggered ? "#FFF" : (node.execute_triggered ? "#AAA" : colState); - } - - //title box - var box_size = 10; - if (node.onDrawTitleBox) { - node.onDrawTitleBox(ctx, title_height, size, this.ds.scale); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - shape == LiteGraph.CIRCLE_SHAPE || - shape == LiteGraph.CARD_SHAPE - ) { - if (low_quality) { - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.arc( - title_height * 0.5, - title_height * -0.5, - box_size * 0.5 + 1, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - - ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; - if(low_quality) - ctx.fillRect( title_height * 0.5 - box_size *0.5, title_height * -0.5 - box_size *0.5, box_size , box_size ); - else - { - ctx.beginPath(); - ctx.arc( - title_height * 0.5, - title_height * -0.5, - box_size * 0.5, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - } else { - if (low_quality) { - ctx.fillStyle = "black"; - ctx.fillRect( - (title_height - box_size) * 0.5 - 1, - (title_height + box_size) * -0.5 - 1, - box_size + 2, - box_size + 2 - ); - } - ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.fillRect( - (title_height - box_size) * 0.5, - (title_height + box_size) * -0.5, - box_size, - box_size - ); - } - ctx.globalAlpha = old_alpha; - - //title text - if (node.onDrawTitleText) { - node.onDrawTitleText( - ctx, - title_height, - size, - this.ds.scale, - this.title_text_font, - selected - ); - } - if (!low_quality) { - ctx.font = this.title_text_font; - var title = String(node.getTitle()); - if (title) { - if (selected) { - ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR; - } else { - ctx.fillStyle = - node.constructor.title_text_color || - this.node_title_color; - } - if (node.flags.collapsed) { - ctx.textAlign = "left"; - var measure = ctx.measureText(title); - ctx.fillText( - title.substr(0,20), //avoid urls too long - title_height,// + measure.width * 0.5, - LiteGraph.NODE_TITLE_TEXT_Y - title_height - ); - ctx.textAlign = "left"; - } else { - ctx.textAlign = "left"; - ctx.fillText( - title, - title_height, - LiteGraph.NODE_TITLE_TEXT_Y - title_height - ); - } - } - } - - //subgraph box - if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) { - var w = LiteGraph.NODE_TITLE_HEIGHT; - var x = node.size[0] - w; - var over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 ); - ctx.fillStyle = over ? "#888" : "#555"; - if( shape == LiteGraph.BOX_SHAPE || low_quality) - ctx.fillRect(x+2, -w+2, w-4, w-4); - else - { - ctx.beginPath(); - ctx.roundRect(x+2, -w+2, w-4, w-4,[4]); - ctx.fill(); - } - ctx.fillStyle = "#333"; - ctx.beginPath(); - ctx.moveTo(x + w * 0.2, -w * 0.6); - ctx.lineTo(x + w * 0.8, -w * 0.6); - ctx.lineTo(x + w * 0.5, -w * 0.3); - ctx.fill(); - } - - //custom title render - if (node.onDrawTitle) { - node.onDrawTitle(ctx); - } - } - - //render selection marker - if (selected) { - if (node.onBounding) { - node.onBounding(area); - } - - if (title_mode == LiteGraph.TRANSPARENT_TITLE) { - area[1] -= title_height; - area[3] += title_height; - } - ctx.lineWidth = 1; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) { - ctx.rect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3] - ); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed) - ) { - ctx.roundRect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3], - [this.round_radius * 2] - ); - } else if (shape == LiteGraph.CARD_SHAPE) { - ctx.roundRect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3], - [this.round_radius * 2,2,this.round_radius * 2,2] - ); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5 + 6, - 0, - Math.PI * 2 - ); - } - ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR; - ctx.stroke(); - ctx.strokeStyle = fgcolor; - ctx.globalAlpha = 1; - } - - // these counter helps in conditioning drawing based on if the node has been executed or an action occurred - if (node.execute_triggered>0) node.execute_triggered--; - if (node.action_triggered>0) node.action_triggered--; - }; - - var margin_area = new Float32Array(4); - var link_bounding = new Float32Array(4); - var tempA = new Float32Array(2); - var tempB = new Float32Array(2); - - /** - * draws every connection visible in the canvas - * OPTIMIZE THIS: pre-catch connections position instead of recomputing them every time - * @method drawConnections - **/ - LGraphCanvas.prototype.drawConnections = function(ctx) { - var now = LiteGraph.getTime(); - var visible_area = this.visible_area; - margin_area[0] = visible_area[0] - 20; - margin_area[1] = visible_area[1] - 20; - margin_area[2] = visible_area[2] + 40; - margin_area[3] = visible_area[3] + 40; - - //draw connections - ctx.lineWidth = this.connections_width; - - ctx.fillStyle = "#AAA"; - ctx.strokeStyle = "#AAA"; - ctx.globalAlpha = this.editor_alpha; - //for every node - var nodes = this.graph._nodes; - for (var n = 0, l = nodes.length; n < l; ++n) { - var node = nodes[n]; - //for every input (we render just inputs because it is easier as every slot can only have one input) - if (!node.inputs || !node.inputs.length) { - continue; - } - - for (var i = 0; i < node.inputs.length; ++i) { - var input = node.inputs[i]; - if (!input || input.link == null) { - continue; - } - var link_id = input.link; - var link = this.graph.links[link_id]; - if (!link) { - continue; - } - - //find link info - var start_node = this.graph.getNodeById(link.origin_id); - if (start_node == null) { - continue; - } - var start_node_slot = link.origin_slot; - var start_node_slotpos = null; - if (start_node_slot == -1) { - start_node_slotpos = [ - start_node.pos[0] + 10, - start_node.pos[1] + 10 - ]; - } else { - start_node_slotpos = start_node.getConnectionPos( - false, - start_node_slot, - tempA - ); - } - var end_node_slotpos = node.getConnectionPos(true, i, tempB); - - //compute link bounding - link_bounding[0] = start_node_slotpos[0]; - link_bounding[1] = start_node_slotpos[1]; - link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0]; - link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1]; - if (link_bounding[2] < 0) { - link_bounding[0] += link_bounding[2]; - link_bounding[2] = Math.abs(link_bounding[2]); - } - if (link_bounding[3] < 0) { - link_bounding[1] += link_bounding[3]; - link_bounding[3] = Math.abs(link_bounding[3]); - } - - //skip links outside of the visible area of the canvas - if (!overlapBounding(link_bounding, margin_area)) { - continue; - } - - var start_slot = start_node.outputs[start_node_slot]; - var end_slot = node.inputs[i]; - if (!start_slot || !end_slot) { - continue; - } - var start_dir = - start_slot.dir || - (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT); - var end_dir = - end_slot.dir || - (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT); - - this.renderLink( - ctx, - start_node_slotpos, - end_node_slotpos, - link, - false, - 0, - null, - start_dir, - end_dir - ); - - //event triggered rendered on top - if (link && link._last_time && now - link._last_time < 1000) { - var f = 2.0 - (now - link._last_time) * 0.002; - var tmp = ctx.globalAlpha; - ctx.globalAlpha = tmp * f; - this.renderLink( - ctx, - start_node_slotpos, - end_node_slotpos, - link, - true, - f, - "white", - start_dir, - end_dir - ); - ctx.globalAlpha = tmp; - } - } - } - ctx.globalAlpha = 1; - }; - - /** - * draws a link between two points - * @method renderLink - * @param {vec2} a start pos - * @param {vec2} b end pos - * @param {Object} link the link object with all the link info - * @param {boolean} skip_border ignore the shadow of the link - * @param {boolean} flow show flow animation (for events) - * @param {string} color the color for the link - * @param {number} start_dir the direction enum - * @param {number} end_dir the direction enum - * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb) - **/ - LGraphCanvas.prototype.renderLink = function( - ctx, - a, - b, - link, - skip_border, - flow, - color, - start_dir, - end_dir, - num_sublines - ) { - if (link) { - this.visible_links.push(link); - } - - //choose color - if (!color && link) { - color = link.color || LGraphCanvas.link_type_colors[link.type]; - } - if (!color) { - color = this.default_link_color; - } - if (link != null && this.highlighted_links[link.id]) { - color = "#FFF"; - } - - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = end_dir || LiteGraph.LEFT; - - var dist = distance(a, b); - - if (this.render_connections_border && this.ds.scale > 0.6) { - ctx.lineWidth = this.connections_width + 4; - } - ctx.lineJoin = "round"; - num_sublines = num_sublines || 1; - if (num_sublines > 1) { - ctx.lineWidth = 0.5; - } - - //begin line shape - ctx.beginPath(); - for (var i = 0; i < num_sublines; i += 1) { - var offsety = (i - (num_sublines - 1) * 0.5) * 5; - - if (this.links_render_mode == LiteGraph.SPLINE_LINK) { - ctx.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - start_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - start_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - start_offset_y = dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - end_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - end_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - end_offset_y = dist * 0.25; - break; - } - ctx.bezierCurveTo( - a[0] + start_offset_x, - a[1] + start_offset_y + offsety, - b[0] + end_offset_x, - b[1] + end_offset_y + offsety, - b[0], - b[1] + offsety - ); - } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) { - ctx.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = -1; - break; - case LiteGraph.RIGHT: - start_offset_x = 1; - break; - case LiteGraph.UP: - start_offset_y = -1; - break; - case LiteGraph.DOWN: - start_offset_y = 1; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = -1; - break; - case LiteGraph.RIGHT: - end_offset_x = 1; - break; - case LiteGraph.UP: - end_offset_y = -1; - break; - case LiteGraph.DOWN: - end_offset_y = 1; - break; - } - var l = 15; - ctx.lineTo( - a[0] + start_offset_x * l, - a[1] + start_offset_y * l + offsety - ); - ctx.lineTo( - b[0] + end_offset_x * l, - b[1] + end_offset_y * l + offsety - ); - ctx.lineTo(b[0], b[1] + offsety); - } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) { - ctx.moveTo(a[0], a[1]); - var start_x = a[0]; - var start_y = a[1]; - var end_x = b[0]; - var end_y = b[1]; - if (start_dir == LiteGraph.RIGHT) { - start_x += 10; - } else { - start_y += 10; - } - if (end_dir == LiteGraph.LEFT) { - end_x -= 10; - } else { - end_y -= 10; - } - ctx.lineTo(start_x, start_y); - ctx.lineTo((start_x + end_x) * 0.5, start_y); - ctx.lineTo((start_x + end_x) * 0.5, end_y); - ctx.lineTo(end_x, end_y); - ctx.lineTo(b[0], b[1]); - } else { - return; - } //unknown - } - - //rendering the outline of the connection can be a little bit slow - if ( - this.render_connections_border && - this.ds.scale > 0.6 && - !skip_border - ) { - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(); - } - - ctx.lineWidth = this.connections_width; - ctx.fillStyle = ctx.strokeStyle = color; - ctx.stroke(); - //end line shape - - var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir); - if (link && link._pos) { - link._pos[0] = pos[0]; - link._pos[1] = pos[1]; - } - - //render arrow in the middle - if ( - this.ds.scale >= 0.6 && - this.highquality_render && - end_dir != LiteGraph.CENTER - ) { - //render arrow - if (this.render_connection_arrows) { - //compute two points in the connection - var posA = this.computeConnectionPoint( - a, - b, - 0.25, - start_dir, - end_dir - ); - var posB = this.computeConnectionPoint( - a, - b, - 0.26, - start_dir, - end_dir - ); - var posC = this.computeConnectionPoint( - a, - b, - 0.75, - start_dir, - end_dir - ); - var posD = this.computeConnectionPoint( - a, - b, - 0.76, - start_dir, - end_dir - ); - - //compute the angle between them so the arrow points in the right direction - var angleA = 0; - var angleB = 0; - if (this.render_curved_connections) { - angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]); - angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]); - } else { - angleB = angleA = b[1] > a[1] ? 0 : Math.PI; - } - - //render arrow - ctx.save(); - ctx.translate(posA[0], posA[1]); - ctx.rotate(angleA); - ctx.beginPath(); - ctx.moveTo(-5, -3); - ctx.lineTo(0, +7); - ctx.lineTo(+5, -3); - ctx.fill(); - ctx.restore(); - ctx.save(); - ctx.translate(posC[0], posC[1]); - ctx.rotate(angleB); - ctx.beginPath(); - ctx.moveTo(-5, -3); - ctx.lineTo(0, +7); - ctx.lineTo(+5, -3); - ctx.fill(); - ctx.restore(); - } - - //circle - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2); - ctx.fill(); - } - - //render flowing points - if (flow) { - ctx.fillStyle = color; - for (var i = 0; i < 5; ++i) { - var f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1; - var pos = this.computeConnectionPoint( - a, - b, - f, - start_dir, - end_dir - ); - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI); - ctx.fill(); - } - } - }; - - //returns the link center point based on curvature - LGraphCanvas.prototype.computeConnectionPoint = function( - a, - b, - t, - start_dir, - end_dir - ) { - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = end_dir || LiteGraph.LEFT; - - var dist = distance(a, b); - var p0 = a; - var p1 = [a[0], a[1]]; - var p2 = [b[0], b[1]]; - var p3 = b; - - switch (start_dir) { - case LiteGraph.LEFT: - p1[0] += dist * -0.25; - break; - case LiteGraph.RIGHT: - p1[0] += dist * 0.25; - break; - case LiteGraph.UP: - p1[1] += dist * -0.25; - break; - case LiteGraph.DOWN: - p1[1] += dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - p2[0] += dist * -0.25; - break; - case LiteGraph.RIGHT: - p2[0] += dist * 0.25; - break; - case LiteGraph.UP: - p2[1] += dist * -0.25; - break; - case LiteGraph.DOWN: - p2[1] += dist * 0.25; - break; - } - - var c1 = (1 - t) * (1 - t) * (1 - t); - var c2 = 3 * ((1 - t) * (1 - t)) * t; - var c3 = 3 * (1 - t) * (t * t); - var c4 = t * t * t; - - var x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0]; - var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1]; - return [x, y]; - }; - - LGraphCanvas.prototype.drawExecutionOrder = function(ctx) { - ctx.shadowColor = "transparent"; - ctx.globalAlpha = 0.25; - - ctx.textAlign = "center"; - ctx.strokeStyle = "white"; - ctx.globalAlpha = 0.75; - - var visible_nodes = this.visible_nodes; - for (var i = 0; i < visible_nodes.length; ++i) { - var node = visible_nodes[i]; - ctx.fillStyle = "black"; - ctx.fillRect( - node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT, - node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT - ); - if (node.order == 0) { - ctx.strokeRect( - node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, - node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT - ); - } - ctx.fillStyle = "#FFF"; - ctx.fillText( - node.order, - node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5, - node.pos[1] - 6 - ); - } - ctx.globalAlpha = 1; - }; - - /** - * draws the widgets stored inside a node - * @method drawNodeWidgets - **/ - LGraphCanvas.prototype.drawNodeWidgets = function( - node, - posY, - ctx, - active_widget - ) { - if (!node.widgets || !node.widgets.length) { - return 0; - } - var width = node.size[0]; - var widgets = node.widgets; - posY += 2; - var H = LiteGraph.NODE_WIDGET_HEIGHT; - var show_text = this.ds.scale > 0.5; - ctx.save(); - ctx.globalAlpha = this.editor_alpha; - var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR; - var background_color = LiteGraph.WIDGET_BGCOLOR; - var text_color = LiteGraph.WIDGET_TEXT_COLOR; - var secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; - var margin = 15; - - for (var i = 0; i < widgets.length; ++i) { - var w = widgets[i]; - var y = posY; - if (w.y) { - y = w.y; - } - w.last_y = y; - ctx.strokeStyle = outline_color; - ctx.fillStyle = "#222"; - ctx.textAlign = "left"; - //ctx.lineWidth = 2; - if(w.disabled) - ctx.globalAlpha *= 0.5; - var widget_width = w.width || width; - - switch (w.type) { - case "button": - ctx.fillStyle = background_color; - if (w.clicked) { - ctx.fillStyle = "#AAA"; - w.clicked = false; - this.dirty_canvas = true; - } - ctx.fillRect(margin, y, widget_width - margin * 2, H); - if(show_text && !w.disabled) - ctx.strokeRect( margin, y, widget_width - margin * 2, H ); - if (show_text) { - ctx.textAlign = "center"; - ctx.fillStyle = text_color; - ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7); - } - break; - case "toggle": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if (show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); - else - ctx.rect(margin, y, widget_width - margin * 2, H ); - ctx.fill(); - if(show_text && !w.disabled) - ctx.stroke(); - ctx.fillStyle = w.value ? "#89A" : "#333"; - ctx.beginPath(); - ctx.arc( widget_width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2 ); - ctx.fill(); - if (show_text) { - ctx.fillStyle = secondary_text_color; - const label = w.label || w.name; - if (label != null) { - ctx.fillText(label, margin * 2, y + H * 0.7); - } - ctx.fillStyle = w.value ? text_color : secondary_text_color; - ctx.textAlign = "right"; - ctx.fillText( - w.value - ? w.options.on || "true" - : w.options.off || "false", - widget_width - 40, - y + H * 0.7 - ); - } - break; - case "slider": - ctx.fillStyle = background_color; - ctx.fillRect(margin, y, widget_width - margin * 2, H); - var range = w.options.max - w.options.min; - var nvalue = (w.value - w.options.min) / range; - if(nvalue < 0.0) nvalue = 0.0; - if(nvalue > 1.0) nvalue = 1.0; - ctx.fillStyle = w.options.hasOwnProperty("slider_color") ? w.options.slider_color : (active_widget == w ? "#89A" : "#678"); - ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H); - if(show_text && !w.disabled) - ctx.strokeRect(margin, y, widget_width - margin * 2, H); - if (w.marker) { - var marker_nvalue = (w.marker - w.options.min) / range; - if(marker_nvalue < 0.0) marker_nvalue = 0.0; - if(marker_nvalue > 1.0) marker_nvalue = 1.0; - ctx.fillStyle = w.options.hasOwnProperty("marker_color") ? w.options.marker_color : "#AA9"; - ctx.fillRect( margin + marker_nvalue * (widget_width - margin * 2), y, 2, H ); - } - if (show_text) { - ctx.textAlign = "center"; - ctx.fillStyle = text_color; - ctx.fillText( - w.label || w.name + " " + Number(w.value).toFixed( - w.options.precision != null - ? w.options.precision - : 3 - ), - widget_width * 0.5, - y + H * 0.7 - ); - } - break; - case "number": - case "combo": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if(show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5] ); - else - ctx.rect(margin, y, widget_width - margin * 2, H ); - ctx.fill(); - if (show_text) { - if(!w.disabled) - ctx.stroke(); - ctx.fillStyle = text_color; - if(!w.disabled) - { - ctx.beginPath(); - ctx.moveTo(margin + 16, y + 5); - ctx.lineTo(margin + 6, y + H * 0.5); - ctx.lineTo(margin + 16, y + H - 5); - ctx.fill(); - ctx.beginPath(); - ctx.moveTo(widget_width - margin - 16, y + 5); - ctx.lineTo(widget_width - margin - 6, y + H * 0.5); - ctx.lineTo(widget_width - margin - 16, y + H - 5); - ctx.fill(); - } - ctx.fillStyle = secondary_text_color; - ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7); - ctx.fillStyle = text_color; - ctx.textAlign = "right"; - if (w.type == "number") { - ctx.fillText( - Number(w.value).toFixed( - w.options.precision !== undefined - ? w.options.precision - : 3 - ), - widget_width - margin * 2 - 20, - y + H * 0.7 - ); - } else { - var v = w.value; - if( w.options.values ) - { - var values = w.options.values; - if( values.constructor === Function ) - values = values(); - if(values && values.constructor !== Array) - v = values[ w.value ]; - } - ctx.fillText( - v, - widget_width - margin * 2 - 20, - y + H * 0.7 - ); - } - } - break; - case "string": - case "text": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if (show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); - else - ctx.rect( margin, y, widget_width - margin * 2, H ); - ctx.fill(); - if (show_text) { - if(!w.disabled) - ctx.stroke(); - ctx.save(); - ctx.beginPath(); - ctx.rect(margin, y, widget_width - margin * 2, H); - ctx.clip(); - - //ctx.stroke(); - ctx.fillStyle = secondary_text_color; - const label = w.label || w.name; - if (label != null) { - ctx.fillText(label, margin * 2, y + H * 0.7); - } - ctx.fillStyle = text_color; - ctx.textAlign = "right"; - ctx.fillText(String(w.value).substr(0,30), widget_width - margin * 2, y + H * 0.7); //30 chars max - ctx.restore(); - } - break; - default: - if (w.draw) { - w.draw(ctx, node, widget_width, y, H); - } - break; - } - posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4; - ctx.globalAlpha = this.editor_alpha; - - } - ctx.restore(); - ctx.textAlign = "left"; - }; - - /** - * process an event on widgets - * @method processNodeWidgets - **/ - LGraphCanvas.prototype.processNodeWidgets = function( - node, - pos, - event, - active_widget - ) { - if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) { - return null; - } - - var x = pos[0] - node.pos[0]; - var y = pos[1] - node.pos[1]; - var width = node.size[0]; - var that = this; - var ref_window = this.getCanvasWindow(); - - for (var i = 0; i < node.widgets.length; ++i) { - var w = node.widgets[i]; - if(!w || w.disabled) - continue; - var widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT; - var widget_width = w.width || width; - //outside - if ( w != active_widget && - (x < 6 || x > widget_width - 12 || y < w.last_y || y > w.last_y + widget_height || w.last_y === undefined) ) - continue; - - var old_value = w.value; - - //if ( w == active_widget || (x > 6 && x < widget_width - 12 && y > w.last_y && y < w.last_y + widget_height) ) { - //inside widget - switch (w.type) { - case "button": - if (event.type === LiteGraph.pointerevents_method+"down") { - if (w.callback) { - setTimeout(function() { - w.callback(w, that, node, pos, event); - }, 20); - } - w.clicked = true; - this.dirty_canvas = true; - } - break; - case "slider": - var old_value = w.value; - var nvalue = clamp((x - 15) / (widget_width - 30), 0, 1); - if(w.options.read_only) break; - w.value = w.options.min + (w.options.max - w.options.min) * nvalue; - if (old_value != w.value) { - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - this.dirty_canvas = true; - break; - case "number": - case "combo": - var old_value = w.value; - var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; - var allow_scroll = true; - if (delta) { - if (x > -3 && x < widget_width + 3) { - allow_scroll = false; - } - } - if (allow_scroll && event.type == LiteGraph.pointerevents_method+"move" && w.type == "number") { - if(event.deltaX) - w.value += event.deltaX * 0.1 * (w.options.step || 1); - if ( w.options.min != null && w.value < w.options.min ) { - w.value = w.options.min; - } - if ( w.options.max != null && w.value > w.options.max ) { - w.value = w.options.max; - } - } else if (event.type == LiteGraph.pointerevents_method+"down") { - var values = w.options.values; - if (values && values.constructor === Function) { - values = w.options.values(w, node); - } - var values_list = null; - - if( w.type != "number") - values_list = values.constructor === Array ? values : Object.keys(values); - - var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; - if (w.type == "number") { - w.value += delta * 0.1 * (w.options.step || 1); - if ( w.options.min != null && w.value < w.options.min ) { - w.value = w.options.min; - } - if ( w.options.max != null && w.value > w.options.max ) { - w.value = w.options.max; - } - } else if (delta) { //clicked in arrow, used for combos - var index = -1; - this.last_mouseclick = 0; //avoids dobl click event - if(values.constructor === Object) - index = values_list.indexOf( String( w.value ) ) + delta; - else - index = values_list.indexOf( w.value ) + delta; - if (index >= values_list.length) { - index = values_list.length - 1; - } - if (index < 0) { - index = 0; - } - if( values.constructor === Array ) - w.value = values[index]; - else - w.value = index; - } else { //combo clicked - var text_values = values != values_list ? Object.values(values) : values; - var menu = new LiteGraph.ContextMenu(text_values, { - scale: Math.max(1, this.ds.scale), - event: event, - className: "dark", - callback: inner_clicked.bind(w) - }, - ref_window); - function inner_clicked(v, option, event) { - if(values != values_list) - v = text_values.indexOf(v); - this.value = v; - inner_value_change(this, v); - that.dirty_canvas = true; - return false; - } - } - } //end mousedown - else if(event.type == LiteGraph.pointerevents_method+"up" && w.type == "number") - { - var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; - if (event.click_time < 200 && delta == 0) { - this.prompt("Value",w.value,function(v) { - // check if v is a valid equation or a number - if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(v)) { - try {//solve the equation if possible - v = eval(v); - } catch (e) { } - } - this.value = Number(v); - inner_value_change(this, this.value); - }.bind(w), - event); - } - } - - if( old_value != w.value ) - setTimeout( - function() { - inner_value_change(this, this.value); - }.bind(w), - 20 - ); - this.dirty_canvas = true; - break; - case "toggle": - if (event.type == LiteGraph.pointerevents_method+"down") { - w.value = !w.value; - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - break; - case "string": - case "text": - if (event.type == LiteGraph.pointerevents_method+"down") { - this.prompt("Value",w.value,function(v) { - inner_value_change(this, v); - }.bind(w), - event,w.options ? w.options.multiline : false ); - } - break; - default: - if (w.mouse) { - this.dirty_canvas = w.mouse(event, [x, y], node); - } - break; - } //end switch - - //value changed - if( old_value != w.value ) - { - if(node.onWidgetChanged) - node.onWidgetChanged( w.name,w.value,old_value,w ); - node.graph._version++; - } - - return w; - }//end for - - function inner_value_change(widget, value) { - if(widget.type == "number"){ - value = Number(value); - } - widget.value = value; - if ( widget.options && widget.options.property && node.properties[widget.options.property] !== undefined ) { - node.setProperty( widget.options.property, value ); - } - if (widget.callback) { - widget.callback(widget.value, that, node, pos, event); - } - } - - return null; - }; - - /** - * draws every group area in the background - * @method drawGroups - **/ - LGraphCanvas.prototype.drawGroups = function(canvas, ctx) { - if (!this.graph) { - return; - } - - var groups = this.graph._groups; - - ctx.save(); - ctx.globalAlpha = 0.5 * this.editor_alpha; - - for (var i = 0; i < groups.length; ++i) { - var group = groups[i]; - - if (!overlapBounding(this.visible_area, group._bounding)) { - continue; - } //out of the visible area - - ctx.fillStyle = group.color || "#335"; - ctx.strokeStyle = group.color || "#335"; - var pos = group._pos; - var size = group._size; - ctx.globalAlpha = 0.25 * this.editor_alpha; - ctx.beginPath(); - ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], size[1]); - ctx.fill(); - ctx.globalAlpha = this.editor_alpha; - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(pos[0] + size[0], pos[1] + size[1]); - ctx.lineTo(pos[0] + size[0] - 10, pos[1] + size[1]); - ctx.lineTo(pos[0] + size[0], pos[1] + size[1] - 10); - ctx.fill(); - - var font_size = - group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; - ctx.font = font_size + "px Arial"; - ctx.textAlign = "left"; - ctx.fillText(group.title, pos[0] + 4, pos[1] + font_size); - } - - ctx.restore(); - }; - - LGraphCanvas.prototype.adjustNodesSize = function() { - var nodes = this.graph._nodes; - for (var i = 0; i < nodes.length; ++i) { - nodes[i].size = nodes[i].computeSize(); - } - this.setDirty(true, true); - }; - - /** - * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode - * @method resize - **/ - LGraphCanvas.prototype.resize = function(width, height) { - if (!width && !height) { - var parent = this.canvas.parentNode; - width = parent.offsetWidth; - height = parent.offsetHeight; - } - - if (this.canvas.width == width && this.canvas.height == height) { - return; - } - - this.canvas.width = width; - this.canvas.height = height; - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - this.setDirty(true, true); - }; - - /** - * switches to live mode (node shapes are not rendered, only the content) - * this feature was designed when graphs where meant to create user interfaces - * @method switchLiveMode - **/ - LGraphCanvas.prototype.switchLiveMode = function(transition) { - if (!transition) { - this.live_mode = !this.live_mode; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - return; - } - - var self = this; - var delta = this.live_mode ? 1.1 : 0.9; - if (this.live_mode) { - this.live_mode = false; - this.editor_alpha = 0.1; - } - - var t = setInterval(function() { - self.editor_alpha *= delta; - self.dirty_canvas = true; - self.dirty_bgcanvas = true; - - if (delta < 1 && self.editor_alpha < 0.01) { - clearInterval(t); - if (delta < 1) { - self.live_mode = true; - } - } - if (delta > 1 && self.editor_alpha > 0.99) { - clearInterval(t); - self.editor_alpha = 1; - } - }, 1); - }; - - LGraphCanvas.prototype.onNodeSelectionChange = function(node) { - return; //disabled - }; - - /* this is an implementation for touch not in production and not ready - */ - /*LGraphCanvas.prototype.touchHandler = function(event) { - //alert("foo"); - var touches = event.changedTouches, - first = touches[0], - type = ""; - - switch (event.type) { - case "touchstart": - type = "mousedown"; - break; - case "touchmove": - type = "mousemove"; - break; - case "touchend": - type = "mouseup"; - break; - default: - return; - } - - //initMouseEvent(type, canBubble, cancelable, view, clickCount, - // screenX, screenY, clientX, clientY, ctrlKey, - // altKey, shiftKey, metaKey, button, relatedTarget); - - // this is eventually a Dom object, get the LGraphCanvas back - if(typeof this.getCanvasWindow == "undefined"){ - var window = this.lgraphcanvas.getCanvasWindow(); - }else{ - var window = this.getCanvasWindow(); - } - - var document = window.document; - - var simulatedEvent = document.createEvent("MouseEvent"); - simulatedEvent.initMouseEvent( - type, - true, - true, - window, - 1, - first.screenX, - first.screenY, - first.clientX, - first.clientY, - false, - false, - false, - false, - 0, //left - null - ); - first.target.dispatchEvent(simulatedEvent); - event.preventDefault(); - };*/ - - /* CONTEXT MENU ********************/ - - LGraphCanvas.onGroupAdd = function(info, entry, mouse_event) { - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var group = new LiteGraph.LGraphGroup(); - group.pos = canvas.convertEventToCanvasOffset(mouse_event); - canvas.graph.add(group); - }; - - /** - * Determines the furthest nodes in each direction - * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted - * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} - */ - LGraphCanvas.getBoundaryNodes = function(nodes) { - let top = null; - let right = null; - let bottom = null; - let left = null; - for (const nID in nodes) { - const node = nodes[nID]; - const [x, y] = node.pos; - const [width, height] = node.size; - - if (top === null || y < top.pos[1]) { - top = node; - } - if (right === null || x + width > right.pos[0] + right.size[0]) { - right = node; - } - if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) { - bottom = node; - } - if (left === null || x < left.pos[0]) { - left = node; - } - } - - return { - "top": top, - "right": right, - "bottom": bottom, - "left": left - }; - } - /** - * Determines the furthest nodes in each direction for the currently selected nodes - * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} - */ - LGraphCanvas.prototype.boundaryNodesForSelection = function() { - return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes)); - } - - /** - * - * @param {LGraphNode[]} nodes a list of nodes - * @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes - * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction) - */ - LGraphCanvas.alignNodes = function (nodes, direction, align_to) { - if (!nodes) { - return; - } - - const canvas = LGraphCanvas.active_canvas; - let boundaryNodes = [] - if (align_to === undefined) { - boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes) - } else { - boundaryNodes = { - "top": align_to, - "right": align_to, - "bottom": align_to, - "left": align_to - } - } - - for (const [_, node] of Object.entries(canvas.selected_nodes)) { - switch (direction) { - case "right": - node.pos[0] = boundaryNodes["right"].pos[0] + boundaryNodes["right"].size[0] - node.size[0]; - break; - case "left": - node.pos[0] = boundaryNodes["left"].pos[0]; - break; - case "top": - node.pos[1] = boundaryNodes["top"].pos[1]; - break; - case "bottom": - node.pos[1] = boundaryNodes["bottom"].pos[1] + boundaryNodes["bottom"].size[1] - node.size[1]; - break; - } - } - - canvas.dirty_canvas = true; - canvas.dirty_bgcanvas = true; - }; - - LGraphCanvas.onNodeAlign = function(value, options, event, prev_menu, node) { - new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { - event: event, - callback: inner_clicked, - parentMenu: prev_menu, - }); - - function inner_clicked(value) { - LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node); - } - } - - LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) { - new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { - event: event, - callback: inner_clicked, - parentMenu: prev_menu, - }); - - function inner_clicked(value) { - LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase()); - } - } - - LGraphCanvas.onMenuAdd = function (node, options, e, prev_menu, callback) { - - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - var graph = canvas.graph; - if (!graph) - return; - - function inner_onMenuAdded(base_category ,prev_menu){ - - var categories = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function(category){return category.startsWith(base_category)}); - var entries = []; - - categories.map(function(category){ - - if (!category) - return; - - var base_category_regex = new RegExp('^(' + base_category + ')'); - var category_name = category.replace(base_category_regex,"").split('/')[0]; - var category_path = base_category === '' ? category_name + '/' : base_category + category_name + '/'; - - var name = category_name; - if(name.indexOf("::") != -1) //in case it has a namespace like "shader::math/rand" it hides the namespace - name = name.split("::")[1]; - - var index = entries.findIndex(function(entry){return entry.value === category_path}); - if (index === -1) { - entries.push({ value: category_path, content: name, has_submenu: true, callback : function(value, event, mouseEvent, contextMenu){ - inner_onMenuAdded(value.value, contextMenu) - }}); - } - - }); - - var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter ); - nodes.map(function(node){ - - if (node.skip_list) - return; - - var entry = { value: node.type, content: node.title, has_submenu: false , callback : function(value, event, mouseEvent, contextMenu){ - - var first_event = contextMenu.getFirstEvent(); - canvas.graph.beforeChange(); - var node = LiteGraph.createNode(value.value); - if (node) { - node.pos = canvas.convertEventToCanvasOffset(first_event); - canvas.graph.add(node); - } - if(callback) - callback(node); - canvas.graph.afterChange(); - - } - } - - entries.push(entry); - - }); - - new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window ); - - } - - inner_onMenuAdded('',prev_menu); - return false; - - }; - - LGraphCanvas.onMenuCollapseAll = function() {}; - - LGraphCanvas.onMenuNodeEdit = function() {}; - - LGraphCanvas.showMenuNodeOptionalInputs = function( - v, - options, - e, - prev_menu, - node - ) { - if (!node) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_inputs; - if (node.onGetInputs) { - options = node.onGetInputs(); - } - - var entries = []; - if (options) { - for (var i=0; i < options.length; i++) { - var entry = options[i]; - if (!entry) { - entries.push(null); - continue; - } - var label = entry[0]; - if(!entry[2]) - entry[2] = {}; - - if (entry[2].label) { - label = entry[2].label; - } - - entry[2].removable = true; - var data = { content: label, value: entry }; - if (entry[1] == LiteGraph.ACTION) { - data.className = "event"; - } - entries.push(data); - } - } - - if (node.onMenuNodeInputs) { - var retEntries = node.onMenuNodeInputs(entries); - if(retEntries) entries = retEntries; - } - - if (!entries.length) { - console.log("no input entries"); - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }, - ref_window - ); - - function inner_clicked(v, e, prev) { - if (!node) { - return; - } - - if (v.callback) { - v.callback.call(that, node, v, e, prev); - } - - if (v.value) { - node.graph.beforeChange(); - node.addInput(v.value[0], v.value[1], v.value[2]); - - if (node.onNodeInputAdd) { // callback to the node when adding a slot - node.onNodeInputAdd(v.value); - } - node.setDirtyCanvas(true, true); - node.graph.afterChange(); - } - } - - return false; - }; - - LGraphCanvas.showMenuNodeOptionalOutputs = function( - v, - options, - e, - prev_menu, - node - ) { - if (!node) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_outputs; - if (node.onGetOutputs) { - options = node.onGetOutputs(); - } - - var entries = []; - if (options) { - for (var i=0; i < options.length; i++) { - var entry = options[i]; - if (!entry) { - //separator? - entries.push(null); - continue; - } - - if ( - node.flags && - node.flags.skip_repeated_outputs && - node.findOutputSlot(entry[0]) != -1 - ) { - continue; - } //skip the ones already on - var label = entry[0]; - if(!entry[2]) - entry[2] = {}; - if (entry[2].label) { - label = entry[2].label; - } - entry[2].removable = true; - var data = { content: label, value: entry }; - if (entry[1] == LiteGraph.EVENT) { - data.className = "event"; - } - entries.push(data); - } - } - - if (this.onMenuNodeOutputs) { - entries = this.onMenuNodeOutputs(entries); - } - if (LiteGraph.do_add_triggers_slots){ //canvas.allow_addOutSlot_onExecuted - if (node.findOutputSlot("onExecuted") == -1){ - entries.push({content: "On Executed", value: ["onExecuted", LiteGraph.EVENT, {nameLocked: true}], className: "event"}); //, opts: {} - } - } - // add callback for modifing the menu elements onMenuNodeOutputs - if (node.onMenuNodeOutputs) { - var retEntries = node.onMenuNodeOutputs(entries); - if(retEntries) entries = retEntries; - } - - if (!entries.length) { - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }, - ref_window - ); - - function inner_clicked(v, e, prev) { - if (!node) { - return; - } - - if (v.callback) { - v.callback.call(that, node, v, e, prev); - } - - if (!v.value) { - return; - } - - var value = v.value[1]; - - if ( - value && - (value.constructor === Object || value.constructor === Array) - ) { - //submenu why? - var entries = []; - for (var i in value) { - entries.push({ content: i, value: value[i] }); - } - new LiteGraph.ContextMenu(entries, { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }); - return false; - } else { - node.graph.beforeChange(); - node.addOutput(v.value[0], v.value[1], v.value[2]); - - if (node.onNodeOutputAdd) { // a callback to the node when adding a slot - node.onNodeOutputAdd(v.value); - } - node.setDirtyCanvas(true, true); - node.graph.afterChange(); - } - } - - return false; - }; - - LGraphCanvas.onShowMenuNodeProperties = function( - value, - options, - e, - prev_menu, - node - ) { - if (!node || !node.properties) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var entries = []; - for (var i in node.properties) { - var value = node.properties[i] !== undefined ? node.properties[i] : " "; - if( typeof value == "object" ) - value = JSON.stringify(value); - var info = node.getPropertyInfo(i); - if(info.type == "enum" || info.type == "combo") - value = LGraphCanvas.getPropertyPrintableValue( value, info.values ); - - //value could contain invalid html characters, clean that - value = LGraphCanvas.decodeHTML(value); - entries.push({ - content: - "" + - (info.label ? info.label : i) + - "" + - "" + - value + - "", - value: i - }); - } - if (!entries.length) { - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - allow_html: true, - node: node - }, - ref_window - ); - - function inner_clicked(v, options, e, prev) { - if (!node) { - return; - } - var rect = this.getBoundingClientRect(); - canvas.showEditPropertyValue(node, v.value, { - position: [rect.left, rect.top] - }); - } - - return false; - }; - - LGraphCanvas.decodeHTML = function(str) { - var e = document.createElement("div"); - e.innerText = str; - return e.innerHTML; - }; - - LGraphCanvas.onMenuResizeNode = function(value, options, e, menu, node) { - if (!node) { - return; - } - - var fApplyMultiNode = function(node){ - node.size = node.computeSize(); - if (node.onResize) - node.onResize(node.size); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.prototype.showLinkMenu = function(link, e) { - var that = this; - // console.log(link); - var node_left = that.graph.getNodeById( link.origin_id ); - var node_right = that.graph.getNodeById( link.target_id ); - var fromType = false; - if (node_left && node_left.outputs && node_left.outputs[link.origin_slot]) fromType = node_left.outputs[link.origin_slot].type; - var destType = false; - if (node_right && node_right.outputs && node_right.outputs[link.target_slot]) destType = node_right.inputs[link.target_slot].type; - - var options = ["Add Node",null,"Delete",null]; - - - var menu = new LiteGraph.ContextMenu(options, { - event: e, - title: link.data != null ? link.data.constructor.name : null, - callback: inner_clicked - }); - - function inner_clicked(v,options,e) { - switch (v) { - case "Add Node": - LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ - // console.debug("node autoconnect"); - if(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length){ - return; - } - // leave the connection type checking inside connectByType - if (node_left.connectByType( link.origin_slot, node, fromType )){ - node.connectByType( link.target_slot, node_right, destType ); - node.pos[0] -= node.size[0] * 0.5; - } - }); - break; - - case "Delete": - that.graph.removeLink(link.id); - break; - default: - /*var nodeCreated = createDefaultNodeForSlot({ nodeFrom: node_left - ,slotFrom: link.origin_slot - ,nodeTo: node - ,slotTo: link.target_slot - ,e: e - ,nodeType: "AUTO" - }); - if(nodeCreated) console.log("new node in beetween "+v+" created");*/ - } - } - - return false; - }; - - LGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection - var optPass = optPass || {}; - var opts = Object.assign({ nodeFrom: null // input - ,slotFrom: null // input - ,nodeTo: null // output - ,slotTo: null // output - ,position: [] // pass the event coords - ,nodeType: null // choose a nodetype to add, AUTO to set at first good - ,posAdd:[0,0] // adjust x,y - ,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h - } - ,optPass - ); - var that = this; - - var isFrom = opts.nodeFrom && opts.slotFrom!==null; - var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null; - - if (!isFrom && !isTo){ - console.warn("No data passed to createDefaultNodeForSlot "+opts.nodeFrom+" "+opts.slotFrom+" "+opts.nodeTo+" "+opts.slotTo); - return false; - } - if (!opts.nodeType){ - console.warn("No type to createDefaultNodeForSlot"); - return false; - } - - var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; - var slotX = isFrom ? opts.slotFrom : opts.slotTo; - - var iSlotConn = false; - switch (typeof slotX){ - case "string": - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - case "object": - // ok slotX - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); - break; - case "number": - iSlotConn = slotX; - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - case "undefined": - default: - // bad ? - //iSlotConn = 0; - console.warn("Cant get slot information "+slotX); - return false; - } - - if (slotX===false || iSlotConn===false){ - console.warn("createDefaultNodeForSlot bad slotX "+slotX+" "+iSlotConn); - } - - // check for defaults nodes for this slottype - var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; - var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; - if(slotTypesDefault && slotTypesDefault[fromSlotType]){ - if (slotX.link !== null) { - // is connected - }else{ - // is not not connected - } - nodeNewType = false; - if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ - for(var typeX in slotTypesDefault[fromSlotType]){ - if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO"){ - nodeNewType = slotTypesDefault[fromSlotType][typeX]; - // console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: "+opts.nodeType); - break; // -------- - } - } - }else{ - if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") nodeNewType = slotTypesDefault[fromSlotType]; - } - if (nodeNewType) { - var nodeNewOpts = false; - if (typeof nodeNewType == "object" && nodeNewType.node){ - nodeNewOpts = nodeNewType; - nodeNewType = nodeNewType.node; - } - - //that.graph.beforeChange(); - - var newNode = LiteGraph.createNode(nodeNewType); - if(newNode){ - // if is object pass options - if (nodeNewOpts){ - if (nodeNewOpts.properties) { - for (var i in nodeNewOpts.properties) { - newNode.addProperty( i, nodeNewOpts.properties[i] ); - } - } - if (nodeNewOpts.inputs) { - newNode.inputs = []; - for (var i in nodeNewOpts.inputs) { - newNode.addOutput( - nodeNewOpts.inputs[i][0], - nodeNewOpts.inputs[i][1] - ); - } - } - if (nodeNewOpts.outputs) { - newNode.outputs = []; - for (var i in nodeNewOpts.outputs) { - newNode.addOutput( - nodeNewOpts.outputs[i][0], - nodeNewOpts.outputs[i][1] - ); - } - } - if (nodeNewOpts.title) { - newNode.title = nodeNewOpts.title; - } - if (nodeNewOpts.json) { - newNode.configure(nodeNewOpts.json); - } - - } - - // add the node - that.graph.add(newNode); - newNode.pos = [ opts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0) - ,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/ - - //that.graph.afterChange(); - - // connect the two! - if (isFrom){ - opts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType ); - }else{ - opts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType ); - } - - // if connecting in between - if (isFrom && isTo){ - // TODO - } - - return true; - - }else{ - console.log("failed creating "+nodeNewType); - } - } - } - return false; - } - - LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection - var optPass = optPass || {}; - var opts = Object.assign({ nodeFrom: null // input - ,slotFrom: null // input - ,nodeTo: null // output - ,slotTo: null // output - ,e: null - } - ,optPass - ); - var that = this; - - var isFrom = opts.nodeFrom && opts.slotFrom; - var isTo = !isFrom && opts.nodeTo && opts.slotTo; - - if (!isFrom && !isTo){ - console.warn("No data passed to showConnectionMenu"); - return false; - } - - var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; - var slotX = isFrom ? opts.slotFrom : opts.slotTo; - - var iSlotConn = false; - switch (typeof slotX){ - case "string": - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - case "object": - // ok slotX - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); - break; - case "number": - iSlotConn = slotX; - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - default: - // bad ? - //iSlotConn = 0; - console.warn("Cant get slot information "+slotX); - return false; - } - - var options = ["Add Node",null]; - - if (that.allow_searchbox){ - options.push("Search"); - options.push(null); - } - - // get defaults nodes for this slottype - var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; - var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; - if(slotTypesDefault && slotTypesDefault[fromSlotType]){ - if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ - for(var typeX in slotTypesDefault[fromSlotType]){ - options.push(slotTypesDefault[fromSlotType][typeX]); - } - }else{ - options.push(slotTypesDefault[fromSlotType]); - } - } - - // build menu - var menu = new LiteGraph.ContextMenu(options, { - event: opts.e, - title: (slotX && slotX.name!="" ? (slotX.name + (fromSlotType?" | ":"")) : "")+(slotX && fromSlotType ? fromSlotType : ""), - callback: inner_clicked - }); - - // callback - function inner_clicked(v,options,e) { - //console.log("Process showConnectionMenu selection"); - switch (v) { - case "Add Node": - LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ - if (isFrom){ - opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType ); - }else{ - opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType ); - } - }); - break; - case "Search": - if(isFrom){ - that.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType}); - }else{ - that.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType}); - } - break; - default: - // check for defaults nodes for this slottype - var nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY] - ,nodeType: v - })); - if (nodeCreated){ - // new node created - //console.log("node "+v+" created") - }else{ - // failed or v is not in defaults - } - break; - } - } - - return false; - }; - - // TODO refactor :: this is used fot title but not for properties! - LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) { - var input_html = ""; - var property = item.property || "title"; - var value = node[property]; - - // TODO refactor :: use createDialog ? - - var dialog = document.createElement("div"); - dialog.is_modified = false; - dialog.className = "graphdialog"; - dialog.innerHTML = - ""; - dialog.close = function() { - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - var title = dialog.querySelector(".name"); - title.innerText = property; - var input = dialog.querySelector(".value"); - if (input) { - input.value = value; - input.addEventListener("blur", function(e) { - this.focus(); - }); - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - inner(); // save - } else if (e.keyCode != 13 && e.target.localName != "textarea") { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - } - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner); - canvas.parentNode.appendChild(dialog); - - if(input) input.focus(); - - var dialogCloseTimer = null; - dialog.addEventListener("mouseleave", function(e) { - if(LiteGraph.dialog_close_on_mouse_leave) - if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) - dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); - }); - dialog.addEventListener("mouseenter", function(e) { - if(LiteGraph.dialog_close_on_mouse_leave) - if(dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - - function inner() { - if(input) setValue(input.value); - } - - function setValue(value) { - if (item.type == "Number") { - value = Number(value); - } else if (item.type == "Boolean") { - value = Boolean(value); - } - node[property] = value; - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - node.setDirtyCanvas(true, true); - } - }; - - // refactor: there are different dialogs, some uses createDialog some dont - LGraphCanvas.prototype.prompt = function(title, value, callback, event, multiline) { - var that = this; - var input_html = ""; - title = title || ""; - - var dialog = document.createElement("div"); - dialog.is_modified = false; - dialog.className = "graphdialog rounded"; - if(multiline) - dialog.innerHTML = " "; - else - dialog.innerHTML = " "; - dialog.close = function() { - that.prompt_box = null; - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - canvas.parentNode.appendChild(dialog); - - if (this.ds.scale > 1) { - dialog.style.transform = "scale(" + this.ds.scale + ")"; - } - - var dialogCloseTimer = null; - var prevent_timeout = false; - LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { - if (prevent_timeout) - return; - if(LiteGraph.dialog_close_on_mouse_leave) - if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) - dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); - }); - LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { - if(LiteGraph.dialog_close_on_mouse_leave) - if(dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - var selInDia = dialog.querySelectorAll("select"); - if (selInDia){ - // if filtering, check focus changed to comboboxes and prevent closing - selInDia.forEach(function(selIn) { - selIn.addEventListener("click", function(e) { - prevent_timeout++; - }); - selIn.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selIn.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - }); - } - - if (that.prompt_box) { - that.prompt_box.close(); - } - that.prompt_box = dialog; - - var first = null; - var timeout = null; - var selected = null; - - var name_element = dialog.querySelector(".name"); - name_element.innerText = title; - var value_element = dialog.querySelector(".value"); - value_element.value = value; - value_element.select(); - - var input = value_element; - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13 && e.target.localName != "textarea") { - if (callback) { - callback(this.value); - } - dialog.close(); - } else { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - - var button = dialog.querySelector("button"); - button.addEventListener("click", function(e) { - if (callback) { - callback(input.value); - } - that.setDirty(true); - dialog.close(); - }); - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - - setTimeout(function() { - input.focus(); - }, 10); - - return dialog; - }; - - LGraphCanvas.search_limit = -1; - LGraphCanvas.prototype.showSearchBox = function(event, options) { - // proposed defaults - var def_options = { slot_from: null - ,node_from: null - ,node_to: null - ,do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out - ,type_filter_in: false // these are default: pass to set initially set values - ,type_filter_out: false - ,show_general_if_none_on_typefilter: true - ,show_general_after_typefiltered: true - ,hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave - ,show_all_if_empty: true - ,show_all_on_open: LiteGraph.search_show_all_on_open - }; - options = Object.assign(def_options, options || {}); - - //console.log(options); - - var that = this; - var input_html = ""; - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - var root_document = canvas.ownerDocument || document; - - var dialog = document.createElement("div"); - dialog.className = "litegraph litesearchbox graphdialog rounded"; - dialog.innerHTML = "Search "; - if (options.do_type_filter){ - dialog.innerHTML += ""; - dialog.innerHTML += ""; - } - dialog.innerHTML += "
"; - - if( root_document.fullscreenElement ) - root_document.fullscreenElement.appendChild(dialog); - else - { - root_document.body.appendChild(dialog); - root_document.body.style.overflow = "hidden"; - } - // dialog element has been appended - - if (options.do_type_filter){ - var selIn = dialog.querySelector(".slot_in_type_filter"); - var selOut = dialog.querySelector(".slot_out_type_filter"); - } - - dialog.close = function() { - that.search_box = null; - this.blur(); - canvas.focus(); - root_document.body.style.overflow = ""; - - setTimeout(function() { - that.canvas.focus(); - }, 20); //important, if canvas loses focus keys wont be captured - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - if (this.ds.scale > 1) { - dialog.style.transform = "scale(" + this.ds.scale + ")"; - } - - // hide on mouse leave - if(options.hide_on_mouse_leave){ - var prevent_timeout = false; - var timeout_close = null; - LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { - if (timeout_close) { - clearTimeout(timeout_close); - timeout_close = null; - } - }); - LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { - if (prevent_timeout){ - return; - } - timeout_close = setTimeout(function() { - dialog.close(); - }, typeof options.hide_on_mouse_leave === "number" ? options.hide_on_mouse_leave : 500); - }); - // if filtering, check focus changed to comboboxes and prevent closing - if (options.do_type_filter){ - selIn.addEventListener("click", function(e) { - prevent_timeout++; - }); - selIn.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selIn.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - selOut.addEventListener("click", function(e) { - prevent_timeout++; - }); - selOut.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selOut.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - } - } - - if (that.search_box) { - that.search_box.close(); - } - that.search_box = dialog; - - var helper = dialog.querySelector(".helper"); - - var first = null; - var timeout = null; - var selected = null; - - var input = dialog.querySelector("input"); - if (input) { - input.addEventListener("blur", function(e) { - this.focus(); - }); - input.addEventListener("keydown", function(e) { - if (e.keyCode == 38) { - //UP - changeSelection(false); - } else if (e.keyCode == 40) { - //DOWN - changeSelection(true); - } else if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - if (selected) { - select(unescape(selected.dataset["type"])); - } else if (first) { - select(first); - } else { - dialog.close(); - } - } else { - if (timeout) { - clearInterval(timeout); - } - timeout = setTimeout(refreshHelper, 10); - return; - } - e.preventDefault(); - e.stopPropagation(); - e.stopImmediatePropagation(); - return true; - }); - } - - // if should filter on type, load and fill selected and choose elements if passed - if (options.do_type_filter){ - if (selIn){ - var aSlots = LiteGraph.slot_types_in; - var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; - - if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION) - options.type_filter_in = "_event_"; - /* this will filter on * .. but better do it manually in case - else if(options.type_filter_in === "" || options.type_filter_in === 0) - options.type_filter_in = "*";*/ - - for (var iK=0; iK (rect.height - 200)) - helper.style.maxHeight = (rect.height - event.layerY - 20) + "px"; - - /* - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - canvas.parentNode.appendChild(dialog); - */ - - input.focus(); - if (options.show_all_on_open) refreshHelper(); - - function select(name) { - if (name) { - if (that.onSearchBoxSelection) { - that.onSearchBoxSelection(name, event, graphcanvas); - } else { - var extra = LiteGraph.searchbox_extras[name.toLowerCase()]; - if (extra) { - name = extra.type; - } - - graphcanvas.graph.beforeChange(); - var node = LiteGraph.createNode(name); - if (node) { - node.pos = graphcanvas.convertEventToCanvasOffset( - event - ); - graphcanvas.graph.add(node, false); - } - - if (extra && extra.data) { - if (extra.data.properties) { - for (var i in extra.data.properties) { - node.addProperty( i, extra.data.properties[i] ); - } - } - if (extra.data.inputs) { - node.inputs = []; - for (var i in extra.data.inputs) { - node.addOutput( - extra.data.inputs[i][0], - extra.data.inputs[i][1] - ); - } - } - if (extra.data.outputs) { - node.outputs = []; - for (var i in extra.data.outputs) { - node.addOutput( - extra.data.outputs[i][0], - extra.data.outputs[i][1] - ); - } - } - if (extra.data.title) { - node.title = extra.data.title; - } - if (extra.data.json) { - node.configure(extra.data.json); - } - - } - - // join node after inserting - if (options.node_from){ - var iS = false; - switch (typeof options.slot_from){ - case "string": - iS = options.node_from.findOutputSlot(options.slot_from); - break; - case "object": - if (options.slot_from.name){ - iS = options.node_from.findOutputSlot(options.slot_from.name); - }else{ - iS = -1; - } - if (iS==-1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; - break; - case "number": - iS = options.slot_from; - break; - default: - iS = 0; // try with first if no name set - } - if (typeof options.node_from.outputs[iS] !== "undefined"){ - if (iS!==false && iS>-1){ - options.node_from.connectByType( iS, node, options.node_from.outputs[iS].type ); - } - }else{ - // console.warn("cant find slot " + options.slot_from); - } - } - if (options.node_to){ - var iS = false; - switch (typeof options.slot_from){ - case "string": - iS = options.node_to.findInputSlot(options.slot_from); - break; - case "object": - if (options.slot_from.name){ - iS = options.node_to.findInputSlot(options.slot_from.name); - }else{ - iS = -1; - } - if (iS==-1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; - break; - case "number": - iS = options.slot_from; - break; - default: - iS = 0; // try with first if no name set - } - if (typeof options.node_to.inputs[iS] !== "undefined"){ - if (iS!==false && iS>-1){ - // try connection - options.node_to.connectByTypeOutput(iS,node,options.node_to.inputs[iS].type); - } - }else{ - // console.warn("cant find slot_nodeTO " + options.slot_from); - } - } - - graphcanvas.graph.afterChange(); - } - } - - dialog.close(); - } - - function changeSelection(forward) { - var prev = selected; - if (selected) { - selected.classList.remove("selected"); - } - if (!selected) { - selected = forward - ? helper.childNodes[0] - : helper.childNodes[helper.childNodes.length]; - } else { - selected = forward - ? selected.nextSibling - : selected.previousSibling; - if (!selected) { - selected = prev; - } - } - if (!selected) { - return; - } - selected.classList.add("selected"); - selected.scrollIntoView({block: "end", behavior: "smooth"}); - } - - function refreshHelper() { - timeout = null; - var str = input.value; - first = null; - helper.innerHTML = ""; - if (!str && !options.show_all_if_empty) { - return; - } - - if (that.onSearchBox) { - var list = that.onSearchBox(helper, str, graphcanvas); - if (list) { - for (var i = 0; i < list.length; ++i) { - addResult(list[i]); - } - } - } else { - var c = 0; - str = str.toLowerCase(); - var filter = graphcanvas.filter || graphcanvas.graph.filter; - - // filter by type preprocess - if(options.do_type_filter && that.search_box){ - var sIn = that.search_box.querySelector(".slot_in_type_filter"); - var sOut = that.search_box.querySelector(".slot_out_type_filter"); - }else{ - var sIn = false; - var sOut = false; - } - - //extras - for (var i in LiteGraph.searchbox_extras) { - var extra = LiteGraph.searchbox_extras[i]; - if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) { - continue; - } - var ctor = LiteGraph.registered_node_types[ extra.type ]; - if( ctor && ctor.filter != filter ) - continue; - if( ! inner_test_filter(extra.type) ) - continue; - addResult( extra.desc, "searchbox_extra" ); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - - var filtered = null; - if (Array.prototype.filter) { //filter supported - var keys = Object.keys( LiteGraph.registered_node_types ); //types - var filtered = keys.filter( inner_test_filter ); - } else { - filtered = []; - for (var i in LiteGraph.registered_node_types) { - if( inner_test_filter(i) ) - filtered.push(i); - } - } - - for (var i = 0; i < filtered.length; i++) { - addResult(filtered[i]); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - - // add general type if filtering - if (options.show_general_after_typefiltered - && (sIn.value || sOut.value) - ){ - filtered_extra = []; - for (var i in LiteGraph.registered_node_types) { - if( inner_test_filter(i, {inTypeOverride: sIn&&sIn.value?"*":false, outTypeOverride: sOut&&sOut.value?"*":false}) ) - filtered_extra.push(i); - } - for (var i = 0; i < filtered_extra.length; i++) { - addResult(filtered_extra[i], "generic_type"); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - } - - // check il filtering gave no results - if ((sIn.value || sOut.value) && - ( (helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter) ) - ){ - filtered_extra = []; - for (var i in LiteGraph.registered_node_types) { - if( inner_test_filter(i, {skipFilter: true}) ) - filtered_extra.push(i); - } - for (var i = 0; i < filtered_extra.length; i++) { - addResult(filtered_extra[i], "not_in_filter"); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - } - - function inner_test_filter( type, optsIn ) - { - var optsIn = optsIn || {}; - var optsDef = { skipFilter: false - ,inTypeOverride: false - ,outTypeOverride: false - }; - var opts = Object.assign(optsDef,optsIn); - var ctor = LiteGraph.registered_node_types[ type ]; - if(filter && ctor.filter != filter ) - return false; - if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1 && (!ctor.title || ctor.title.toLowerCase().indexOf(str) === -1)) - return false; - - // filter by slot IN, OUT types - if(options.do_type_filter && !opts.skipFilter){ - var sType = type; - - var sV = sIn.value; - if (opts.inTypeOverride!==false) sV = opts.inTypeOverride; - //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 - - if(sIn && sV){ - //console.log("will check filter against "+sV); - if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes){ // type is stored - //console.debug("check "+sType+" in "+LiteGraph.registered_slot_in_types[sV].nodes); - var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType); - if (doesInc!==false){ - //console.log(sType+" HAS "+sV); - }else{ - /*console.debug(LiteGraph.registered_slot_in_types[sV]); - console.log(+" DONT includes "+type);*/ - return false; - } - } - } - - var sV = sOut.value; - if (opts.outTypeOverride!==false) sV = opts.outTypeOverride; - //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 - - if(sOut && sV){ - //console.log("search will check filter against "+sV); - if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes){ // type is stored - //console.debug("check "+sType+" in "+LiteGraph.registered_slot_out_types[sV].nodes); - var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType); - if (doesInc!==false){ - //console.log(sType+" HAS "+sV); - }else{ - /*console.debug(LiteGraph.registered_slot_out_types[sV]); - console.log(+" DONT includes "+type);*/ - return false; - } - } - } - } - return true; - } - } - - function addResult(type, className) { - var help = document.createElement("div"); - if (!first) { - first = type; - } - - const nodeType = LiteGraph.registered_node_types[type]; - if (nodeType?.title) { - help.innerText = nodeType?.title; - const typeEl = document.createElement("span"); - typeEl.className = "litegraph lite-search-item-type"; - typeEl.textContent = type; - help.append(typeEl); - } else { - help.innerText = type; - } - - help.dataset["type"] = escape(type); - help.className = "litegraph lite-search-item"; - if (className) { - help.className += " " + className; - } - help.addEventListener("click", function(e) { - select(unescape(this.dataset["type"])); - }); - helper.appendChild(help); - } - } - - return dialog; - }; - - LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) { - if (!node || node.properties[property] === undefined) { - return; - } - - options = options || {}; - var that = this; - - var info = node.getPropertyInfo(property); - var type = info.type; - - var input_html = ""; - - if (type == "string" || type == "number" || type == "array" || type == "object") { - input_html = ""; - } else if ( (type == "enum" || type == "combo") && info.values) { - input_html = ""; - } else if (type == "boolean" || type == "toggle") { - input_html = - ""; - } else { - console.warn("unknown type: " + type); - return; - } - - var dialog = this.createDialog( - "" + - (info.label ? info.label : property) + - "" + - input_html + - "", - options - ); - - var input = false; - if ((type == "enum" || type == "combo") && info.values) { - input = dialog.querySelector("select"); - input.addEventListener("change", function(e) { - dialog.modified(); - setValue(e.target.value); - //var index = e.target.value; - //setValue( e.options[e.selectedIndex].value ); - }); - } else if (type == "boolean" || type == "toggle") { - input = dialog.querySelector("input"); - if (input) { - input.addEventListener("click", function(e) { - dialog.modified(); - setValue(!!input.checked); - }); - } - } else { - input = dialog.querySelector("input"); - if (input) { - input.addEventListener("blur", function(e) { - this.focus(); - }); - - var v = node.properties[property] !== undefined ? node.properties[property] : ""; - if (type !== 'string') { - v = JSON.stringify(v); - } - - input.value = v; - input.addEventListener("keydown", function(e) { - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - // ENTER - inner(); // save - } else if (e.keyCode != 13) { - dialog.modified(); - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - } - } - if (input) input.focus(); - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner); - - function inner() { - setValue(input.value); - } - - function setValue(value) { - - if(info && info.values && info.values.constructor === Object && info.values[value] != undefined ) - value = info.values[value]; - - if (typeof node.properties[property] == "number") { - value = Number(value); - } - if (type == "array" || type == "object") { - value = JSON.parse(value); - } - node.properties[property] = value; - if (node.graph) { - node.graph._version++; - } - if (node.onPropertyChanged) { - node.onPropertyChanged(property, value); - } - if(options.onclose) - options.onclose(); - dialog.close(); - node.setDirtyCanvas(true, true); - } - - return dialog; - }; - - // TODO refactor, theer are different dialog, some uses createDialog, some dont - LGraphCanvas.prototype.createDialog = function(html, options) { - var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true }; - options = Object.assign(def_options, options || {}); - - var dialog = document.createElement("div"); - dialog.className = "graphdialog"; - dialog.innerHTML = html; - dialog.is_modified = false; - - var rect = this.canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (options.position) { - offsetx += options.position[0]; - offsety += options.position[1]; - } else if (options.event) { - offsetx += options.event.clientX; - offsety += options.event.clientY; - } //centered - else { - offsetx += this.canvas.width * 0.5; - offsety += this.canvas.height * 0.5; - } - - dialog.style.left = offsetx + "px"; - dialog.style.top = offsety + "px"; - - this.canvas.parentNode.appendChild(dialog); - - // acheck for input and use default behaviour: save on enter, close on esc - if (options.checkForInput){ - var aI = []; - var focused = false; - if (aI = dialog.querySelectorAll("input")){ - aI.forEach(function(iX) { - iX.addEventListener("keydown",function(e){ - dialog.modified(); - if (e.keyCode == 27) { - dialog.close(); - } else if (e.keyCode != 13) { - return; - } - // set value ? - e.preventDefault(); - e.stopPropagation(); - }); - if (!focused) iX.focus(); - }); - } - } - - dialog.modified = function(){ - dialog.is_modified = true; - } - dialog.close = function() { - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - var dialogCloseTimer = null; - var prevent_timeout = false; - dialog.addEventListener("mouseleave", function(e) { - if (prevent_timeout) - return; - if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) - if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) - dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); - }); - dialog.addEventListener("mouseenter", function(e) { - if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) - if(dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - var selInDia = dialog.querySelectorAll("select"); - if (selInDia){ - // if filtering, check focus changed to comboboxes and prevent closing - selInDia.forEach(function(selIn) { - selIn.addEventListener("click", function(e) { - prevent_timeout++; - }); - selIn.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selIn.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - }); - } - - return dialog; - }; - - LGraphCanvas.prototype.createPanel = function(title, options) { - options = options || {}; - - var ref_window = options.window || window; - var root = document.createElement("div"); - root.className = "litegraph dialog"; - root.innerHTML = "
"; - root.header = root.querySelector(".dialog-header"); - - if(options.width) - root.style.width = options.width + (options.width.constructor === Number ? "px" : ""); - if(options.height) - root.style.height = options.height + (options.height.constructor === Number ? "px" : ""); - if(options.closable) - { - var close = document.createElement("span"); - close.innerHTML = "✕"; - close.classList.add("close"); - close.addEventListener("click",function(){ - root.close(); - }); - root.header.appendChild(close); - } - root.title_element = root.querySelector(".dialog-title"); - root.title_element.innerText = title; - root.content = root.querySelector(".dialog-content"); - root.alt_content = root.querySelector(".dialog-alt-content"); - root.footer = root.querySelector(".dialog-footer"); - - root.close = function() - { - if (root.onClose && typeof root.onClose == "function"){ - root.onClose(); - } - if(root.parentNode) - root.parentNode.removeChild(root); - /* XXX CHECK THIS */ - if(this.parentNode){ - this.parentNode.removeChild(this); - } - /* XXX this was not working, was fixed with an IF, check this */ - } - - // function to swap panel content - root.toggleAltContent = function(force){ - if (typeof force != "undefined"){ - var vTo = force ? "block" : "none"; - var vAlt = force ? "none" : "block"; - }else{ - var vTo = root.alt_content.style.display != "block" ? "block" : "none"; - var vAlt = root.alt_content.style.display != "block" ? "none" : "block"; - } - root.alt_content.style.display = vTo; - root.content.style.display = vAlt; - } - - root.toggleFooterVisibility = function(force){ - if (typeof force != "undefined"){ - var vTo = force ? "block" : "none"; - }else{ - var vTo = root.footer.style.display != "block" ? "block" : "none"; - } - root.footer.style.display = vTo; - } - - root.clear = function() - { - this.content.innerHTML = ""; - } - - root.addHTML = function(code, classname, on_footer) - { - var elem = document.createElement("div"); - if(classname) - elem.className = classname; - elem.innerHTML = code; - if(on_footer) - root.footer.appendChild(elem); - else - root.content.appendChild(elem); - return elem; - } - - root.addButton = function( name, callback, options ) - { - var elem = document.createElement("button"); - elem.innerText = name; - elem.options = options; - elem.classList.add("btn"); - elem.addEventListener("click",callback); - root.footer.appendChild(elem); - return elem; - } - - root.addSeparator = function() - { - var elem = document.createElement("div"); - elem.className = "separator"; - root.content.appendChild(elem); - } - - root.addWidget = function( type, name, value, options, callback ) - { - options = options || {}; - var str_value = String(value); - type = type.toLowerCase(); - if(type == "number") - str_value = value.toFixed(3); - - var elem = document.createElement("div"); - elem.className = "property"; - elem.innerHTML = ""; - elem.querySelector(".property_name").innerText = options.label || name; - var value_element = elem.querySelector(".property_value"); - value_element.innerText = str_value; - elem.dataset["property"] = name; - elem.dataset["type"] = options.type || type; - elem.options = options; - elem.value = value; - - if( type == "code" ) - elem.addEventListener("click", function(e){ root.inner_showCodePad( this.dataset["property"] ); }); - else if (type == "boolean") - { - elem.classList.add("boolean"); - if(value) - elem.classList.add("bool-on"); - elem.addEventListener("click", function(){ - //var v = node.properties[this.dataset["property"]]; - //node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; - var propname = this.dataset["property"]; - this.value = !this.value; - this.classList.toggle("bool-on"); - this.querySelector(".property_value").innerText = this.value ? "true" : "false"; - innerChange(propname, this.value ); - }); - } - else if (type == "string" || type == "number") - { - value_element.setAttribute("contenteditable",true); - value_element.addEventListener("keydown", function(e){ - if(e.code == "Enter" && (type != "string" || !e.shiftKey)) // allow for multiline - { - e.preventDefault(); - this.blur(); - } - }); - value_element.addEventListener("blur", function(){ - var v = this.innerText; - var propname = this.parentNode.dataset["property"]; - var proptype = this.parentNode.dataset["type"]; - if( proptype == "number") - v = Number(v); - innerChange(propname, v); - }); - } - else if (type == "enum" || type == "combo") { - var str_value = LGraphCanvas.getPropertyPrintableValue( value, options.values ); - value_element.innerText = str_value; - - value_element.addEventListener("click", function(event){ - var values = options.values || []; - var propname = this.parentNode.dataset["property"]; - var elem_that = this; - var menu = new LiteGraph.ContextMenu(values,{ - event: event, - className: "dark", - callback: inner_clicked - }, - ref_window); - function inner_clicked(v, option, event) { - //node.setProperty(propname,v); - //graphcanvas.dirty_canvas = true; - elem_that.innerText = v; - innerChange(propname,v); - return false; - } - }); - } - - root.content.appendChild(elem); - - function innerChange(name, value) - { - //console.log("change",name,value); - //that.dirty_canvas = true; - if(options.callback) - options.callback(name,value,options); - if(callback) - callback(name,value,options); - } - - return elem; - } - - if (root.onOpen && typeof root.onOpen == "function") root.onOpen(); - - return root; - }; - - LGraphCanvas.getPropertyPrintableValue = function(value, values) - { - if(!values) - return String(value); - - if(values.constructor === Array) - { - return String(value); - } - - if(values.constructor === Object) - { - var desc_value = ""; - for(var k in values) - { - if(values[k] != value) - continue; - desc_value = k; - break; - } - return String(value) + " ("+desc_value+")"; - } - } - - LGraphCanvas.prototype.closePanels = function(){ - var panel = document.querySelector("#node-panel"); - if(panel) - panel.close(); - var panel = document.querySelector("#option-panel"); - if(panel) - panel.close(); - } - - LGraphCanvas.prototype.showShowGraphOptionsPanel = function(refOpts, obEv, refMenu, refMenu2){ - if(this.constructor && this.constructor.name == "HTMLDivElement"){ - // assume coming from the menu event click - if (!obEv || !obEv.event || !obEv.event.target || !obEv.event.target.lgraphcanvas){ - console.warn("Canvas not found"); // need a ref to canvas obj - /*console.debug(event); - console.debug(event.target);*/ - return; - } - var graphcanvas = obEv.event.target.lgraphcanvas; - }else{ - // assume called internally - var graphcanvas = this; - } - graphcanvas.closePanels(); - var ref_window = graphcanvas.getCanvasWindow(); - panel = graphcanvas.createPanel("Options",{ - closable: true - ,window: ref_window - ,onOpen: function(){ - graphcanvas.OPTIONPANEL_IS_OPEN = true; - } - ,onClose: function(){ - graphcanvas.OPTIONPANEL_IS_OPEN = false; - graphcanvas.options_panel = null; - } - }); - graphcanvas.options_panel = panel; - panel.id = "option-panel"; - panel.classList.add("settings"); - - function inner_refresh(){ - - panel.content.innerHTML = ""; //clear - - var fUpdate = function(name, value, options){ - switch(name){ - /*case "Render mode": - // Case "".. - if (options.values && options.key){ - var kV = Object.values(options.values).indexOf(value); - if (kV>=0 && options.values[kV]){ - console.debug("update graph options: "+options.key+": "+kV); - graphcanvas[options.key] = kV; - //console.debug(graphcanvas); - break; - } - } - console.warn("unexpected options"); - console.debug(options); - break;*/ - default: - //console.debug("want to update graph options: "+name+": "+value); - if (options && options.key){ - name = options.key; - } - if (options.values){ - value = Object.values(options.values).indexOf(value); - } - //console.debug("update graph option: "+name+": "+value); - graphcanvas[name] = value; - break; - } - }; - - // panel.addWidget( "string", "Graph name", "", {}, fUpdate); // implement - - var aProps = LiteGraph.availableCanvasOptions; - aProps.sort(); - for(var pI in aProps){ - var pX = aProps[pI]; - panel.addWidget( "boolean", pX, graphcanvas[pX], {key: pX, on: "True", off: "False"}, fUpdate); - } - - var aLinks = [ graphcanvas.links_render_mode ]; - panel.addWidget( "combo", "Render mode", LiteGraph.LINK_RENDER_MODES[graphcanvas.links_render_mode], {key: "links_render_mode", values: LiteGraph.LINK_RENDER_MODES}, fUpdate); - - panel.addSeparator(); - - panel.footer.innerHTML = ""; // clear - - } - inner_refresh(); - - graphcanvas.canvas.parentNode.appendChild( panel ); - } - - LGraphCanvas.prototype.showShowNodePanel = function( node ) - { - this.SELECTED_NODE = node; - this.closePanels(); - var ref_window = this.getCanvasWindow(); - var that = this; - var graphcanvas = this; - var panel = this.createPanel(node.title || "",{ - closable: true - ,window: ref_window - ,onOpen: function(){ - graphcanvas.NODEPANEL_IS_OPEN = true; - } - ,onClose: function(){ - graphcanvas.NODEPANEL_IS_OPEN = false; - graphcanvas.node_panel = null; - } - }); - graphcanvas.node_panel = panel; - panel.id = "node-panel"; - panel.node = node; - panel.classList.add("settings"); - - function inner_refresh() - { - panel.content.innerHTML = ""; //clear - panel.addHTML(""+node.type+""+(node.constructor.desc || "")+""); - - panel.addHTML("

Properties

"); - - var fUpdate = function(name,value){ - graphcanvas.graph.beforeChange(node); - switch(name){ - case "Title": - node.title = value; - break; - case "Mode": - var kV = Object.values(LiteGraph.NODE_MODES).indexOf(value); - if (kV>=0 && LiteGraph.NODE_MODES[kV]){ - node.changeMode(kV); - }else{ - console.warn("unexpected mode: "+value); - } - break; - case "Color": - if (LGraphCanvas.node_colors[value]){ - node.color = LGraphCanvas.node_colors[value].color; - node.bgcolor = LGraphCanvas.node_colors[value].bgcolor; - }else{ - console.warn("unexpected color: "+value); - } - break; - default: - node.setProperty(name,value); - break; - } - graphcanvas.graph.afterChange(); - graphcanvas.dirty_canvas = true; - }; - - panel.addWidget( "string", "Title", node.title, {}, fUpdate); - - panel.addWidget( "combo", "Mode", LiteGraph.NODE_MODES[node.mode], {values: LiteGraph.NODE_MODES}, fUpdate); - - var nodeCol = ""; - if (node.color !== undefined){ - nodeCol = Object.keys(LGraphCanvas.node_colors).filter(function(nK){ return LGraphCanvas.node_colors[nK].color == node.color; }); - } - - panel.addWidget( "combo", "Color", nodeCol, {values: Object.keys(LGraphCanvas.node_colors)}, fUpdate); - - for(var pName in node.properties) - { - var value = node.properties[pName]; - var info = node.getPropertyInfo(pName); - var type = info.type || "string"; - - //in case the user wants control over the side panel widget - if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(pName,panel) ) - continue; - - panel.addWidget( info.widget || info.type, pName, value, info, fUpdate); - } - - panel.addSeparator(); - - if(node.onShowCustomPanelInfo) - node.onShowCustomPanelInfo(panel); - - panel.footer.innerHTML = ""; // clear - panel.addButton("Delete",function(){ - if(node.block_delete) - return; - node.graph.remove(node); - panel.close(); - }).classList.add("delete"); - } - - panel.inner_showCodePad = function( propname ) - { - panel.classList.remove("settings"); - panel.classList.add("centered"); - - - /*if(window.CodeFlask) //disabled for now - { - panel.content.innerHTML = "
"; - var flask = new CodeFlask( "div.code", { language: 'js' }); - flask.updateCode(node.properties[propname]); - flask.onUpdate( function(code) { - node.setProperty(propname, code); - }); - } - else - {*/ - panel.alt_content.innerHTML = ""; - var textarea = panel.alt_content.querySelector("textarea"); - var fDoneWith = function(){ - panel.toggleAltContent(false); //if(node_prop_div) node_prop_div.style.display = "block"; // panel.close(); - panel.toggleFooterVisibility(true); - textarea.parentNode.removeChild(textarea); - panel.classList.add("settings"); - panel.classList.remove("centered"); - inner_refresh(); - } - textarea.value = node.properties[propname]; - textarea.addEventListener("keydown", function(e){ - if(e.code == "Enter" && e.ctrlKey ) - { - node.setProperty(propname, textarea.value); - fDoneWith(); - } - }); - panel.toggleAltContent(true); - panel.toggleFooterVisibility(false); - textarea.style.height = "calc(100% - 40px)"; - /*}*/ - var assign = panel.addButton( "Assign", function(){ - node.setProperty(propname, textarea.value); - fDoneWith(); - }); - panel.alt_content.appendChild(assign); //panel.content.appendChild(assign); - var button = panel.addButton( "Close", fDoneWith); - button.style.float = "right"; - panel.alt_content.appendChild(button); // panel.content.appendChild(button); - } - - inner_refresh(); - - this.canvas.parentNode.appendChild( panel ); - } - - LGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node) - { - console.log("showing subgraph properties dialog"); - - var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); - if(old_panel) - old_panel.close(); - - var panel = this.createPanel("Subgraph Inputs",{closable:true, width: 500}); - panel.node = node; - panel.classList.add("subgraph_dialog"); - - function inner_refresh() - { - panel.clear(); - - //show currents - if(node.inputs) - for(var i = 0; i < node.inputs.length; ++i) - { - var input = node.inputs[i]; - if(input.not_subgraph_input) - continue; - var html = " "; - var elem = panel.addHTML(html,"subgraph_property"); - elem.dataset["name"] = input.name; - elem.dataset["slot"] = i; - elem.querySelector(".name").innerText = input.name; - elem.querySelector(".type").innerText = input.type; - elem.querySelector("button").addEventListener("click",function(e){ - node.removeInput( Number( this.parentNode.dataset["slot"] ) ); - inner_refresh(); - }); - } - } - - //add extra - var html = " + NameType"; - var elem = panel.addHTML(html,"subgraph_property extra", true); - elem.querySelector("button").addEventListener("click", function(e){ - var elem = this.parentNode; - var name = elem.querySelector(".name").value; - var type = elem.querySelector(".type").value; - if(!name || node.findInputSlot(name) != -1) - return; - node.addInput(name,type); - elem.querySelector(".name").value = ""; - elem.querySelector(".type").value = ""; - inner_refresh(); - }); - - inner_refresh(); - this.canvas.parentNode.appendChild(panel); - return panel; - } - LGraphCanvas.prototype.showSubgraphPropertiesDialogRight = function (node) { - - // console.log("showing subgraph properties dialog"); - var that = this; - // old_panel if old_panel is exist close it - var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); - if (old_panel) - old_panel.close(); - // new panel - var panel = this.createPanel("Subgraph Outputs", { closable: true, width: 500 }); - panel.node = node; - panel.classList.add("subgraph_dialog"); - - function inner_refresh() { - panel.clear(); - //show currents - if (node.outputs) - for (var i = 0; i < node.outputs.length; ++i) { - var input = node.outputs[i]; - if (input.not_subgraph_output) - continue; - var html = " "; - var elem = panel.addHTML(html, "subgraph_property"); - elem.dataset["name"] = input.name; - elem.dataset["slot"] = i; - elem.querySelector(".name").innerText = input.name; - elem.querySelector(".type").innerText = input.type; - elem.querySelector("button").addEventListener("click", function (e) { - node.removeOutput(Number(this.parentNode.dataset["slot"])); - inner_refresh(); - }); - } - } - - //add extra - var html = " + NameType"; - var elem = panel.addHTML(html, "subgraph_property extra", true); - elem.querySelector(".name").addEventListener("keydown", function (e) { - if (e.keyCode == 13) { - addOutput.apply(this) - } - }) - elem.querySelector("button").addEventListener("click", function (e) { - addOutput.apply(this) - }); - function addOutput() { - var elem = this.parentNode; - var name = elem.querySelector(".name").value; - var type = elem.querySelector(".type").value; - if (!name || node.findOutputSlot(name) != -1) - return; - node.addOutput(name, type); - elem.querySelector(".name").value = ""; - elem.querySelector(".type").value = ""; - inner_refresh(); - } - - inner_refresh(); - this.canvas.parentNode.appendChild(panel); - return panel; - } - LGraphCanvas.prototype.checkPanels = function() - { - if(!this.canvas) - return; - var panels = this.canvas.parentNode.querySelectorAll(".litegraph.dialog"); - for(var i = 0; i < panels.length; ++i) - { - var panel = panels[i]; - if( !panel.node ) - continue; - if( !panel.node.graph || panel.graph != this.graph ) - panel.close(); - } - } - - LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) { - node.graph.beforeChange(/*?*/); - - var fApplyMultiNode = function(node){ - node.collapse(); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.graph.afterChange(/*?*/); - }; - - LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) { - node.pin(); - }; - - LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) { - new LiteGraph.ContextMenu( - LiteGraph.NODE_MODES, - { event: e, callback: inner_clicked, parentMenu: menu, node: node } - ); - - function inner_clicked(v) { - if (!node) { - return; - } - var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v); - var fApplyMultiNode = function(node){ - if (kV>=0 && LiteGraph.NODE_MODES[kV]) - node.changeMode(kV); - else{ - console.warn("unexpected mode: "+v); - node.changeMode(LiteGraph.ALWAYS); - } - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - } - - return false; - }; - - LGraphCanvas.onMenuNodeColors = function(value, options, e, menu, node) { - if (!node) { - throw "no node for color"; - } - - var values = []; - values.push({ - value: null, - content: - "No color" - }); - - for (var i in LGraphCanvas.node_colors) { - var color = LGraphCanvas.node_colors[i]; - var value = { - value: i, - content: - "" + - i + - "" - }; - values.push(value); - } - new LiteGraph.ContextMenu(values, { - event: e, - callback: inner_clicked, - parentMenu: menu, - node: node - }); - - function inner_clicked(v) { - if (!node) { - return; - } - - var color = v.value ? LGraphCanvas.node_colors[v.value] : null; - - var fApplyColor = function(node){ - if (color) { - if (node.constructor === LiteGraph.LGraphGroup) { - node.color = color.groupcolor; - } else { - node.color = color.color; - node.bgcolor = color.bgcolor; - } - } else { - delete node.color; - delete node.bgcolor; - } - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyColor(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyColor(graphcanvas.selected_nodes[i]); - } - } - node.setDirtyCanvas(true, true); - } - - return false; - }; - - LGraphCanvas.onMenuNodeShapes = function(value, options, e, menu, node) { - if (!node) { - throw "no node passed"; - } - - new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES, { - event: e, - callback: inner_clicked, - parentMenu: menu, - node: node - }); - - function inner_clicked(v) { - if (!node) { - return; - } - node.graph.beforeChange(/*?*/); //node - - var fApplyMultiNode = function(node){ - node.shape = v; - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.graph.afterChange(/*?*/); //node - node.setDirtyCanvas(true); - } - - return false; - }; - - LGraphCanvas.onMenuNodeRemove = function(value, options, e, menu, node) { - if (!node) { - throw "no node passed"; - } - - var graph = node.graph; - graph.beforeChange(); - - - var fApplyMultiNode = function(node){ - if (node.removable === false) { - return; - } - graph.remove(node); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - graph.afterChange(); - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.onMenuNodeToSubgraph = function(value, options, e, menu, node) { - var graph = node.graph; - var graphcanvas = LGraphCanvas.active_canvas; - if(!graphcanvas) //?? - return; - - var nodes_list = Object.values( graphcanvas.selected_nodes || {} ); - if( !nodes_list.length ) - nodes_list = [ node ]; - - var subgraph_node = LiteGraph.createNode("graph/subgraph"); - subgraph_node.pos = node.pos.concat(); - graph.add(subgraph_node); - - subgraph_node.buildFromNodes( nodes_list ); - - graphcanvas.deselectAllNodes(); - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) { - - node.graph.beforeChange(); - - var newSelected = {}; - - var fApplyMultiNode = function(node){ - if (node.clonable === false) { - return; - } - var newnode = node.clone(); - if (!newnode) { - return; - } - newnode.pos = [node.pos[0] + 5, node.pos[1] + 5]; - node.graph.add(newnode); - newSelected[newnode.id] = newnode; - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - if(Object.keys(newSelected).length){ - graphcanvas.selectNodes(newSelected); - } - - node.graph.afterChange(); - - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.node_colors = { - red: { color: "#322", bgcolor: "#533", groupcolor: "#A88" }, - brown: { color: "#332922", bgcolor: "#593930", groupcolor: "#b06634" }, - green: { color: "#232", bgcolor: "#353", groupcolor: "#8A8" }, - blue: { color: "#223", bgcolor: "#335", groupcolor: "#88A" }, - pale_blue: { - color: "#2a363b", - bgcolor: "#3f5159", - groupcolor: "#3f789e" - }, - cyan: { color: "#233", bgcolor: "#355", groupcolor: "#8AA" }, - purple: { color: "#323", bgcolor: "#535", groupcolor: "#a1309b" }, - yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" }, - black: { color: "#222", bgcolor: "#000", groupcolor: "#444" } - }; - - LGraphCanvas.prototype.getCanvasMenuOptions = function() { - var options = null; - var that = this; - if (this.getMenuOptions) { - options = this.getMenuOptions(); - } else { - options = [ - { - content: "Add Node", - has_submenu: true, - callback: LGraphCanvas.onMenuAdd - }, - { content: "Add Group", callback: LGraphCanvas.onGroupAdd }, - //{ content: "Arrange", callback: that.graph.arrange }, - //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } - ]; - /*if (LiteGraph.showCanvasOptions){ - options.push({ content: "Options", callback: that.showShowGraphOptionsPanel }); - }*/ - - if (Object.keys(this.selected_nodes).length > 1) { - options.push({ - content: "Align", - has_submenu: true, - callback: LGraphCanvas.onGroupAlign, - }) - } - - if (this._graph_stack && this._graph_stack.length > 0) { - options.push(null, { - content: "Close subgraph", - callback: this.closeSubgraph.bind(this) - }); - } - } - - if (this.getExtraMenuOptions) { - var extra = this.getExtraMenuOptions(this, options); - if (extra) { - options = options.concat(extra); - } - } - - return options; - }; - - //called by processContextMenu to extract the menu list - LGraphCanvas.prototype.getNodeMenuOptions = function(node) { - var options = null; - - if (node.getMenuOptions) { - options = node.getMenuOptions(this); - } else { - options = [ - { - content: "Inputs", - has_submenu: true, - disabled: true, - callback: LGraphCanvas.showMenuNodeOptionalInputs - }, - { - content: "Outputs", - has_submenu: true, - disabled: true, - callback: LGraphCanvas.showMenuNodeOptionalOutputs - }, - null, - { - content: "Properties", - has_submenu: true, - callback: LGraphCanvas.onShowMenuNodeProperties - }, - { - content: "Properties Panel", - callback: function(item, options, e, menu, node) { LGraphCanvas.active_canvas.showShowNodePanel(node) } - }, - null, - { - content: "Title", - callback: LGraphCanvas.onShowPropertyEditor - }, - { - content: "Mode", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeMode - }]; - if(node.resizable !== false){ - options.push({ - content: "Resize", callback: LGraphCanvas.onMenuResizeNode - }); - } - options.push( - { - content: "Collapse", - callback: LGraphCanvas.onMenuNodeCollapse - }, - { content: "Pin", callback: LGraphCanvas.onMenuNodePin }, - { - content: "Colors", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeColors - }, - { - content: "Shapes", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeShapes - }, - null - ); - } - - if (node.onGetInputs) { - var inputs = node.onGetInputs(); - if (inputs && inputs.length) { - options[0].disabled = false; - } - } - - if (node.onGetOutputs) { - var outputs = node.onGetOutputs(); - if (outputs && outputs.length) { - options[1].disabled = false; - } - } - - if (node.getExtraMenuOptions) { - var extra = node.getExtraMenuOptions(this, options); - if (extra) { - extra.push(null); - options = extra.concat(options); - } - } - - if (node.clonable !== false) { - options.push({ - content: "Clone", - callback: LGraphCanvas.onMenuNodeClone - }); - } - - if(0) //TODO - options.push({ - content: "To Subgraph", - callback: LGraphCanvas.onMenuNodeToSubgraph - }); - - if (Object.keys(this.selected_nodes).length > 1) { - options.push({ - content: "Align Selected To", - has_submenu: true, - callback: LGraphCanvas.onNodeAlign, - }) - } - - options.push(null, { - content: "Remove", - disabled: !(node.removable !== false && !node.block_delete ), - callback: LGraphCanvas.onMenuNodeRemove - }); - - if (node.graph && node.graph.onGetNodeMenuOptions) { - node.graph.onGetNodeMenuOptions(options, node); - } - - return options; - }; - - LGraphCanvas.prototype.getGroupMenuOptions = function(node) { - var o = [ - { content: "Title", callback: LGraphCanvas.onShowPropertyEditor }, - { - content: "Color", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeColors - }, - { - content: "Font size", - property: "font_size", - type: "Number", - callback: LGraphCanvas.onShowPropertyEditor - }, - null, - { content: "Remove", callback: LGraphCanvas.onMenuNodeRemove } - ]; - - return o; - }; - - LGraphCanvas.prototype.processContextMenu = function(node, event) { - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var menu_info = null; - var options = { - event: event, - callback: inner_option_clicked, - extra: node - }; - - if(node) - options.title = node.type; - - //check if mouse is in input - var slot = null; - if (node) { - slot = node.getSlotInPosition(event.canvasX, event.canvasY); - LGraphCanvas.active_node = node; - } - - if (slot) { - //on slot - menu_info = []; - if (node.getSlotMenuOptions) { - menu_info = node.getSlotMenuOptions(slot); - } else { - if ( - slot && - slot.output && - slot.output.links && - slot.output.links.length - ) { - menu_info.push({ content: "Disconnect Links", slot: slot }); - } - var _slot = slot.input || slot.output; - if (_slot.removable){ - menu_info.push( - _slot.locked - ? "Cannot remove" - : { content: "Remove Slot", slot: slot } - ); - } - if (!_slot.nameLocked){ - menu_info.push({ content: "Rename Slot", slot: slot }); - } - - } - options.title = - (slot.input ? slot.input.type : slot.output.type) || "*"; - if (slot.input && slot.input.type == LiteGraph.ACTION) { - options.title = "Action"; - } - if (slot.output && slot.output.type == LiteGraph.EVENT) { - options.title = "Event"; - } - } else { - if (node) { - //on node - menu_info = this.getNodeMenuOptions(node); - } else { - menu_info = this.getCanvasMenuOptions(); - var group = this.graph.getGroupOnPos( - event.canvasX, - event.canvasY - ); - if (group) { - //on group - menu_info.push(null, { - content: "Edit Group", - has_submenu: true, - submenu: { - title: "Group", - extra: group, - options: this.getGroupMenuOptions(group) - } - }); - } - } - } - - //show menu - if (!menu_info) { - return; - } - - var menu = new LiteGraph.ContextMenu(menu_info, options, ref_window); - - function inner_option_clicked(v, options, e) { - if (!v) { - return; - } - - if (v.content == "Remove Slot") { - var info = v.slot; - node.graph.beforeChange(); - if (info.input) { - node.removeInput(info.slot); - } else if (info.output) { - node.removeOutput(info.slot); - } - node.graph.afterChange(); - return; - } else if (v.content == "Disconnect Links") { - var info = v.slot; - node.graph.beforeChange(); - if (info.output) { - node.disconnectOutput(info.slot); - } else if (info.input) { - node.disconnectInput(info.slot); - } - node.graph.afterChange(); - return; - } else if (v.content == "Rename Slot") { - var info = v.slot; - var slot_info = info.input - ? node.getInputInfo(info.slot) - : node.getOutputInfo(info.slot); - var dialog = that.createDialog( - "Name", - options - ); - var input = dialog.querySelector("input"); - if (input && slot_info) { - input.value = slot_info.label || ""; - } - var inner = function(){ - node.graph.beforeChange(); - if (input.value) { - if (slot_info) { - slot_info.label = input.value; - } - that.setDirty(true); - } - dialog.close(); - node.graph.afterChange(); - } - dialog.querySelector("button").addEventListener("click", inner); - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - inner(); // save - } else if (e.keyCode != 13 && e.target.localName != "textarea") { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - input.focus(); - } - - //if(v.callback) - // return v.callback.call(that, node, options, e, menu, that, event ); - } - }; - - //API ************************************************* - //like rect but rounded corners - if (typeof(window) != "undefined" && window.CanvasRenderingContext2D && !window.CanvasRenderingContext2D.prototype.roundRect) { - window.CanvasRenderingContext2D.prototype.roundRect = function( - x, - y, - w, - h, - radius, - radius_low - ) { - var top_left_radius = 0; - var top_right_radius = 0; - var bottom_left_radius = 0; - var bottom_right_radius = 0; - - if ( radius === 0 ) - { - this.rect(x,y,w,h); - return; - } - - if(radius_low === undefined) - radius_low = radius; - - //make it compatible with official one - if(radius != null && radius.constructor === Array) - { - if(radius.length == 1) - top_left_radius = top_right_radius = bottom_left_radius = bottom_right_radius = radius[0]; - else if(radius.length == 2) - { - top_left_radius = bottom_right_radius = radius[0]; - top_right_radius = bottom_left_radius = radius[1]; - } - else if(radius.length == 4) - { - top_left_radius = radius[0]; - top_right_radius = radius[1]; - bottom_left_radius = radius[2]; - bottom_right_radius = radius[3]; - } - else - return; - } - else //old using numbers - { - top_left_radius = radius || 0; - top_right_radius = radius || 0; - bottom_left_radius = radius_low || 0; - bottom_right_radius = radius_low || 0; - } - - //top right - this.moveTo(x + top_left_radius, y); - this.lineTo(x + w - top_right_radius, y); - this.quadraticCurveTo(x + w, y, x + w, y + top_right_radius); - - //bottom right - this.lineTo(x + w, y + h - bottom_right_radius); - this.quadraticCurveTo( - x + w, - y + h, - x + w - bottom_right_radius, - y + h - ); - - //bottom left - this.lineTo(x + bottom_right_radius, y + h); - this.quadraticCurveTo(x, y + h, x, y + h - bottom_left_radius); - - //top left - this.lineTo(x, y + bottom_left_radius); - this.quadraticCurveTo(x, y, x + top_left_radius, y); - }; - }//if - - function compareObjects(a, b) { - for (var i in a) { - if (a[i] != b[i]) { - return false; - } - } - return true; - } - LiteGraph.compareObjects = compareObjects; - - function distance(a, b) { - return Math.sqrt( - (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) - ); - } - LiteGraph.distance = distance; - - function colorToString(c) { - return ( - "rgba(" + - Math.round(c[0] * 255).toFixed() + - "," + - Math.round(c[1] * 255).toFixed() + - "," + - Math.round(c[2] * 255).toFixed() + - "," + - (c.length == 4 ? c[3].toFixed(2) : "1.0") + - ")" - ); - } - LiteGraph.colorToString = colorToString; - - function isInsideRectangle(x, y, left, top, width, height) { - if (left < x && left + width > x && top < y && top + height > y) { - return true; - } - return false; - } - LiteGraph.isInsideRectangle = isInsideRectangle; - - //[minx,miny,maxx,maxy] - function growBounding(bounding, x, y) { - if (x < bounding[0]) { - bounding[0] = x; - } else if (x > bounding[2]) { - bounding[2] = x; - } - - if (y < bounding[1]) { - bounding[1] = y; - } else if (y > bounding[3]) { - bounding[3] = y; - } - } - LiteGraph.growBounding = growBounding; - - //point inside bounding box - function isInsideBounding(p, bb) { - if ( - p[0] < bb[0][0] || - p[1] < bb[0][1] || - p[0] > bb[1][0] || - p[1] > bb[1][1] - ) { - return false; - } - return true; - } - LiteGraph.isInsideBounding = isInsideBounding; - - //bounding overlap, format: [ startx, starty, width, height ] - function overlapBounding(a, b) { - var A_end_x = a[0] + a[2]; - var A_end_y = a[1] + a[3]; - var B_end_x = b[0] + b[2]; - var B_end_y = b[1] + b[3]; - - if ( - a[0] > B_end_x || - a[1] > B_end_y || - A_end_x < b[0] || - A_end_y < b[1] - ) { - return false; - } - return true; - } - LiteGraph.overlapBounding = overlapBounding; - - //Convert a hex value to its decimal value - the inputted hex must be in the - // format of a hex triplet - the kind we use for HTML colours. The function - // will return an array with three values. - function hex2num(hex) { - if (hex.charAt(0) == "#") { - hex = hex.slice(1); - } //Remove the '#' char - if there is one. - hex = hex.toUpperCase(); - var hex_alphabets = "0123456789ABCDEF"; - var value = new Array(3); - var k = 0; - var int1, int2; - for (var i = 0; i < 6; i += 2) { - int1 = hex_alphabets.indexOf(hex.charAt(i)); - int2 = hex_alphabets.indexOf(hex.charAt(i + 1)); - value[k] = int1 * 16 + int2; - k++; - } - return value; - } - - LiteGraph.hex2num = hex2num; - - //Give a array with three values as the argument and the function will return - // the corresponding hex triplet. - function num2hex(triplet) { - var hex_alphabets = "0123456789ABCDEF"; - var hex = "#"; - var int1, int2; - for (var i = 0; i < 3; i++) { - int1 = triplet[i] / 16; - int2 = triplet[i] % 16; - - hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); - } - return hex; - } - - LiteGraph.num2hex = num2hex; - - /* LiteGraph GUI elements used for canvas editing *************************************/ - - /** - * ContextMenu from LiteGUI - * - * @class ContextMenu - * @constructor - * @param {Array} values (allows object { title: "Nice text", callback: function ... }) - * @param {Object} options [optional] Some options:\ - * - title: title to show on top of the menu - * - callback: function to call when an option is clicked, it receives the item information - * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback - * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position - */ - function ContextMenu(values, options) { - options = options || {}; - this.options = options; - var that = this; - - //to link a menu with its parent - if (options.parentMenu) { - if (options.parentMenu.constructor !== this.constructor) { - console.error( - "parentMenu must be of class ContextMenu, ignoring it" - ); - options.parentMenu = null; - } else { - this.parentMenu = options.parentMenu; - this.parentMenu.lock = true; - this.parentMenu.current_submenu = this; - } - } - - var eventClass = null; - if(options.event) //use strings because comparing classes between windows doesnt work - eventClass = options.event.constructor.name; - if ( eventClass !== "MouseEvent" && - eventClass !== "CustomEvent" && - eventClass !== "PointerEvent" - ) { - console.error( - "Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. ("+eventClass+")" - ); - options.event = null; - } - - var root = document.createElement("div"); - root.className = "litegraph litecontextmenu litemenubar-panel"; - if (options.className) { - root.className += " " + options.className; - } - root.style.minWidth = 100; - root.style.minHeight = 100; - root.style.pointerEvents = "none"; - setTimeout(function() { - root.style.pointerEvents = "auto"; - }, 100); //delay so the mouse up event is not caught by this element - - //this prevents the default context browser menu to open in case this menu was created when pressing right button - LiteGraph.pointerListenerAdd(root,"up", - function(e) { - //console.log("pointerevents: ContextMenu up root prevent"); - e.preventDefault(); - return true; - }, - true - ); - root.addEventListener( - "contextmenu", - function(e) { - if (e.button != 2) { - //right button - return false; - } - e.preventDefault(); - return false; - }, - true - ); - - LiteGraph.pointerListenerAdd(root,"down", - function(e) { - //console.log("pointerevents: ContextMenu down"); - if (e.button == 2) { - that.close(); - e.preventDefault(); - return true; - } - }, - true - ); - - function on_mouse_wheel(e) { - var pos = parseInt(root.style.top); - root.style.top = - (pos + e.deltaY * options.scroll_speed).toFixed() + "px"; - e.preventDefault(); - return true; - } - - if (!options.scroll_speed) { - options.scroll_speed = 0.1; - } - - root.addEventListener("wheel", on_mouse_wheel, true); - root.addEventListener("mousewheel", on_mouse_wheel, true); - - this.root = root; - - //title - if (options.title) { - var element = document.createElement("div"); - element.className = "litemenu-title"; - element.innerHTML = options.title; - root.appendChild(element); - } - - //entries - var num = 0; - for (var i=0; i < values.length; i++) { - var name = values.constructor == Array ? values[i] : i; - if (name != null && name.constructor !== String) { - name = name.content === undefined ? String(name) : name.content; - } - var value = values[i]; - this.addItem(name, value, options); - num++; - } - - //close on leave? touch enabled devices won't work TODO use a global device detector and condition on that - /*LiteGraph.pointerListenerAdd(root,"leave", function(e) { - console.log("pointerevents: ContextMenu leave"); - if (that.lock) { - return; - } - if (root.closing_timer) { - clearTimeout(root.closing_timer); - } - root.closing_timer = setTimeout(that.close.bind(that, e), 500); - //that.close(e); - });*/ - - LiteGraph.pointerListenerAdd(root,"enter", function(e) { - //console.log("pointerevents: ContextMenu enter"); - if (root.closing_timer) { - clearTimeout(root.closing_timer); - } - }); - - //insert before checking position - var root_document = document; - if (options.event) { - root_document = options.event.target.ownerDocument; - } - - if (!root_document) { - root_document = document; - } - - if( root_document.fullscreenElement ) - root_document.fullscreenElement.appendChild(root); - else - root_document.body.appendChild(root); - - //compute best position - var left = options.left || 0; - var top = options.top || 0; - if (options.event) { - left = options.event.clientX - 10; - top = options.event.clientY - 10; - if (options.title) { - top -= 20; - } - - if (options.parentMenu) { - var rect = options.parentMenu.root.getBoundingClientRect(); - left = rect.left + rect.width; - } - - var body_rect = document.body.getBoundingClientRect(); - var root_rect = root.getBoundingClientRect(); - if(body_rect.height == 0) - console.error("document.body height is 0. That is dangerous, set html,body { height: 100%; }"); - - if (body_rect.width && left > body_rect.width - root_rect.width - 10) { - left = body_rect.width - root_rect.width - 10; - } - if (body_rect.height && top > body_rect.height - root_rect.height - 10) { - top = body_rect.height - root_rect.height - 10; - } - } - - root.style.left = left + "px"; - root.style.top = top + "px"; - - if (options.scale) { - root.style.transform = "scale(" + options.scale + ")"; - } - } - - ContextMenu.prototype.addItem = function(name, value, options) { - var that = this; - options = options || {}; - - var element = document.createElement("div"); - element.className = "litemenu-entry submenu"; - - var disabled = false; - - if (value === null) { - element.classList.add("separator"); - //element.innerHTML = "
" - //continue; - } else { - element.innerHTML = value && value.title ? value.title : name; - element.value = value; - - if (value) { - if (value.disabled) { - disabled = true; - element.classList.add("disabled"); - } - if (value.submenu || value.has_submenu) { - element.classList.add("has_submenu"); - } - } - - if (typeof value == "function") { - element.dataset["value"] = name; - element.onclick_callback = value; - } else { - element.dataset["value"] = value; - } - - if (value.className) { - element.className += " " + value.className; - } - } - - this.root.appendChild(element); - if (!disabled) { - element.addEventListener("click", inner_onclick); - } - if (!disabled && options.autoopen) { - LiteGraph.pointerListenerAdd(element,"enter",inner_over); - } - - function inner_over(e) { - var value = this.value; - if (!value || !value.has_submenu) { - return; - } - //if it is a submenu, autoopen like the item was clicked - inner_onclick.call(this, e); - } - - //menu option clicked - function inner_onclick(e) { - var value = this.value; - var close_parent = true; - - if (that.current_submenu) { - that.current_submenu.close(e); - } - - //global callback - if (options.callback) { - var r = options.callback.call( - this, - value, - options, - e, - that, - options.node - ); - if (r === true) { - close_parent = false; - } - } - - //special cases - if (value) { - if ( - value.callback && - !options.ignore_item_callbacks && - value.disabled !== true - ) { - //item callback - var r = value.callback.call( - this, - value, - options, - e, - that, - options.extra - ); - if (r === true) { - close_parent = false; - } - } - if (value.submenu) { - if (!value.submenu.options) { - throw "ContextMenu submenu needs options"; - } - var submenu = new that.constructor(value.submenu.options, { - callback: value.submenu.callback, - event: e, - parentMenu: that, - ignore_item_callbacks: - value.submenu.ignore_item_callbacks, - title: value.submenu.title, - extra: value.submenu.extra, - autoopen: options.autoopen - }); - close_parent = false; - } - } - - if (close_parent && !that.lock) { - that.close(); - } - } - - return element; - }; - - ContextMenu.prototype.close = function(e, ignore_parent_menu) { - if (this.root.parentNode) { - this.root.parentNode.removeChild(this.root); - } - if (this.parentMenu && !ignore_parent_menu) { - this.parentMenu.lock = false; - this.parentMenu.current_submenu = null; - if (e === undefined) { - this.parentMenu.close(); - } else if ( - e && - !ContextMenu.isCursorOverElement(e, this.parentMenu.root) - ) { - ContextMenu.trigger(this.parentMenu.root, LiteGraph.pointerevents_method+"leave", e); - } - } - if (this.current_submenu) { - this.current_submenu.close(e, true); - } - - if (this.root.closing_timer) { - clearTimeout(this.root.closing_timer); - } - - // TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu - // on key press, allow filtering/selecting the context menu elements - }; - - //this code is used to trigger events easily (used in the context menu mouseleave - ContextMenu.trigger = function(element, event_name, params, origin) { - var evt = document.createEvent("CustomEvent"); - evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail - evt.srcElement = origin; - if (element.dispatchEvent) { - element.dispatchEvent(evt); - } else if (element.__events) { - element.__events.dispatchEvent(evt); - } - //else nothing seems binded here so nothing to do - return evt; - }; - - //returns the top most menu - ContextMenu.prototype.getTopMenu = function() { - if (this.options.parentMenu) { - return this.options.parentMenu.getTopMenu(); - } - return this; - }; - - ContextMenu.prototype.getFirstEvent = function() { - if (this.options.parentMenu) { - return this.options.parentMenu.getFirstEvent(); - } - return this.options.event; - }; - - ContextMenu.isCursorOverElement = function(event, element) { - var left = event.clientX; - var top = event.clientY; - var rect = element.getBoundingClientRect(); - if (!rect) { - return false; - } - if ( - top > rect.top && - top < rect.top + rect.height && - left > rect.left && - left < rect.left + rect.width - ) { - return true; - } - return false; - }; - - LiteGraph.ContextMenu = ContextMenu; - - LiteGraph.closeAllContextMenus = function(ref_window) { - ref_window = ref_window || window; - - var elements = ref_window.document.querySelectorAll(".litecontextmenu"); - if (!elements.length) { - return; - } - - var result = []; - for (var i = 0; i < elements.length; i++) { - result.push(elements[i]); - } - - for (var i=0; i < result.length; i++) { - if (result[i].close) { - result[i].close(); - } else if (result[i].parentNode) { - result[i].parentNode.removeChild(result[i]); - } - } - }; - - LiteGraph.extendClass = function(target, origin) { - for (var i in origin) { - //copy class properties - if (target.hasOwnProperty(i)) { - continue; - } - target[i] = origin[i]; - } - - if (origin.prototype) { - //copy prototype properties - for (var i in origin.prototype) { - //only enumerable - if (!origin.prototype.hasOwnProperty(i)) { - continue; - } - - if (target.prototype.hasOwnProperty(i)) { - //avoid overwriting existing ones - continue; - } - - //copy getters - if (origin.prototype.__lookupGetter__(i)) { - target.prototype.__defineGetter__( - i, - origin.prototype.__lookupGetter__(i) - ); - } else { - target.prototype[i] = origin.prototype[i]; - } - - //and setters - if (origin.prototype.__lookupSetter__(i)) { - target.prototype.__defineSetter__( - i, - origin.prototype.__lookupSetter__(i) - ); - } - } - } - }; - - //used by some widgets to render a curve editor - function CurveEditor( points ) - { - this.points = points; - this.selected = -1; - this.nearest = -1; - this.size = null; //stores last size used - this.must_update = true; - this.margin = 5; - } - - CurveEditor.sampleCurve = function(f,points) - { - if(!points) - return; - for(var i = 0; i < points.length - 1; ++i) - { - var p = points[i]; - var pn = points[i+1]; - if(pn[0] < f) - continue; - var r = (pn[0] - p[0]); - if( Math.abs(r) < 0.00001 ) - return p[1]; - var local_f = (f - p[0]) / r; - return p[1] * (1.0 - local_f) + pn[1] * local_f; - } - return 0; - } - - CurveEditor.prototype.draw = function( ctx, size, graphcanvas, background_color, line_color, inactive ) - { - var points = this.points; - if(!points) - return; - this.size = size; - var w = size[0] - this.margin * 2; - var h = size[1] - this.margin * 2; - - line_color = line_color || "#666"; - - ctx.save(); - ctx.translate(this.margin,this.margin); - - if(background_color) - { - ctx.fillStyle = "#111"; - ctx.fillRect(0,0,w,h); - ctx.fillStyle = "#222"; - ctx.fillRect(w*0.5,0,1,h); - ctx.strokeStyle = "#333"; - ctx.strokeRect(0,0,w,h); - } - ctx.strokeStyle = line_color; - if(inactive) - ctx.globalAlpha = 0.5; - ctx.beginPath(); - for(var i = 0; i < points.length; ++i) - { - var p = points[i]; - ctx.lineTo( p[0] * w, (1.0 - p[1]) * h ); - } - ctx.stroke(); - ctx.globalAlpha = 1; - if(!inactive) - for(var i = 0; i < points.length; ++i) - { - var p = points[i]; - ctx.fillStyle = this.selected == i ? "#FFF" : (this.nearest == i ? "#DDD" : "#AAA"); - ctx.beginPath(); - ctx.arc( p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2 ); - ctx.fill(); - } - ctx.restore(); - } - - //localpos is mouse in curve editor space - CurveEditor.prototype.onMouseDown = function( localpos, graphcanvas ) - { - var points = this.points; - if(!points) - return; - if( localpos[1] < 0 ) - return; - - //this.captureInput(true); - var w = this.size[0] - this.margin * 2; - var h = this.size[1] - this.margin * 2; - var x = localpos[0] - this.margin; - var y = localpos[1] - this.margin; - var pos = [x,y]; - var max_dist = 30 / graphcanvas.ds.scale; - //search closer one - this.selected = this.getCloserPoint(pos, max_dist); - //create one - if(this.selected == -1) - { - var point = [x / w, 1 - y / h]; - points.push(point); - points.sort(function(a,b){ return a[0] - b[0]; }); - this.selected = points.indexOf(point); - this.must_update = true; - } - if(this.selected != -1) - return true; - } - - CurveEditor.prototype.onMouseMove = function( localpos, graphcanvas ) - { - var points = this.points; - if(!points) - return; - var s = this.selected; - if(s < 0) - return; - var x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2 ); - var y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2 ); - var curvepos = [(localpos[0] - this.margin),(localpos[1] - this.margin)]; - var max_dist = 30 / graphcanvas.ds.scale; - this._nearest = this.getCloserPoint(curvepos, max_dist); - var point = points[s]; - if(point) - { - var is_edge_point = s == 0 || s == points.length - 1; - if( !is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10) ) - { - points.splice(s,1); - this.selected = -1; - return; - } - if( !is_edge_point ) //not edges - point[0] = clamp(x, 0, 1); - else - point[0] = s == 0 ? 0 : 1; - point[1] = 1.0 - clamp(y, 0, 1); - points.sort(function(a,b){ return a[0] - b[0]; }); - this.selected = points.indexOf(point); - this.must_update = true; - } - } - - CurveEditor.prototype.onMouseUp = function( localpos, graphcanvas ) - { - this.selected = -1; - return false; - } - - CurveEditor.prototype.getCloserPoint = function(pos, max_dist) - { - var points = this.points; - if(!points) - return -1; - max_dist = max_dist || 30; - var w = (this.size[0] - this.margin * 2); - var h = (this.size[1] - this.margin * 2); - var num = points.length; - var p2 = [0,0]; - var min_dist = 1000000; - var closest = -1; - var last_valid = -1; - for(var i = 0; i < num; ++i) - { - var p = points[i]; - p2[0] = p[0] * w; - p2[1] = (1.0 - p[1]) * h; - if(p2[0] < pos[0]) - last_valid = i; - var dist = vec2.distance(pos,p2); - if(dist > min_dist || dist > max_dist) - continue; - closest = i; - min_dist = dist; - } - return closest; - } - - LiteGraph.CurveEditor = CurveEditor; - - //used to create nodes from wrapping functions - LiteGraph.getParameterNames = function(func) { - return (func + "") - .replace(/[/][/].*$/gm, "") // strip single-line comments - .replace(/\s+/g, "") // strip white space - .replace(/[/][*][^/*]*[*][/]/g, "") // strip multi-line comments /**/ - .split("){", 1)[0] - .replace(/^[^(]*[(]/, "") // extract the parameters - .replace(/=[^,]+/g, "") // strip any ES6 defaults - .split(",") - .filter(Boolean); // split & filter [""] - }; - - /* helper for interaction: pointer, touch, mouse Listeners - used by LGraphCanvas DragAndScale ContextMenu*/ - LiteGraph.pointerListenerAdd = function(oDOM, sEvIn, fCall, capture=false) { - if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!=="function"){ - //console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall); - return; // -- break -- - } - - var sMethod = LiteGraph.pointerevents_method; - var sEvent = sEvIn; - - // UNDER CONSTRUCTION - // convert pointerevents to touch event when not available - if (sMethod=="pointer" && !window.PointerEvent){ - console.warn("sMethod=='pointer' && !window.PointerEvent"); - console.log("Converting pointer["+sEvent+"] : down move up cancel enter TO touchstart touchmove touchend, etc .."); - switch(sEvent){ - case "down":{ - sMethod = "touch"; - sEvent = "start"; - break; - } - case "move":{ - sMethod = "touch"; - //sEvent = "move"; - break; - } - case "up":{ - sMethod = "touch"; - sEvent = "end"; - break; - } - case "cancel":{ - sMethod = "touch"; - //sEvent = "cancel"; - break; - } - case "enter":{ - console.log("debug: Should I send a move event?"); // ??? - break; - } - // case "over": case "out": not used at now - default:{ - console.warn("PointerEvent not available in this browser ? The event "+sEvent+" would not be called"); - } - } - } - - switch(sEvent){ - //both pointer and move events - case "down": case "up": case "move": case "over": case "out": case "enter": - { - oDOM.addEventListener(sMethod+sEvent, fCall, capture); - } - // only pointerevents - case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": - { - if (sMethod!="mouse"){ - return oDOM.addEventListener(sMethod+sEvent, fCall, capture); - } - } - // not "pointer" || "mouse" - default: - return oDOM.addEventListener(sEvent, fCall, capture); - } - } - LiteGraph.pointerListenerRemove = function(oDOM, sEvent, fCall, capture=false) { - if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!=="function"){ - //console.log("cant pointerListenerRemove "+oDOM+", "+sEvent+", "+fCall); - return; // -- break -- - } - switch(sEvent){ - //both pointer and move events - case "down": case "up": case "move": case "over": case "out": case "enter": - { - if (LiteGraph.pointerevents_method=="pointer" || LiteGraph.pointerevents_method=="mouse"){ - oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); - } - } - // only pointerevents - case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": - { - if (LiteGraph.pointerevents_method=="pointer"){ - return oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); - } - } - // not "pointer" || "mouse" - default: - return oDOM.removeEventListener(sEvent, fCall, capture); - } - } - - function clamp(v, a, b) { - return a > v ? a : b < v ? b : v; - }; - global.clamp = clamp; - - if (typeof window != "undefined" && !window["requestAnimationFrame"]) { - window.requestAnimationFrame = - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - function(callback) { - window.setTimeout(callback, 1000 / 60); - }; - } -})(this); - -if (typeof exports != "undefined") { - exports.LiteGraph = this.LiteGraph; - exports.LGraph = this.LGraph; - exports.LLink = this.LLink; - exports.LGraphNode = this.LGraphNode; - exports.LGraphGroup = this.LGraphGroup; - exports.DragAndScale = this.DragAndScale; - exports.LGraphCanvas = this.LGraphCanvas; - exports.ContextMenu = this.ContextMenu; -} - - diff --git a/web/lib/litegraph.css b/web/lib/litegraph.css deleted file mode 100644 index 5524e24ba..000000000 --- a/web/lib/litegraph.css +++ /dev/null @@ -1,693 +0,0 @@ -/* this CSS contains only the basic CSS needed to run the app and use it */ - -.lgraphcanvas { - /*cursor: crosshair;*/ - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; - outline: none; - font-family: Tahoma, sans-serif; -} - -.lgraphcanvas * { - box-sizing: border-box; -} - -.litegraph.litecontextmenu { - font-family: Tahoma, sans-serif; - position: fixed; - top: 100px; - left: 100px; - min-width: 100px; - color: #aaf; - padding: 0; - box-shadow: 0 0 10px black !important; - background-color: #2e2e2e !important; - z-index: 10; -} - -.litegraph.litecontextmenu.dark { - background-color: #000 !important; -} - -.litegraph.litecontextmenu .litemenu-title img { - margin-top: 2px; - margin-left: 2px; - margin-right: 4px; -} - -.litegraph.litecontextmenu .litemenu-entry { - margin: 2px; - padding: 2px; -} - -.litegraph.litecontextmenu .litemenu-entry.submenu { - background-color: #2e2e2e !important; -} - -.litegraph.litecontextmenu.dark .litemenu-entry.submenu { - background-color: #000 !important; -} - -.litegraph .litemenubar ul { - font-family: Tahoma, sans-serif; - margin: 0; - padding: 0; -} - -.litegraph .litemenubar li { - font-size: 14px; - color: #999; - display: inline-block; - min-width: 50px; - padding-left: 10px; - padding-right: 10px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; - cursor: pointer; -} - -.litegraph .litemenubar li:hover { - background-color: #777; - color: #eee; -} - -.litegraph .litegraph .litemenubar-panel { - position: absolute; - top: 5px; - left: 5px; - min-width: 100px; - background-color: #444; - box-shadow: 0 0 3px black; - padding: 4px; - border-bottom: 2px solid #aaf; - z-index: 10; -} - -.litegraph .litemenu-entry, -.litemenu-title { - font-size: 12px; - color: #aaa; - padding: 0 0 0 4px; - margin: 2px; - padding-left: 2px; - -moz-user-select: none; - -webkit-user-select: none; - user-select: none; - cursor: pointer; -} - -.litegraph .litemenu-entry .icon { - display: inline-block; - width: 12px; - height: 12px; - margin: 2px; - vertical-align: top; -} - -.litegraph .litemenu-entry.checked .icon { - background-color: #aaf; -} - -.litegraph .litemenu-entry .more { - float: right; - padding-right: 5px; -} - -.litegraph .litemenu-entry.disabled { - opacity: 0.5; - cursor: default; -} - -.litegraph .litemenu-entry.separator { - display: block; - border-top: 1px solid #333; - border-bottom: 1px solid #666; - width: 100%; - height: 0px; - margin: 3px 0 2px 0; - background-color: transparent; - padding: 0 !important; - cursor: default !important; -} - -.litegraph .litemenu-entry.has_submenu { - border-right: 2px solid cyan; -} - -.litegraph .litemenu-title { - color: #dde; - background-color: #111; - margin: 0; - padding: 2px; - cursor: default; -} - -.litegraph .litemenu-entry:hover:not(.disabled):not(.separator) { - background-color: #444 !important; - color: #eee; - transition: all 0.2s; -} - -.litegraph .litemenu-entry .property_name { - display: inline-block; - text-align: left; - min-width: 80px; - min-height: 1.2em; -} - -.litegraph .litemenu-entry .property_value { - display: inline-block; - background-color: rgba(0, 0, 0, 0.5); - text-align: right; - min-width: 80px; - min-height: 1.2em; - vertical-align: middle; - padding-right: 10px; -} - -.litegraph.litesearchbox { - font-family: Tahoma, sans-serif; - position: absolute; - background-color: rgba(0, 0, 0, 0.5); - padding-top: 4px; -} - -.litegraph.litesearchbox input, -.litegraph.litesearchbox select { - margin-top: 3px; - min-width: 60px; - min-height: 1.5em; - background-color: black; - border: 0; - color: white; - padding-left: 10px; - margin-right: 5px; - max-width: 300px; -} - -.litegraph.litesearchbox .name { - display: inline-block; - min-width: 60px; - min-height: 1.5em; - padding-left: 10px; -} - -.litegraph.litesearchbox .helper { - overflow: auto; - max-height: 200px; - margin-top: 2px; -} - -.litegraph.lite-search-item { - font-family: Tahoma, sans-serif; - background-color: rgba(0, 0, 0, 0.5); - color: white; - padding-top: 2px; -} - -.litegraph.lite-search-item.not_in_filter{ - /*background-color: rgba(50, 50, 50, 0.5);*/ - /*color: #999;*/ - color: #B99; - font-style: italic; -} - -.litegraph.lite-search-item.generic_type{ - /*background-color: rgba(50, 50, 50, 0.5);*/ - /*color: #DD9;*/ - color: #999; - font-style: italic; -} - -.litegraph.lite-search-item:hover, -.litegraph.lite-search-item.selected { - cursor: pointer; - background-color: white; - color: black; -} - -.litegraph.lite-search-item-type { - display: inline-block; - background: rgba(0,0,0,0.2); - margin-left: 5px; - font-size: 14px; - padding: 2px 5px; - position: relative; - top: -2px; - opacity: 0.8; - border-radius: 4px; - } - -/* DIALOGs ******/ - -.litegraph .dialog { - position: absolute; - top: 50%; - left: 50%; - margin-top: -150px; - margin-left: -200px; - - background-color: #2A2A2A; - - min-width: 400px; - min-height: 200px; - box-shadow: 0 0 4px #111; - border-radius: 6px; -} - -.litegraph .dialog.settings { - left: 10px; - top: 10px; - height: calc( 100% - 20px ); - margin: auto; - max-width: 50%; -} - -.litegraph .dialog.centered { - top: 50px; - left: 50%; - position: absolute; - transform: translateX(-50%); - min-width: 600px; - min-height: 300px; - height: calc( 100% - 100px ); - margin: auto; -} - -.litegraph .dialog .close { - float: right; - margin: 4px; - margin-right: 10px; - cursor: pointer; - font-size: 1.4em; -} - -.litegraph .dialog .close:hover { - color: white; -} - -.litegraph .dialog .dialog-header { - color: #AAA; - border-bottom: 1px solid #161616; -} - -.litegraph .dialog .dialog-header { height: 40px; } -.litegraph .dialog .dialog-footer { height: 50px; padding: 10px; border-top: 1px solid #1a1a1a;} - -.litegraph .dialog .dialog-header .dialog-title { - font: 20px "Arial"; - margin: 4px; - padding: 4px 10px; - display: inline-block; -} - -.litegraph .dialog .dialog-content, .litegraph .dialog .dialog-alt-content { - height: calc(100% - 90px); - width: 100%; - min-height: 100px; - display: inline-block; - color: #AAA; - /*background-color: black;*/ - overflow: auto; -} - -.litegraph .dialog .dialog-content h3 { - margin: 10px; -} - -.litegraph .dialog .dialog-content .connections { - flex-direction: row; -} - -.litegraph .dialog .dialog-content .connections .connections_side { - width: calc(50% - 5px); - min-height: 100px; - background-color: black; - display: flex; -} - -.litegraph .dialog .node_type { - font-size: 1.2em; - display: block; - margin: 10px; -} - -.litegraph .dialog .node_desc { - opacity: 0.5; - display: block; - margin: 10px; -} - -.litegraph .dialog .separator { - display: block; - width: calc( 100% - 4px ); - height: 1px; - border-top: 1px solid #000; - border-bottom: 1px solid #333; - margin: 10px 2px; - padding: 0; -} - -.litegraph .dialog .property { - margin-bottom: 2px; - padding: 4px; -} - -.litegraph .dialog .property:hover { - background: #545454; -} - -.litegraph .dialog .property_name { - color: #737373; - display: inline-block; - text-align: left; - vertical-align: top; - width: 160px; - padding-left: 4px; - overflow: hidden; - margin-right: 6px; -} - -.litegraph .dialog .property:hover .property_name { - color: white; -} - -.litegraph .dialog .property_value { - display: inline-block; - text-align: right; - color: #AAA; - background-color: #1A1A1A; - /*width: calc( 100% - 122px );*/ - max-width: calc( 100% - 162px ); - min-width: 200px; - max-height: 300px; - min-height: 20px; - padding: 4px; - padding-right: 12px; - overflow: hidden; - cursor: pointer; - border-radius: 3px; -} - -.litegraph .dialog .property_value:hover { - color: white; -} - -.litegraph .dialog .property.boolean .property_value { - padding-right: 30px; - color: #A88; - /*width: auto; - float: right;*/ -} - -.litegraph .dialog .property.boolean.bool-on .property_name{ - color: #8A8; -} -.litegraph .dialog .property.boolean.bool-on .property_value{ - color: #8A8; -} - -.litegraph .dialog .btn { - border: 0; - border-radius: 4px; - padding: 4px 20px; - margin-left: 0px; - background-color: #060606; - color: #8e8e8e; -} - -.litegraph .dialog .btn:hover { - background-color: #111; - color: #FFF; -} - -.litegraph .dialog .btn.delete:hover { - background-color: #F33; - color: black; -} - -.litegraph .subgraph_property { - padding: 4px; -} - -.litegraph .subgraph_property:hover { - background-color: #333; -} - -.litegraph .subgraph_property.extra { - margin-top: 8px; -} - -.litegraph .subgraph_property span.name { - font-size: 1.3em; - padding-left: 4px; -} - -.litegraph .subgraph_property span.type { - opacity: 0.5; - margin-right: 20px; - padding-left: 4px; -} - -.litegraph .subgraph_property span.label { - display: inline-block; - width: 60px; - padding: 0px 10px; -} - -.litegraph .subgraph_property input { - width: 140px; - color: #999; - background-color: #1A1A1A; - border-radius: 4px; - border: 0; - margin-right: 10px; - padding: 4px; - padding-left: 10px; -} - -.litegraph .subgraph_property button { - background-color: #1c1c1c; - color: #aaa; - border: 0; - border-radius: 2px; - padding: 4px 10px; - cursor: pointer; -} - -.litegraph .subgraph_property.extra { - color: #ccc; -} - -.litegraph .subgraph_property.extra input { - background-color: #111; -} - -.litegraph .bullet_icon { - margin-left: 10px; - border-radius: 10px; - width: 12px; - height: 12px; - background-color: #666; - display: inline-block; - margin-top: 2px; - margin-right: 4px; - transition: background-color 0.1s ease 0s; - -moz-transition: background-color 0.1s ease 0s; -} - -.litegraph .bullet_icon:hover { - background-color: #698; - cursor: pointer; -} - -/* OLD */ - -.graphcontextmenu { - padding: 4px; - min-width: 100px; -} - -.graphcontextmenu-title { - color: #dde; - background-color: #222; - margin: 0; - padding: 2px; - cursor: default; -} - -.graphmenu-entry { - box-sizing: border-box; - margin: 2px; - padding-left: 20px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; - transition: all linear 0.3s; -} - -.graphmenu-entry.event, -.litemenu-entry.event { - border-left: 8px solid orange; - padding-left: 12px; -} - -.graphmenu-entry.disabled { - opacity: 0.3; -} - -.graphmenu-entry.submenu { - border-right: 2px solid #eee; -} - -.graphmenu-entry:hover { - background-color: #555; -} - -.graphmenu-entry.separator { - background-color: #111; - border-bottom: 1px solid #666; - height: 1px; - width: calc(100% - 20px); - -moz-width: calc(100% - 20px); - -webkit-width: calc(100% - 20px); -} - -.graphmenu-entry .property_name { - display: inline-block; - text-align: left; - min-width: 80px; - min-height: 1.2em; -} - -.graphmenu-entry .property_value, -.litemenu-entry .property_value { - display: inline-block; - background-color: rgba(0, 0, 0, 0.5); - text-align: right; - min-width: 80px; - min-height: 1.2em; - vertical-align: middle; - padding-right: 10px; -} - -.graphdialog { - position: absolute; - top: 10px; - left: 10px; - min-height: 2em; - background-color: #333; - font-size: 1.2em; - box-shadow: 0 0 10px black !important; - z-index: 10; -} - -.graphdialog.rounded { - border-radius: 12px; - padding-right: 2px; -} - -.graphdialog .name { - display: inline-block; - min-width: 60px; - min-height: 1.5em; - padding-left: 10px; -} - -.graphdialog input, -.graphdialog textarea, -.graphdialog select { - margin: 3px; - min-width: 60px; - min-height: 1.5em; - background-color: black; - border: 0; - color: white; - padding-left: 10px; - outline: none; -} - -.graphdialog textarea { - min-height: 150px; -} - -.graphdialog button { - margin-top: 3px; - vertical-align: top; - background-color: #999; - border: 0; -} - -.graphdialog button.rounded, -.graphdialog input.rounded { - border-radius: 0 12px 12px 0; -} - -.graphdialog .helper { - overflow: auto; - max-height: 200px; -} - -.graphdialog .help-item { - padding-left: 10px; -} - -.graphdialog .help-item:hover, -.graphdialog .help-item.selected { - cursor: pointer; - background-color: white; - color: black; -} - -.litegraph .dialog { - min-height: 0; -} -.litegraph .dialog .dialog-content { -display: block; -} -.litegraph .dialog .dialog-content .subgraph_property { -padding: 5px; -} -.litegraph .dialog .dialog-footer { -margin: 0; -} -.litegraph .dialog .dialog-footer .subgraph_property { -margin-top: 0; -display: flex; -align-items: center; -padding: 5px; -} -.litegraph .dialog .dialog-footer .subgraph_property .name { -flex: 1; -} -.litegraph .graphdialog { -display: flex; -align-items: center; -border-radius: 20px; -padding: 4px 10px; -position: fixed; -} -.litegraph .graphdialog .name { -padding: 0; -min-height: 0; -font-size: 16px; -vertical-align: middle; -} -.litegraph .graphdialog .value { -font-size: 16px; -min-height: 0; -margin: 0 10px; -padding: 2px 5px; -} -.litegraph .graphdialog input[type="checkbox"] { -width: 16px; -height: 16px; -} -.litegraph .graphdialog button { -padding: 4px 18px; -border-radius: 20px; -cursor: pointer; -} - diff --git a/web/lib/litegraph.extensions.js b/web/lib/litegraph.extensions.js deleted file mode 100644 index 32853fe49..000000000 --- a/web/lib/litegraph.extensions.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Changes the background color of the canvas. - * - * @method updateBackground - * @param {image} String - * @param {clearBackgroundColor} String - * @ - */ -LGraphCanvas.prototype.updateBackground = function (image, clearBackgroundColor) { - this._bg_img = new Image(); - this._bg_img.name = image; - this._bg_img.src = image; - this._bg_img.onload = () => { - this.draw(true, true); - }; - this.background_image = image; - - this.clear_background = true; - this.clear_background_color = clearBackgroundColor; - this._pattern = null -} diff --git a/web/lib/materialdesignicons.min.css b/web/lib/materialdesignicons.min.css deleted file mode 100644 index 459ce9ea8..000000000 --- a/web/lib/materialdesignicons.min.css +++ /dev/null @@ -1,3 +0,0 @@ -@font-face{font-family:"Material Design Icons";src:url("../fonts/materialdesignicons-webfont.eot?v=7.4.47");src:url("../fonts/materialdesignicons-webfont.eot?#iefix&v=7.4.47") format("embedded-opentype"),url("../fonts/materialdesignicons-webfont.woff2?v=7.4.47") format("woff2"),url("../fonts/materialdesignicons-webfont.woff?v=7.4.47") format("woff"),url("../fonts/materialdesignicons-webfont.ttf?v=7.4.47") format("truetype");font-weight:normal;font-style:normal}.mdi:before,.mdi-set{display:inline-block;font:normal normal normal 24px/1 "Material Design Icons";font-size:inherit;text-rendering:auto;line-height:inherit;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.mdi-ab-testing::before{content:"\F01C9"}.mdi-abacus::before{content:"\F16E0"}.mdi-abjad-arabic::before{content:"\F1328"}.mdi-abjad-hebrew::before{content:"\F1329"}.mdi-abugida-devanagari::before{content:"\F132A"}.mdi-abugida-thai::before{content:"\F132B"}.mdi-access-point::before{content:"\F0003"}.mdi-access-point-check::before{content:"\F1538"}.mdi-access-point-minus::before{content:"\F1539"}.mdi-access-point-network::before{content:"\F0002"}.mdi-access-point-network-off::before{content:"\F0BE1"}.mdi-access-point-off::before{content:"\F1511"}.mdi-access-point-plus::before{content:"\F153A"}.mdi-access-point-remove::before{content:"\F153B"}.mdi-account::before{content:"\F0004"}.mdi-account-alert::before{content:"\F0005"}.mdi-account-alert-outline::before{content:"\F0B50"}.mdi-account-arrow-down::before{content:"\F1868"}.mdi-account-arrow-down-outline::before{content:"\F1869"}.mdi-account-arrow-left::before{content:"\F0B51"}.mdi-account-arrow-left-outline::before{content:"\F0B52"}.mdi-account-arrow-right::before{content:"\F0B53"}.mdi-account-arrow-right-outline::before{content:"\F0B54"}.mdi-account-arrow-up::before{content:"\F1867"}.mdi-account-arrow-up-outline::before{content:"\F186A"}.mdi-account-badge::before{content:"\F1B0A"}.mdi-account-badge-outline::before{content:"\F1B0B"}.mdi-account-box::before{content:"\F0006"}.mdi-account-box-edit-outline::before{content:"\F1CC8"}.mdi-account-box-minus-outline::before{content:"\F1CC9"}.mdi-account-box-multiple::before{content:"\F0934"}.mdi-account-box-multiple-outline::before{content:"\F100A"}.mdi-account-box-outline::before{content:"\F0007"}.mdi-account-box-plus-outline::before{content:"\F1CCA"}.mdi-account-cancel::before{content:"\F12DF"}.mdi-account-cancel-outline::before{content:"\F12E0"}.mdi-account-card::before{content:"\F1BA4"}.mdi-account-card-outline::before{content:"\F1BA5"}.mdi-account-cash::before{content:"\F1097"}.mdi-account-cash-outline::before{content:"\F1098"}.mdi-account-check::before{content:"\F0008"}.mdi-account-check-outline::before{content:"\F0BE2"}.mdi-account-child::before{content:"\F0A89"}.mdi-account-child-circle::before{content:"\F0A8A"}.mdi-account-child-outline::before{content:"\F10C8"}.mdi-account-circle::before{content:"\F0009"}.mdi-account-circle-outline::before{content:"\F0B55"}.mdi-account-clock::before{content:"\F0B56"}.mdi-account-clock-outline::before{content:"\F0B57"}.mdi-account-cog::before{content:"\F1370"}.mdi-account-cog-outline::before{content:"\F1371"}.mdi-account-convert::before{content:"\F000A"}.mdi-account-convert-outline::before{content:"\F1301"}.mdi-account-cowboy-hat::before{content:"\F0E9B"}.mdi-account-cowboy-hat-outline::before{content:"\F17F3"}.mdi-account-credit-card::before{content:"\F1BA6"}.mdi-account-credit-card-outline::before{content:"\F1BA7"}.mdi-account-details::before{content:"\F0631"}.mdi-account-details-outline::before{content:"\F1372"}.mdi-account-edit::before{content:"\F06BC"}.mdi-account-edit-outline::before{content:"\F0FFB"}.mdi-account-eye::before{content:"\F0420"}.mdi-account-eye-outline::before{content:"\F127B"}.mdi-account-file::before{content:"\F1CA7"}.mdi-account-file-outline::before{content:"\F1CA8"}.mdi-account-file-text::before{content:"\F1CA9"}.mdi-account-file-text-outline::before{content:"\F1CAA"}.mdi-account-filter::before{content:"\F0936"}.mdi-account-filter-outline::before{content:"\F0F9D"}.mdi-account-group::before{content:"\F0849"}.mdi-account-group-outline::before{content:"\F0B58"}.mdi-account-hard-hat::before{content:"\F05B5"}.mdi-account-hard-hat-outline::before{content:"\F1A1F"}.mdi-account-heart::before{content:"\F0899"}.mdi-account-heart-outline::before{content:"\F0BE3"}.mdi-account-injury::before{content:"\F1815"}.mdi-account-injury-outline::before{content:"\F1816"}.mdi-account-key::before{content:"\F000B"}.mdi-account-key-outline::before{content:"\F0BE4"}.mdi-account-lock::before{content:"\F115E"}.mdi-account-lock-open::before{content:"\F1960"}.mdi-account-lock-open-outline::before{content:"\F1961"}.mdi-account-lock-outline::before{content:"\F115F"}.mdi-account-minus::before{content:"\F000D"}.mdi-account-minus-outline::before{content:"\F0AEC"}.mdi-account-multiple::before{content:"\F000E"}.mdi-account-multiple-check::before{content:"\F08C5"}.mdi-account-multiple-check-outline::before{content:"\F11FE"}.mdi-account-multiple-minus::before{content:"\F05D3"}.mdi-account-multiple-minus-outline::before{content:"\F0BE5"}.mdi-account-multiple-outline::before{content:"\F000F"}.mdi-account-multiple-plus::before{content:"\F0010"}.mdi-account-multiple-plus-outline::before{content:"\F0800"}.mdi-account-multiple-remove::before{content:"\F120A"}.mdi-account-multiple-remove-outline::before{content:"\F120B"}.mdi-account-music::before{content:"\F0803"}.mdi-account-music-outline::before{content:"\F0CE9"}.mdi-account-network::before{content:"\F0011"}.mdi-account-network-off::before{content:"\F1AF1"}.mdi-account-network-off-outline::before{content:"\F1AF2"}.mdi-account-network-outline::before{content:"\F0BE6"}.mdi-account-off::before{content:"\F0012"}.mdi-account-off-outline::before{content:"\F0BE7"}.mdi-account-outline::before{content:"\F0013"}.mdi-account-plus::before{content:"\F0014"}.mdi-account-plus-outline::before{content:"\F0801"}.mdi-account-question::before{content:"\F0B59"}.mdi-account-question-outline::before{content:"\F0B5A"}.mdi-account-reactivate::before{content:"\F152B"}.mdi-account-reactivate-outline::before{content:"\F152C"}.mdi-account-remove::before{content:"\F0015"}.mdi-account-remove-outline::before{content:"\F0AED"}.mdi-account-school::before{content:"\F1A20"}.mdi-account-school-outline::before{content:"\F1A21"}.mdi-account-search::before{content:"\F0016"}.mdi-account-search-outline::before{content:"\F0935"}.mdi-account-settings::before{content:"\F0630"}.mdi-account-settings-outline::before{content:"\F10C9"}.mdi-account-star::before{content:"\F0017"}.mdi-account-star-outline::before{content:"\F0BE8"}.mdi-account-supervisor::before{content:"\F0A8B"}.mdi-account-supervisor-circle::before{content:"\F0A8C"}.mdi-account-supervisor-circle-outline::before{content:"\F14EC"}.mdi-account-supervisor-outline::before{content:"\F112D"}.mdi-account-switch::before{content:"\F0019"}.mdi-account-switch-outline::before{content:"\F04CB"}.mdi-account-sync::before{content:"\F191B"}.mdi-account-sync-outline::before{content:"\F191C"}.mdi-account-tag::before{content:"\F1C1B"}.mdi-account-tag-outline::before{content:"\F1C1C"}.mdi-account-tie::before{content:"\F0CE3"}.mdi-account-tie-hat::before{content:"\F1898"}.mdi-account-tie-hat-outline::before{content:"\F1899"}.mdi-account-tie-outline::before{content:"\F10CA"}.mdi-account-tie-voice::before{content:"\F1308"}.mdi-account-tie-voice-off::before{content:"\F130A"}.mdi-account-tie-voice-off-outline::before{content:"\F130B"}.mdi-account-tie-voice-outline::before{content:"\F1309"}.mdi-account-tie-woman::before{content:"\F1A8C"}.mdi-account-voice::before{content:"\F05CB"}.mdi-account-voice-off::before{content:"\F0ED4"}.mdi-account-wrench::before{content:"\F189A"}.mdi-account-wrench-outline::before{content:"\F189B"}.mdi-adjust::before{content:"\F001A"}.mdi-advertisements::before{content:"\F192A"}.mdi-advertisements-off::before{content:"\F192B"}.mdi-air-conditioner::before{content:"\F001B"}.mdi-air-filter::before{content:"\F0D43"}.mdi-air-horn::before{content:"\F0DAC"}.mdi-air-humidifier::before{content:"\F1099"}.mdi-air-humidifier-off::before{content:"\F1466"}.mdi-air-purifier::before{content:"\F0D44"}.mdi-air-purifier-off::before{content:"\F1B57"}.mdi-airbag::before{content:"\F0BE9"}.mdi-airballoon::before{content:"\F001C"}.mdi-airballoon-outline::before{content:"\F100B"}.mdi-airplane::before{content:"\F001D"}.mdi-airplane-alert::before{content:"\F187A"}.mdi-airplane-check::before{content:"\F187B"}.mdi-airplane-clock::before{content:"\F187C"}.mdi-airplane-cog::before{content:"\F187D"}.mdi-airplane-edit::before{content:"\F187E"}.mdi-airplane-landing::before{content:"\F05D4"}.mdi-airplane-marker::before{content:"\F187F"}.mdi-airplane-minus::before{content:"\F1880"}.mdi-airplane-off::before{content:"\F001E"}.mdi-airplane-plus::before{content:"\F1881"}.mdi-airplane-remove::before{content:"\F1882"}.mdi-airplane-search::before{content:"\F1883"}.mdi-airplane-settings::before{content:"\F1884"}.mdi-airplane-takeoff::before{content:"\F05D5"}.mdi-airport::before{content:"\F084B"}.mdi-alarm::before{content:"\F0020"}.mdi-alarm-bell::before{content:"\F078E"}.mdi-alarm-check::before{content:"\F0021"}.mdi-alarm-light::before{content:"\F078F"}.mdi-alarm-light-off::before{content:"\F171E"}.mdi-alarm-light-off-outline::before{content:"\F171F"}.mdi-alarm-light-outline::before{content:"\F0BEA"}.mdi-alarm-multiple::before{content:"\F0022"}.mdi-alarm-note::before{content:"\F0E71"}.mdi-alarm-note-off::before{content:"\F0E72"}.mdi-alarm-off::before{content:"\F0023"}.mdi-alarm-panel::before{content:"\F15C4"}.mdi-alarm-panel-outline::before{content:"\F15C5"}.mdi-alarm-plus::before{content:"\F0024"}.mdi-alarm-snooze::before{content:"\F068E"}.mdi-album::before{content:"\F0025"}.mdi-alert::before{content:"\F0026"}.mdi-alert-box::before{content:"\F0027"}.mdi-alert-box-outline::before{content:"\F0CE4"}.mdi-alert-circle::before{content:"\F0028"}.mdi-alert-circle-check::before{content:"\F11ED"}.mdi-alert-circle-check-outline::before{content:"\F11EE"}.mdi-alert-circle-outline::before{content:"\F05D6"}.mdi-alert-decagram::before{content:"\F06BD"}.mdi-alert-decagram-outline::before{content:"\F0CE5"}.mdi-alert-minus::before{content:"\F14BB"}.mdi-alert-minus-outline::before{content:"\F14BE"}.mdi-alert-octagon::before{content:"\F0029"}.mdi-alert-octagon-outline::before{content:"\F0CE6"}.mdi-alert-octagram::before{content:"\F0767"}.mdi-alert-octagram-outline::before{content:"\F0CE7"}.mdi-alert-outline::before{content:"\F002A"}.mdi-alert-plus::before{content:"\F14BA"}.mdi-alert-plus-outline::before{content:"\F14BD"}.mdi-alert-remove::before{content:"\F14BC"}.mdi-alert-remove-outline::before{content:"\F14BF"}.mdi-alert-rhombus::before{content:"\F11CE"}.mdi-alert-rhombus-outline::before{content:"\F11CF"}.mdi-alien::before{content:"\F089A"}.mdi-alien-outline::before{content:"\F10CB"}.mdi-align-horizontal-center::before{content:"\F11C3"}.mdi-align-horizontal-distribute::before{content:"\F1962"}.mdi-align-horizontal-left::before{content:"\F11C2"}.mdi-align-horizontal-right::before{content:"\F11C4"}.mdi-align-vertical-bottom::before{content:"\F11C5"}.mdi-align-vertical-center::before{content:"\F11C6"}.mdi-align-vertical-distribute::before{content:"\F1963"}.mdi-align-vertical-top::before{content:"\F11C7"}.mdi-all-inclusive::before{content:"\F06BE"}.mdi-all-inclusive-box::before{content:"\F188D"}.mdi-all-inclusive-box-outline::before{content:"\F188E"}.mdi-allergy::before{content:"\F1258"}.mdi-alpha::before{content:"\F002B"}.mdi-alpha-a::before{content:"\F0AEE"}.mdi-alpha-a-box::before{content:"\F0B08"}.mdi-alpha-a-box-outline::before{content:"\F0BEB"}.mdi-alpha-a-circle::before{content:"\F0BEC"}.mdi-alpha-a-circle-outline::before{content:"\F0BED"}.mdi-alpha-b::before{content:"\F0AEF"}.mdi-alpha-b-box::before{content:"\F0B09"}.mdi-alpha-b-box-outline::before{content:"\F0BEE"}.mdi-alpha-b-circle::before{content:"\F0BEF"}.mdi-alpha-b-circle-outline::before{content:"\F0BF0"}.mdi-alpha-c::before{content:"\F0AF0"}.mdi-alpha-c-box::before{content:"\F0B0A"}.mdi-alpha-c-box-outline::before{content:"\F0BF1"}.mdi-alpha-c-circle::before{content:"\F0BF2"}.mdi-alpha-c-circle-outline::before{content:"\F0BF3"}.mdi-alpha-d::before{content:"\F0AF1"}.mdi-alpha-d-box::before{content:"\F0B0B"}.mdi-alpha-d-box-outline::before{content:"\F0BF4"}.mdi-alpha-d-circle::before{content:"\F0BF5"}.mdi-alpha-d-circle-outline::before{content:"\F0BF6"}.mdi-alpha-e::before{content:"\F0AF2"}.mdi-alpha-e-box::before{content:"\F0B0C"}.mdi-alpha-e-box-outline::before{content:"\F0BF7"}.mdi-alpha-e-circle::before{content:"\F0BF8"}.mdi-alpha-e-circle-outline::before{content:"\F0BF9"}.mdi-alpha-f::before{content:"\F0AF3"}.mdi-alpha-f-box::before{content:"\F0B0D"}.mdi-alpha-f-box-outline::before{content:"\F0BFA"}.mdi-alpha-f-circle::before{content:"\F0BFB"}.mdi-alpha-f-circle-outline::before{content:"\F0BFC"}.mdi-alpha-g::before{content:"\F0AF4"}.mdi-alpha-g-box::before{content:"\F0B0E"}.mdi-alpha-g-box-outline::before{content:"\F0BFD"}.mdi-alpha-g-circle::before{content:"\F0BFE"}.mdi-alpha-g-circle-outline::before{content:"\F0BFF"}.mdi-alpha-h::before{content:"\F0AF5"}.mdi-alpha-h-box::before{content:"\F0B0F"}.mdi-alpha-h-box-outline::before{content:"\F0C00"}.mdi-alpha-h-circle::before{content:"\F0C01"}.mdi-alpha-h-circle-outline::before{content:"\F0C02"}.mdi-alpha-i::before{content:"\F0AF6"}.mdi-alpha-i-box::before{content:"\F0B10"}.mdi-alpha-i-box-outline::before{content:"\F0C03"}.mdi-alpha-i-circle::before{content:"\F0C04"}.mdi-alpha-i-circle-outline::before{content:"\F0C05"}.mdi-alpha-j::before{content:"\F0AF7"}.mdi-alpha-j-box::before{content:"\F0B11"}.mdi-alpha-j-box-outline::before{content:"\F0C06"}.mdi-alpha-j-circle::before{content:"\F0C07"}.mdi-alpha-j-circle-outline::before{content:"\F0C08"}.mdi-alpha-k::before{content:"\F0AF8"}.mdi-alpha-k-box::before{content:"\F0B12"}.mdi-alpha-k-box-outline::before{content:"\F0C09"}.mdi-alpha-k-circle::before{content:"\F0C0A"}.mdi-alpha-k-circle-outline::before{content:"\F0C0B"}.mdi-alpha-l::before{content:"\F0AF9"}.mdi-alpha-l-box::before{content:"\F0B13"}.mdi-alpha-l-box-outline::before{content:"\F0C0C"}.mdi-alpha-l-circle::before{content:"\F0C0D"}.mdi-alpha-l-circle-outline::before{content:"\F0C0E"}.mdi-alpha-m::before{content:"\F0AFA"}.mdi-alpha-m-box::before{content:"\F0B14"}.mdi-alpha-m-box-outline::before{content:"\F0C0F"}.mdi-alpha-m-circle::before{content:"\F0C10"}.mdi-alpha-m-circle-outline::before{content:"\F0C11"}.mdi-alpha-n::before{content:"\F0AFB"}.mdi-alpha-n-box::before{content:"\F0B15"}.mdi-alpha-n-box-outline::before{content:"\F0C12"}.mdi-alpha-n-circle::before{content:"\F0C13"}.mdi-alpha-n-circle-outline::before{content:"\F0C14"}.mdi-alpha-o::before{content:"\F0AFC"}.mdi-alpha-o-box::before{content:"\F0B16"}.mdi-alpha-o-box-outline::before{content:"\F0C15"}.mdi-alpha-o-circle::before{content:"\F0C16"}.mdi-alpha-o-circle-outline::before{content:"\F0C17"}.mdi-alpha-p::before{content:"\F0AFD"}.mdi-alpha-p-box::before{content:"\F0B17"}.mdi-alpha-p-box-outline::before{content:"\F0C18"}.mdi-alpha-p-circle::before{content:"\F0C19"}.mdi-alpha-p-circle-outline::before{content:"\F0C1A"}.mdi-alpha-q::before{content:"\F0AFE"}.mdi-alpha-q-box::before{content:"\F0B18"}.mdi-alpha-q-box-outline::before{content:"\F0C1B"}.mdi-alpha-q-circle::before{content:"\F0C1C"}.mdi-alpha-q-circle-outline::before{content:"\F0C1D"}.mdi-alpha-r::before{content:"\F0AFF"}.mdi-alpha-r-box::before{content:"\F0B19"}.mdi-alpha-r-box-outline::before{content:"\F0C1E"}.mdi-alpha-r-circle::before{content:"\F0C1F"}.mdi-alpha-r-circle-outline::before{content:"\F0C20"}.mdi-alpha-s::before{content:"\F0B00"}.mdi-alpha-s-box::before{content:"\F0B1A"}.mdi-alpha-s-box-outline::before{content:"\F0C21"}.mdi-alpha-s-circle::before{content:"\F0C22"}.mdi-alpha-s-circle-outline::before{content:"\F0C23"}.mdi-alpha-t::before{content:"\F0B01"}.mdi-alpha-t-box::before{content:"\F0B1B"}.mdi-alpha-t-box-outline::before{content:"\F0C24"}.mdi-alpha-t-circle::before{content:"\F0C25"}.mdi-alpha-t-circle-outline::before{content:"\F0C26"}.mdi-alpha-u::before{content:"\F0B02"}.mdi-alpha-u-box::before{content:"\F0B1C"}.mdi-alpha-u-box-outline::before{content:"\F0C27"}.mdi-alpha-u-circle::before{content:"\F0C28"}.mdi-alpha-u-circle-outline::before{content:"\F0C29"}.mdi-alpha-v::before{content:"\F0B03"}.mdi-alpha-v-box::before{content:"\F0B1D"}.mdi-alpha-v-box-outline::before{content:"\F0C2A"}.mdi-alpha-v-circle::before{content:"\F0C2B"}.mdi-alpha-v-circle-outline::before{content:"\F0C2C"}.mdi-alpha-w::before{content:"\F0B04"}.mdi-alpha-w-box::before{content:"\F0B1E"}.mdi-alpha-w-box-outline::before{content:"\F0C2D"}.mdi-alpha-w-circle::before{content:"\F0C2E"}.mdi-alpha-w-circle-outline::before{content:"\F0C2F"}.mdi-alpha-x::before{content:"\F0B05"}.mdi-alpha-x-box::before{content:"\F0B1F"}.mdi-alpha-x-box-outline::before{content:"\F0C30"}.mdi-alpha-x-circle::before{content:"\F0C31"}.mdi-alpha-x-circle-outline::before{content:"\F0C32"}.mdi-alpha-y::before{content:"\F0B06"}.mdi-alpha-y-box::before{content:"\F0B20"}.mdi-alpha-y-box-outline::before{content:"\F0C33"}.mdi-alpha-y-circle::before{content:"\F0C34"}.mdi-alpha-y-circle-outline::before{content:"\F0C35"}.mdi-alpha-z::before{content:"\F0B07"}.mdi-alpha-z-box::before{content:"\F0B21"}.mdi-alpha-z-box-outline::before{content:"\F0C36"}.mdi-alpha-z-circle::before{content:"\F0C37"}.mdi-alpha-z-circle-outline::before{content:"\F0C38"}.mdi-alphabet-aurebesh::before{content:"\F132C"}.mdi-alphabet-cyrillic::before{content:"\F132D"}.mdi-alphabet-greek::before{content:"\F132E"}.mdi-alphabet-latin::before{content:"\F132F"}.mdi-alphabet-piqad::before{content:"\F1330"}.mdi-alphabet-tengwar::before{content:"\F1337"}.mdi-alphabetical::before{content:"\F002C"}.mdi-alphabetical-off::before{content:"\F100C"}.mdi-alphabetical-variant::before{content:"\F100D"}.mdi-alphabetical-variant-off::before{content:"\F100E"}.mdi-altimeter::before{content:"\F05D7"}.mdi-ambulance::before{content:"\F002F"}.mdi-ammunition::before{content:"\F0CE8"}.mdi-ampersand::before{content:"\F0A8D"}.mdi-amplifier::before{content:"\F0030"}.mdi-amplifier-off::before{content:"\F11B5"}.mdi-anchor::before{content:"\F0031"}.mdi-android::before{content:"\F0032"}.mdi-android-studio::before{content:"\F0034"}.mdi-angle-acute::before{content:"\F0937"}.mdi-angle-obtuse::before{content:"\F0938"}.mdi-angle-right::before{content:"\F0939"}.mdi-angular::before{content:"\F06B2"}.mdi-angularjs::before{content:"\F06BF"}.mdi-animation::before{content:"\F05D8"}.mdi-animation-outline::before{content:"\F0A8F"}.mdi-animation-play::before{content:"\F093A"}.mdi-animation-play-outline::before{content:"\F0A90"}.mdi-ansible::before{content:"\F109A"}.mdi-antenna::before{content:"\F1119"}.mdi-anvil::before{content:"\F089B"}.mdi-apache-kafka::before{content:"\F100F"}.mdi-api::before{content:"\F109B"}.mdi-api-off::before{content:"\F1257"}.mdi-apple::before{content:"\F0035"}.mdi-apple-finder::before{content:"\F0036"}.mdi-apple-icloud::before{content:"\F0038"}.mdi-apple-ios::before{content:"\F0037"}.mdi-apple-keyboard-caps::before{content:"\F0632"}.mdi-apple-keyboard-command::before{content:"\F0633"}.mdi-apple-keyboard-control::before{content:"\F0634"}.mdi-apple-keyboard-option::before{content:"\F0635"}.mdi-apple-keyboard-shift::before{content:"\F0636"}.mdi-apple-safari::before{content:"\F0039"}.mdi-application::before{content:"\F08C6"}.mdi-application-array::before{content:"\F10F5"}.mdi-application-array-outline::before{content:"\F10F6"}.mdi-application-braces::before{content:"\F10F7"}.mdi-application-braces-outline::before{content:"\F10F8"}.mdi-application-brackets::before{content:"\F0C8B"}.mdi-application-brackets-outline::before{content:"\F0C8C"}.mdi-application-cog::before{content:"\F0675"}.mdi-application-cog-outline::before{content:"\F1577"}.mdi-application-edit::before{content:"\F00AE"}.mdi-application-edit-outline::before{content:"\F0619"}.mdi-application-export::before{content:"\F0DAD"}.mdi-application-import::before{content:"\F0DAE"}.mdi-application-outline::before{content:"\F0614"}.mdi-application-parentheses::before{content:"\F10F9"}.mdi-application-parentheses-outline::before{content:"\F10FA"}.mdi-application-settings::before{content:"\F0B60"}.mdi-application-settings-outline::before{content:"\F1555"}.mdi-application-variable::before{content:"\F10FB"}.mdi-application-variable-outline::before{content:"\F10FC"}.mdi-approximately-equal::before{content:"\F0F9E"}.mdi-approximately-equal-box::before{content:"\F0F9F"}.mdi-apps::before{content:"\F003B"}.mdi-apps-box::before{content:"\F0D46"}.mdi-arch::before{content:"\F08C7"}.mdi-archive::before{content:"\F003C"}.mdi-archive-alert::before{content:"\F14FD"}.mdi-archive-alert-outline::before{content:"\F14FE"}.mdi-archive-arrow-down::before{content:"\F1259"}.mdi-archive-arrow-down-outline::before{content:"\F125A"}.mdi-archive-arrow-up::before{content:"\F125B"}.mdi-archive-arrow-up-outline::before{content:"\F125C"}.mdi-archive-cancel::before{content:"\F174B"}.mdi-archive-cancel-outline::before{content:"\F174C"}.mdi-archive-check::before{content:"\F174D"}.mdi-archive-check-outline::before{content:"\F174E"}.mdi-archive-clock::before{content:"\F174F"}.mdi-archive-clock-outline::before{content:"\F1750"}.mdi-archive-cog::before{content:"\F1751"}.mdi-archive-cog-outline::before{content:"\F1752"}.mdi-archive-edit::before{content:"\F1753"}.mdi-archive-edit-outline::before{content:"\F1754"}.mdi-archive-eye::before{content:"\F1755"}.mdi-archive-eye-outline::before{content:"\F1756"}.mdi-archive-lock::before{content:"\F1757"}.mdi-archive-lock-open::before{content:"\F1758"}.mdi-archive-lock-open-outline::before{content:"\F1759"}.mdi-archive-lock-outline::before{content:"\F175A"}.mdi-archive-marker::before{content:"\F175B"}.mdi-archive-marker-outline::before{content:"\F175C"}.mdi-archive-minus::before{content:"\F175D"}.mdi-archive-minus-outline::before{content:"\F175E"}.mdi-archive-music::before{content:"\F175F"}.mdi-archive-music-outline::before{content:"\F1760"}.mdi-archive-off::before{content:"\F1761"}.mdi-archive-off-outline::before{content:"\F1762"}.mdi-archive-outline::before{content:"\F120E"}.mdi-archive-plus::before{content:"\F1763"}.mdi-archive-plus-outline::before{content:"\F1764"}.mdi-archive-refresh::before{content:"\F1765"}.mdi-archive-refresh-outline::before{content:"\F1766"}.mdi-archive-remove::before{content:"\F1767"}.mdi-archive-remove-outline::before{content:"\F1768"}.mdi-archive-search::before{content:"\F1769"}.mdi-archive-search-outline::before{content:"\F176A"}.mdi-archive-settings::before{content:"\F176B"}.mdi-archive-settings-outline::before{content:"\F176C"}.mdi-archive-star::before{content:"\F176D"}.mdi-archive-star-outline::before{content:"\F176E"}.mdi-archive-sync::before{content:"\F176F"}.mdi-archive-sync-outline::before{content:"\F1770"}.mdi-arm-flex::before{content:"\F0FD7"}.mdi-arm-flex-outline::before{content:"\F0FD6"}.mdi-arrange-bring-forward::before{content:"\F003D"}.mdi-arrange-bring-to-front::before{content:"\F003E"}.mdi-arrange-send-backward::before{content:"\F003F"}.mdi-arrange-send-to-back::before{content:"\F0040"}.mdi-arrow-all::before{content:"\F0041"}.mdi-arrow-bottom-left::before{content:"\F0042"}.mdi-arrow-bottom-left-bold-box::before{content:"\F1964"}.mdi-arrow-bottom-left-bold-box-outline::before{content:"\F1965"}.mdi-arrow-bottom-left-bold-outline::before{content:"\F09B7"}.mdi-arrow-bottom-left-thick::before{content:"\F09B8"}.mdi-arrow-bottom-left-thin::before{content:"\F19B6"}.mdi-arrow-bottom-left-thin-circle-outline::before{content:"\F1596"}.mdi-arrow-bottom-right::before{content:"\F0043"}.mdi-arrow-bottom-right-bold-box::before{content:"\F1966"}.mdi-arrow-bottom-right-bold-box-outline::before{content:"\F1967"}.mdi-arrow-bottom-right-bold-outline::before{content:"\F09B9"}.mdi-arrow-bottom-right-thick::before{content:"\F09BA"}.mdi-arrow-bottom-right-thin::before{content:"\F19B7"}.mdi-arrow-bottom-right-thin-circle-outline::before{content:"\F1595"}.mdi-arrow-collapse::before{content:"\F0615"}.mdi-arrow-collapse-all::before{content:"\F0044"}.mdi-arrow-collapse-down::before{content:"\F0792"}.mdi-arrow-collapse-horizontal::before{content:"\F084C"}.mdi-arrow-collapse-left::before{content:"\F0793"}.mdi-arrow-collapse-right::before{content:"\F0794"}.mdi-arrow-collapse-up::before{content:"\F0795"}.mdi-arrow-collapse-vertical::before{content:"\F084D"}.mdi-arrow-decision::before{content:"\F09BB"}.mdi-arrow-decision-auto::before{content:"\F09BC"}.mdi-arrow-decision-auto-outline::before{content:"\F09BD"}.mdi-arrow-decision-outline::before{content:"\F09BE"}.mdi-arrow-down::before{content:"\F0045"}.mdi-arrow-down-bold::before{content:"\F072E"}.mdi-arrow-down-bold-box::before{content:"\F072F"}.mdi-arrow-down-bold-box-outline::before{content:"\F0730"}.mdi-arrow-down-bold-circle::before{content:"\F0047"}.mdi-arrow-down-bold-circle-outline::before{content:"\F0048"}.mdi-arrow-down-bold-hexagon-outline::before{content:"\F0049"}.mdi-arrow-down-bold-outline::before{content:"\F09BF"}.mdi-arrow-down-box::before{content:"\F06C0"}.mdi-arrow-down-circle::before{content:"\F0CDB"}.mdi-arrow-down-circle-outline::before{content:"\F0CDC"}.mdi-arrow-down-drop-circle::before{content:"\F004A"}.mdi-arrow-down-drop-circle-outline::before{content:"\F004B"}.mdi-arrow-down-left::before{content:"\F17A1"}.mdi-arrow-down-left-bold::before{content:"\F17A2"}.mdi-arrow-down-right::before{content:"\F17A3"}.mdi-arrow-down-right-bold::before{content:"\F17A4"}.mdi-arrow-down-thick::before{content:"\F0046"}.mdi-arrow-down-thin::before{content:"\F19B3"}.mdi-arrow-down-thin-circle-outline::before{content:"\F1599"}.mdi-arrow-expand::before{content:"\F0616"}.mdi-arrow-expand-all::before{content:"\F004C"}.mdi-arrow-expand-down::before{content:"\F0796"}.mdi-arrow-expand-horizontal::before{content:"\F084E"}.mdi-arrow-expand-left::before{content:"\F0797"}.mdi-arrow-expand-right::before{content:"\F0798"}.mdi-arrow-expand-up::before{content:"\F0799"}.mdi-arrow-expand-vertical::before{content:"\F084F"}.mdi-arrow-horizontal-lock::before{content:"\F115B"}.mdi-arrow-left::before{content:"\F004D"}.mdi-arrow-left-bold::before{content:"\F0731"}.mdi-arrow-left-bold-box::before{content:"\F0732"}.mdi-arrow-left-bold-box-outline::before{content:"\F0733"}.mdi-arrow-left-bold-circle::before{content:"\F004F"}.mdi-arrow-left-bold-circle-outline::before{content:"\F0050"}.mdi-arrow-left-bold-hexagon-outline::before{content:"\F0051"}.mdi-arrow-left-bold-outline::before{content:"\F09C0"}.mdi-arrow-left-bottom::before{content:"\F17A5"}.mdi-arrow-left-bottom-bold::before{content:"\F17A6"}.mdi-arrow-left-box::before{content:"\F06C1"}.mdi-arrow-left-circle::before{content:"\F0CDD"}.mdi-arrow-left-circle-outline::before{content:"\F0CDE"}.mdi-arrow-left-drop-circle::before{content:"\F0052"}.mdi-arrow-left-drop-circle-outline::before{content:"\F0053"}.mdi-arrow-left-right::before{content:"\F0E73"}.mdi-arrow-left-right-bold::before{content:"\F0E74"}.mdi-arrow-left-right-bold-outline::before{content:"\F09C1"}.mdi-arrow-left-thick::before{content:"\F004E"}.mdi-arrow-left-thin::before{content:"\F19B1"}.mdi-arrow-left-thin-circle-outline::before{content:"\F159A"}.mdi-arrow-left-top::before{content:"\F17A7"}.mdi-arrow-left-top-bold::before{content:"\F17A8"}.mdi-arrow-oscillating::before{content:"\F1C91"}.mdi-arrow-oscillating-off::before{content:"\F1C92"}.mdi-arrow-projectile::before{content:"\F1840"}.mdi-arrow-projectile-multiple::before{content:"\F183F"}.mdi-arrow-right::before{content:"\F0054"}.mdi-arrow-right-bold::before{content:"\F0734"}.mdi-arrow-right-bold-box::before{content:"\F0735"}.mdi-arrow-right-bold-box-outline::before{content:"\F0736"}.mdi-arrow-right-bold-circle::before{content:"\F0056"}.mdi-arrow-right-bold-circle-outline::before{content:"\F0057"}.mdi-arrow-right-bold-hexagon-outline::before{content:"\F0058"}.mdi-arrow-right-bold-outline::before{content:"\F09C2"}.mdi-arrow-right-bottom::before{content:"\F17A9"}.mdi-arrow-right-bottom-bold::before{content:"\F17AA"}.mdi-arrow-right-box::before{content:"\F06C2"}.mdi-arrow-right-circle::before{content:"\F0CDF"}.mdi-arrow-right-circle-outline::before{content:"\F0CE0"}.mdi-arrow-right-drop-circle::before{content:"\F0059"}.mdi-arrow-right-drop-circle-outline::before{content:"\F005A"}.mdi-arrow-right-thick::before{content:"\F0055"}.mdi-arrow-right-thin::before{content:"\F19B0"}.mdi-arrow-right-thin-circle-outline::before{content:"\F1598"}.mdi-arrow-right-top::before{content:"\F17AB"}.mdi-arrow-right-top-bold::before{content:"\F17AC"}.mdi-arrow-split-horizontal::before{content:"\F093B"}.mdi-arrow-split-vertical::before{content:"\F093C"}.mdi-arrow-top-left::before{content:"\F005B"}.mdi-arrow-top-left-bold-box::before{content:"\F1968"}.mdi-arrow-top-left-bold-box-outline::before{content:"\F1969"}.mdi-arrow-top-left-bold-outline::before{content:"\F09C3"}.mdi-arrow-top-left-bottom-right::before{content:"\F0E75"}.mdi-arrow-top-left-bottom-right-bold::before{content:"\F0E76"}.mdi-arrow-top-left-thick::before{content:"\F09C4"}.mdi-arrow-top-left-thin::before{content:"\F19B5"}.mdi-arrow-top-left-thin-circle-outline::before{content:"\F1593"}.mdi-arrow-top-right::before{content:"\F005C"}.mdi-arrow-top-right-bold-box::before{content:"\F196A"}.mdi-arrow-top-right-bold-box-outline::before{content:"\F196B"}.mdi-arrow-top-right-bold-outline::before{content:"\F09C5"}.mdi-arrow-top-right-bottom-left::before{content:"\F0E77"}.mdi-arrow-top-right-bottom-left-bold::before{content:"\F0E78"}.mdi-arrow-top-right-thick::before{content:"\F09C6"}.mdi-arrow-top-right-thin::before{content:"\F19B4"}.mdi-arrow-top-right-thin-circle-outline::before{content:"\F1594"}.mdi-arrow-u-down-left::before{content:"\F17AD"}.mdi-arrow-u-down-left-bold::before{content:"\F17AE"}.mdi-arrow-u-down-right::before{content:"\F17AF"}.mdi-arrow-u-down-right-bold::before{content:"\F17B0"}.mdi-arrow-u-left-bottom::before{content:"\F17B1"}.mdi-arrow-u-left-bottom-bold::before{content:"\F17B2"}.mdi-arrow-u-left-top::before{content:"\F17B3"}.mdi-arrow-u-left-top-bold::before{content:"\F17B4"}.mdi-arrow-u-right-bottom::before{content:"\F17B5"}.mdi-arrow-u-right-bottom-bold::before{content:"\F17B6"}.mdi-arrow-u-right-top::before{content:"\F17B7"}.mdi-arrow-u-right-top-bold::before{content:"\F17B8"}.mdi-arrow-u-up-left::before{content:"\F17B9"}.mdi-arrow-u-up-left-bold::before{content:"\F17BA"}.mdi-arrow-u-up-right::before{content:"\F17BB"}.mdi-arrow-u-up-right-bold::before{content:"\F17BC"}.mdi-arrow-up::before{content:"\F005D"}.mdi-arrow-up-bold::before{content:"\F0737"}.mdi-arrow-up-bold-box::before{content:"\F0738"}.mdi-arrow-up-bold-box-outline::before{content:"\F0739"}.mdi-arrow-up-bold-circle::before{content:"\F005F"}.mdi-arrow-up-bold-circle-outline::before{content:"\F0060"}.mdi-arrow-up-bold-hexagon-outline::before{content:"\F0061"}.mdi-arrow-up-bold-outline::before{content:"\F09C7"}.mdi-arrow-up-box::before{content:"\F06C3"}.mdi-arrow-up-circle::before{content:"\F0CE1"}.mdi-arrow-up-circle-outline::before{content:"\F0CE2"}.mdi-arrow-up-down::before{content:"\F0E79"}.mdi-arrow-up-down-bold::before{content:"\F0E7A"}.mdi-arrow-up-down-bold-outline::before{content:"\F09C8"}.mdi-arrow-up-drop-circle::before{content:"\F0062"}.mdi-arrow-up-drop-circle-outline::before{content:"\F0063"}.mdi-arrow-up-left::before{content:"\F17BD"}.mdi-arrow-up-left-bold::before{content:"\F17BE"}.mdi-arrow-up-right::before{content:"\F17BF"}.mdi-arrow-up-right-bold::before{content:"\F17C0"}.mdi-arrow-up-thick::before{content:"\F005E"}.mdi-arrow-up-thin::before{content:"\F19B2"}.mdi-arrow-up-thin-circle-outline::before{content:"\F1597"}.mdi-arrow-vertical-lock::before{content:"\F115C"}.mdi-artboard::before{content:"\F1B9A"}.mdi-artstation::before{content:"\F0B5B"}.mdi-aspect-ratio::before{content:"\F0A24"}.mdi-assistant::before{content:"\F0064"}.mdi-asterisk::before{content:"\F06C4"}.mdi-asterisk-circle-outline::before{content:"\F1A27"}.mdi-at::before{content:"\F0065"}.mdi-atlassian::before{content:"\F0804"}.mdi-atm::before{content:"\F0D47"}.mdi-atom::before{content:"\F0768"}.mdi-atom-variant::before{content:"\F0E7B"}.mdi-attachment::before{content:"\F0066"}.mdi-attachment-check::before{content:"\F1AC1"}.mdi-attachment-lock::before{content:"\F19C4"}.mdi-attachment-minus::before{content:"\F1AC2"}.mdi-attachment-off::before{content:"\F1AC3"}.mdi-attachment-plus::before{content:"\F1AC4"}.mdi-attachment-remove::before{content:"\F1AC5"}.mdi-atv::before{content:"\F1B70"}.mdi-audio-input-rca::before{content:"\F186B"}.mdi-audio-input-stereo-minijack::before{content:"\F186C"}.mdi-audio-input-xlr::before{content:"\F186D"}.mdi-audio-video::before{content:"\F093D"}.mdi-audio-video-off::before{content:"\F11B6"}.mdi-augmented-reality::before{content:"\F0850"}.mdi-aurora::before{content:"\F1BB9"}.mdi-auto-download::before{content:"\F137E"}.mdi-auto-fix::before{content:"\F0068"}.mdi-auto-mode::before{content:"\F1C20"}.mdi-auto-upload::before{content:"\F0069"}.mdi-autorenew::before{content:"\F006A"}.mdi-autorenew-off::before{content:"\F19E7"}.mdi-av-timer::before{content:"\F006B"}.mdi-awning::before{content:"\F1B87"}.mdi-awning-outline::before{content:"\F1B88"}.mdi-aws::before{content:"\F0E0F"}.mdi-axe::before{content:"\F08C8"}.mdi-axe-battle::before{content:"\F1842"}.mdi-axis::before{content:"\F0D48"}.mdi-axis-arrow::before{content:"\F0D49"}.mdi-axis-arrow-info::before{content:"\F140E"}.mdi-axis-arrow-lock::before{content:"\F0D4A"}.mdi-axis-lock::before{content:"\F0D4B"}.mdi-axis-x-arrow::before{content:"\F0D4C"}.mdi-axis-x-arrow-lock::before{content:"\F0D4D"}.mdi-axis-x-rotate-clockwise::before{content:"\F0D4E"}.mdi-axis-x-rotate-counterclockwise::before{content:"\F0D4F"}.mdi-axis-x-y-arrow-lock::before{content:"\F0D50"}.mdi-axis-y-arrow::before{content:"\F0D51"}.mdi-axis-y-arrow-lock::before{content:"\F0D52"}.mdi-axis-y-rotate-clockwise::before{content:"\F0D53"}.mdi-axis-y-rotate-counterclockwise::before{content:"\F0D54"}.mdi-axis-z-arrow::before{content:"\F0D55"}.mdi-axis-z-arrow-lock::before{content:"\F0D56"}.mdi-axis-z-rotate-clockwise::before{content:"\F0D57"}.mdi-axis-z-rotate-counterclockwise::before{content:"\F0D58"}.mdi-babel::before{content:"\F0A25"}.mdi-baby::before{content:"\F006C"}.mdi-baby-bottle::before{content:"\F0F39"}.mdi-baby-bottle-outline::before{content:"\F0F3A"}.mdi-baby-buggy::before{content:"\F13E0"}.mdi-baby-buggy-off::before{content:"\F1AF3"}.mdi-baby-carriage::before{content:"\F068F"}.mdi-baby-carriage-off::before{content:"\F0FA0"}.mdi-baby-face::before{content:"\F0E7C"}.mdi-baby-face-outline::before{content:"\F0E7D"}.mdi-backburger::before{content:"\F006D"}.mdi-backspace::before{content:"\F006E"}.mdi-backspace-outline::before{content:"\F0B5C"}.mdi-backspace-reverse::before{content:"\F0E7E"}.mdi-backspace-reverse-outline::before{content:"\F0E7F"}.mdi-backup-restore::before{content:"\F006F"}.mdi-bacteria::before{content:"\F0ED5"}.mdi-bacteria-outline::before{content:"\F0ED6"}.mdi-badge-account::before{content:"\F0DA7"}.mdi-badge-account-alert::before{content:"\F0DA8"}.mdi-badge-account-alert-outline::before{content:"\F0DA9"}.mdi-badge-account-horizontal::before{content:"\F0E0D"}.mdi-badge-account-horizontal-outline::before{content:"\F0E0E"}.mdi-badge-account-outline::before{content:"\F0DAA"}.mdi-badminton::before{content:"\F0851"}.mdi-bag-carry-on::before{content:"\F0F3B"}.mdi-bag-carry-on-check::before{content:"\F0D65"}.mdi-bag-carry-on-off::before{content:"\F0F3C"}.mdi-bag-checked::before{content:"\F0F3D"}.mdi-bag-personal::before{content:"\F0E10"}.mdi-bag-personal-off::before{content:"\F0E11"}.mdi-bag-personal-off-outline::before{content:"\F0E12"}.mdi-bag-personal-outline::before{content:"\F0E13"}.mdi-bag-personal-plus::before{content:"\F1CA4"}.mdi-bag-personal-plus-outline::before{content:"\F1CA5"}.mdi-bag-personal-tag::before{content:"\F1B0C"}.mdi-bag-personal-tag-outline::before{content:"\F1B0D"}.mdi-bag-suitcase::before{content:"\F158B"}.mdi-bag-suitcase-off::before{content:"\F158D"}.mdi-bag-suitcase-off-outline::before{content:"\F158E"}.mdi-bag-suitcase-outline::before{content:"\F158C"}.mdi-baguette::before{content:"\F0F3E"}.mdi-balcony::before{content:"\F1817"}.mdi-balloon::before{content:"\F0A26"}.mdi-ballot::before{content:"\F09C9"}.mdi-ballot-outline::before{content:"\F09CA"}.mdi-ballot-recount::before{content:"\F0C39"}.mdi-ballot-recount-outline::before{content:"\F0C3A"}.mdi-bandage::before{content:"\F0DAF"}.mdi-bank::before{content:"\F0070"}.mdi-bank-check::before{content:"\F1655"}.mdi-bank-circle::before{content:"\F1C03"}.mdi-bank-circle-outline::before{content:"\F1C04"}.mdi-bank-minus::before{content:"\F0DB0"}.mdi-bank-off::before{content:"\F1656"}.mdi-bank-off-outline::before{content:"\F1657"}.mdi-bank-outline::before{content:"\F0E80"}.mdi-bank-plus::before{content:"\F0DB1"}.mdi-bank-remove::before{content:"\F0DB2"}.mdi-bank-transfer::before{content:"\F0A27"}.mdi-bank-transfer-in::before{content:"\F0A28"}.mdi-bank-transfer-out::before{content:"\F0A29"}.mdi-barcode::before{content:"\F0071"}.mdi-barcode-off::before{content:"\F1236"}.mdi-barcode-scan::before{content:"\F0072"}.mdi-barley::before{content:"\F0073"}.mdi-barley-off::before{content:"\F0B5D"}.mdi-barn::before{content:"\F0B5E"}.mdi-barrel::before{content:"\F0074"}.mdi-barrel-outline::before{content:"\F1A28"}.mdi-baseball::before{content:"\F0852"}.mdi-baseball-bat::before{content:"\F0853"}.mdi-baseball-diamond::before{content:"\F15EC"}.mdi-baseball-diamond-outline::before{content:"\F15ED"}.mdi-baseball-outline::before{content:"\F1C5A"}.mdi-bash::before{content:"\F1183"}.mdi-basket::before{content:"\F0076"}.mdi-basket-check::before{content:"\F18E5"}.mdi-basket-check-outline::before{content:"\F18E6"}.mdi-basket-fill::before{content:"\F0077"}.mdi-basket-minus::before{content:"\F1523"}.mdi-basket-minus-outline::before{content:"\F1524"}.mdi-basket-off::before{content:"\F1525"}.mdi-basket-off-outline::before{content:"\F1526"}.mdi-basket-outline::before{content:"\F1181"}.mdi-basket-plus::before{content:"\F1527"}.mdi-basket-plus-outline::before{content:"\F1528"}.mdi-basket-remove::before{content:"\F1529"}.mdi-basket-remove-outline::before{content:"\F152A"}.mdi-basket-unfill::before{content:"\F0078"}.mdi-basketball::before{content:"\F0806"}.mdi-basketball-hoop::before{content:"\F0C3B"}.mdi-basketball-hoop-outline::before{content:"\F0C3C"}.mdi-bat::before{content:"\F0B5F"}.mdi-bathtub::before{content:"\F1818"}.mdi-bathtub-outline::before{content:"\F1819"}.mdi-battery::before{content:"\F0079"}.mdi-battery-10::before{content:"\F007A"}.mdi-battery-10-bluetooth::before{content:"\F093E"}.mdi-battery-20::before{content:"\F007B"}.mdi-battery-20-bluetooth::before{content:"\F093F"}.mdi-battery-30::before{content:"\F007C"}.mdi-battery-30-bluetooth::before{content:"\F0940"}.mdi-battery-40::before{content:"\F007D"}.mdi-battery-40-bluetooth::before{content:"\F0941"}.mdi-battery-50::before{content:"\F007E"}.mdi-battery-50-bluetooth::before{content:"\F0942"}.mdi-battery-60::before{content:"\F007F"}.mdi-battery-60-bluetooth::before{content:"\F0943"}.mdi-battery-70::before{content:"\F0080"}.mdi-battery-70-bluetooth::before{content:"\F0944"}.mdi-battery-80::before{content:"\F0081"}.mdi-battery-80-bluetooth::before{content:"\F0945"}.mdi-battery-90::before{content:"\F0082"}.mdi-battery-90-bluetooth::before{content:"\F0946"}.mdi-battery-alert::before{content:"\F0083"}.mdi-battery-alert-bluetooth::before{content:"\F0947"}.mdi-battery-alert-variant::before{content:"\F10CC"}.mdi-battery-alert-variant-outline::before{content:"\F10CD"}.mdi-battery-arrow-down::before{content:"\F17DE"}.mdi-battery-arrow-down-outline::before{content:"\F17DF"}.mdi-battery-arrow-up::before{content:"\F17E0"}.mdi-battery-arrow-up-outline::before{content:"\F17E1"}.mdi-battery-bluetooth::before{content:"\F0948"}.mdi-battery-bluetooth-variant::before{content:"\F0949"}.mdi-battery-charging::before{content:"\F0084"}.mdi-battery-charging-10::before{content:"\F089C"}.mdi-battery-charging-100::before{content:"\F0085"}.mdi-battery-charging-20::before{content:"\F0086"}.mdi-battery-charging-30::before{content:"\F0087"}.mdi-battery-charging-40::before{content:"\F0088"}.mdi-battery-charging-50::before{content:"\F089D"}.mdi-battery-charging-60::before{content:"\F0089"}.mdi-battery-charging-70::before{content:"\F089E"}.mdi-battery-charging-80::before{content:"\F008A"}.mdi-battery-charging-90::before{content:"\F008B"}.mdi-battery-charging-high::before{content:"\F12A6"}.mdi-battery-charging-low::before{content:"\F12A4"}.mdi-battery-charging-medium::before{content:"\F12A5"}.mdi-battery-charging-outline::before{content:"\F089F"}.mdi-battery-charging-wireless::before{content:"\F0807"}.mdi-battery-charging-wireless-10::before{content:"\F0808"}.mdi-battery-charging-wireless-20::before{content:"\F0809"}.mdi-battery-charging-wireless-30::before{content:"\F080A"}.mdi-battery-charging-wireless-40::before{content:"\F080B"}.mdi-battery-charging-wireless-50::before{content:"\F080C"}.mdi-battery-charging-wireless-60::before{content:"\F080D"}.mdi-battery-charging-wireless-70::before{content:"\F080E"}.mdi-battery-charging-wireless-80::before{content:"\F080F"}.mdi-battery-charging-wireless-90::before{content:"\F0810"}.mdi-battery-charging-wireless-alert::before{content:"\F0811"}.mdi-battery-charging-wireless-outline::before{content:"\F0812"}.mdi-battery-check::before{content:"\F17E2"}.mdi-battery-check-outline::before{content:"\F17E3"}.mdi-battery-clock::before{content:"\F19E5"}.mdi-battery-clock-outline::before{content:"\F19E6"}.mdi-battery-heart::before{content:"\F120F"}.mdi-battery-heart-outline::before{content:"\F1210"}.mdi-battery-heart-variant::before{content:"\F1211"}.mdi-battery-high::before{content:"\F12A3"}.mdi-battery-lock::before{content:"\F179C"}.mdi-battery-lock-open::before{content:"\F179D"}.mdi-battery-low::before{content:"\F12A1"}.mdi-battery-medium::before{content:"\F12A2"}.mdi-battery-minus::before{content:"\F17E4"}.mdi-battery-minus-outline::before{content:"\F17E5"}.mdi-battery-minus-variant::before{content:"\F008C"}.mdi-battery-negative::before{content:"\F008D"}.mdi-battery-off::before{content:"\F125D"}.mdi-battery-off-outline::before{content:"\F125E"}.mdi-battery-outline::before{content:"\F008E"}.mdi-battery-plus::before{content:"\F17E6"}.mdi-battery-plus-outline::before{content:"\F17E7"}.mdi-battery-plus-variant::before{content:"\F008F"}.mdi-battery-positive::before{content:"\F0090"}.mdi-battery-remove::before{content:"\F17E8"}.mdi-battery-remove-outline::before{content:"\F17E9"}.mdi-battery-sync::before{content:"\F1834"}.mdi-battery-sync-outline::before{content:"\F1835"}.mdi-battery-unknown::before{content:"\F0091"}.mdi-battery-unknown-bluetooth::before{content:"\F094A"}.mdi-beach::before{content:"\F0092"}.mdi-beaker::before{content:"\F0CEA"}.mdi-beaker-alert::before{content:"\F1229"}.mdi-beaker-alert-outline::before{content:"\F122A"}.mdi-beaker-check::before{content:"\F122B"}.mdi-beaker-check-outline::before{content:"\F122C"}.mdi-beaker-minus::before{content:"\F122D"}.mdi-beaker-minus-outline::before{content:"\F122E"}.mdi-beaker-outline::before{content:"\F0690"}.mdi-beaker-plus::before{content:"\F122F"}.mdi-beaker-plus-outline::before{content:"\F1230"}.mdi-beaker-question::before{content:"\F1231"}.mdi-beaker-question-outline::before{content:"\F1232"}.mdi-beaker-remove::before{content:"\F1233"}.mdi-beaker-remove-outline::before{content:"\F1234"}.mdi-bed::before{content:"\F02E3"}.mdi-bed-clock::before{content:"\F1B94"}.mdi-bed-double::before{content:"\F0FD4"}.mdi-bed-double-outline::before{content:"\F0FD3"}.mdi-bed-empty::before{content:"\F08A0"}.mdi-bed-king::before{content:"\F0FD2"}.mdi-bed-king-outline::before{content:"\F0FD1"}.mdi-bed-outline::before{content:"\F0099"}.mdi-bed-queen::before{content:"\F0FD0"}.mdi-bed-queen-outline::before{content:"\F0FDB"}.mdi-bed-single::before{content:"\F106D"}.mdi-bed-single-outline::before{content:"\F106E"}.mdi-bee::before{content:"\F0FA1"}.mdi-bee-flower::before{content:"\F0FA2"}.mdi-beehive-off-outline::before{content:"\F13ED"}.mdi-beehive-outline::before{content:"\F10CE"}.mdi-beekeeper::before{content:"\F14E2"}.mdi-beer::before{content:"\F0098"}.mdi-beer-outline::before{content:"\F130C"}.mdi-bell::before{content:"\F009A"}.mdi-bell-alert::before{content:"\F0D59"}.mdi-bell-alert-outline::before{content:"\F0E81"}.mdi-bell-badge::before{content:"\F116B"}.mdi-bell-badge-outline::before{content:"\F0178"}.mdi-bell-cancel::before{content:"\F13E7"}.mdi-bell-cancel-outline::before{content:"\F13E8"}.mdi-bell-check::before{content:"\F11E5"}.mdi-bell-check-outline::before{content:"\F11E6"}.mdi-bell-circle::before{content:"\F0D5A"}.mdi-bell-circle-outline::before{content:"\F0D5B"}.mdi-bell-cog::before{content:"\F1A29"}.mdi-bell-cog-outline::before{content:"\F1A2A"}.mdi-bell-minus::before{content:"\F13E9"}.mdi-bell-minus-outline::before{content:"\F13EA"}.mdi-bell-off::before{content:"\F009B"}.mdi-bell-off-outline::before{content:"\F0A91"}.mdi-bell-outline::before{content:"\F009C"}.mdi-bell-plus::before{content:"\F009D"}.mdi-bell-plus-outline::before{content:"\F0A92"}.mdi-bell-remove::before{content:"\F13EB"}.mdi-bell-remove-outline::before{content:"\F13EC"}.mdi-bell-ring::before{content:"\F009E"}.mdi-bell-ring-outline::before{content:"\F009F"}.mdi-bell-sleep::before{content:"\F00A0"}.mdi-bell-sleep-outline::before{content:"\F0A93"}.mdi-bench::before{content:"\F1C21"}.mdi-bench-back::before{content:"\F1C22"}.mdi-beta::before{content:"\F00A1"}.mdi-betamax::before{content:"\F09CB"}.mdi-biathlon::before{content:"\F0E14"}.mdi-bicycle::before{content:"\F109C"}.mdi-bicycle-basket::before{content:"\F1235"}.mdi-bicycle-cargo::before{content:"\F189C"}.mdi-bicycle-electric::before{content:"\F15B4"}.mdi-bicycle-penny-farthing::before{content:"\F15E9"}.mdi-bike::before{content:"\F00A3"}.mdi-bike-fast::before{content:"\F111F"}.mdi-bike-pedal::before{content:"\F1C23"}.mdi-bike-pedal-clipless::before{content:"\F1C24"}.mdi-bike-pedal-mountain::before{content:"\F1C25"}.mdi-billboard::before{content:"\F1010"}.mdi-billiards::before{content:"\F0B61"}.mdi-billiards-rack::before{content:"\F0B62"}.mdi-binoculars::before{content:"\F00A5"}.mdi-bio::before{content:"\F00A6"}.mdi-biohazard::before{content:"\F00A7"}.mdi-bird::before{content:"\F15C6"}.mdi-bitbucket::before{content:"\F00A8"}.mdi-bitcoin::before{content:"\F0813"}.mdi-black-mesa::before{content:"\F00A9"}.mdi-blender::before{content:"\F0CEB"}.mdi-blender-outline::before{content:"\F181A"}.mdi-blender-software::before{content:"\F00AB"}.mdi-blinds::before{content:"\F00AC"}.mdi-blinds-horizontal::before{content:"\F1A2B"}.mdi-blinds-horizontal-closed::before{content:"\F1A2C"}.mdi-blinds-open::before{content:"\F1011"}.mdi-blinds-vertical::before{content:"\F1A2D"}.mdi-blinds-vertical-closed::before{content:"\F1A2E"}.mdi-block-helper::before{content:"\F00AD"}.mdi-blood-bag::before{content:"\F0CEC"}.mdi-bluetooth::before{content:"\F00AF"}.mdi-bluetooth-audio::before{content:"\F00B0"}.mdi-bluetooth-connect::before{content:"\F00B1"}.mdi-bluetooth-off::before{content:"\F00B2"}.mdi-bluetooth-settings::before{content:"\F00B3"}.mdi-bluetooth-transfer::before{content:"\F00B4"}.mdi-blur::before{content:"\F00B5"}.mdi-blur-linear::before{content:"\F00B6"}.mdi-blur-off::before{content:"\F00B7"}.mdi-blur-radial::before{content:"\F00B8"}.mdi-bolt::before{content:"\F0DB3"}.mdi-bomb::before{content:"\F0691"}.mdi-bomb-off::before{content:"\F06C5"}.mdi-bone::before{content:"\F00B9"}.mdi-bone-off::before{content:"\F19E0"}.mdi-book::before{content:"\F00BA"}.mdi-book-account::before{content:"\F13AD"}.mdi-book-account-outline::before{content:"\F13AE"}.mdi-book-alert::before{content:"\F167C"}.mdi-book-alert-outline::before{content:"\F167D"}.mdi-book-alphabet::before{content:"\F061D"}.mdi-book-arrow-down::before{content:"\F167E"}.mdi-book-arrow-down-outline::before{content:"\F167F"}.mdi-book-arrow-left::before{content:"\F1680"}.mdi-book-arrow-left-outline::before{content:"\F1681"}.mdi-book-arrow-right::before{content:"\F1682"}.mdi-book-arrow-right-outline::before{content:"\F1683"}.mdi-book-arrow-up::before{content:"\F1684"}.mdi-book-arrow-up-outline::before{content:"\F1685"}.mdi-book-cancel::before{content:"\F1686"}.mdi-book-cancel-outline::before{content:"\F1687"}.mdi-book-check::before{content:"\F14F3"}.mdi-book-check-outline::before{content:"\F14F4"}.mdi-book-clock::before{content:"\F1688"}.mdi-book-clock-outline::before{content:"\F1689"}.mdi-book-cog::before{content:"\F168A"}.mdi-book-cog-outline::before{content:"\F168B"}.mdi-book-cross::before{content:"\F00A2"}.mdi-book-edit::before{content:"\F168C"}.mdi-book-edit-outline::before{content:"\F168D"}.mdi-book-education::before{content:"\F16C9"}.mdi-book-education-outline::before{content:"\F16CA"}.mdi-book-heart::before{content:"\F1A1D"}.mdi-book-heart-outline::before{content:"\F1A1E"}.mdi-book-information-variant::before{content:"\F106F"}.mdi-book-lock::before{content:"\F079A"}.mdi-book-lock-open::before{content:"\F079B"}.mdi-book-lock-open-outline::before{content:"\F168E"}.mdi-book-lock-outline::before{content:"\F168F"}.mdi-book-marker::before{content:"\F1690"}.mdi-book-marker-outline::before{content:"\F1691"}.mdi-book-minus::before{content:"\F05D9"}.mdi-book-minus-multiple::before{content:"\F0A94"}.mdi-book-minus-multiple-outline::before{content:"\F090B"}.mdi-book-minus-outline::before{content:"\F1692"}.mdi-book-multiple::before{content:"\F00BB"}.mdi-book-multiple-outline::before{content:"\F0436"}.mdi-book-music::before{content:"\F0067"}.mdi-book-music-outline::before{content:"\F1693"}.mdi-book-off::before{content:"\F1694"}.mdi-book-off-outline::before{content:"\F1695"}.mdi-book-open::before{content:"\F00BD"}.mdi-book-open-blank-variant::before{content:"\F00BE"}.mdi-book-open-blank-variant-outline::before{content:"\F1CCB"}.mdi-book-open-outline::before{content:"\F0B63"}.mdi-book-open-page-variant::before{content:"\F05DA"}.mdi-book-open-page-variant-outline::before{content:"\F15D6"}.mdi-book-open-variant::before{content:"\F14F7"}.mdi-book-open-variant-outline::before{content:"\F1CCC"}.mdi-book-outline::before{content:"\F0B64"}.mdi-book-play::before{content:"\F0E82"}.mdi-book-play-outline::before{content:"\F0E83"}.mdi-book-plus::before{content:"\F05DB"}.mdi-book-plus-multiple::before{content:"\F0A95"}.mdi-book-plus-multiple-outline::before{content:"\F0ADE"}.mdi-book-plus-outline::before{content:"\F1696"}.mdi-book-refresh::before{content:"\F1697"}.mdi-book-refresh-outline::before{content:"\F1698"}.mdi-book-remove::before{content:"\F0A97"}.mdi-book-remove-multiple::before{content:"\F0A96"}.mdi-book-remove-multiple-outline::before{content:"\F04CA"}.mdi-book-remove-outline::before{content:"\F1699"}.mdi-book-search::before{content:"\F0E84"}.mdi-book-search-outline::before{content:"\F0E85"}.mdi-book-settings::before{content:"\F169A"}.mdi-book-settings-outline::before{content:"\F169B"}.mdi-book-sync::before{content:"\F169C"}.mdi-book-sync-outline::before{content:"\F16C8"}.mdi-book-variant::before{content:"\F00BF"}.mdi-bookmark::before{content:"\F00C0"}.mdi-bookmark-box::before{content:"\F1B75"}.mdi-bookmark-box-multiple::before{content:"\F196C"}.mdi-bookmark-box-multiple-outline::before{content:"\F196D"}.mdi-bookmark-box-outline::before{content:"\F1B76"}.mdi-bookmark-check::before{content:"\F00C1"}.mdi-bookmark-check-outline::before{content:"\F137B"}.mdi-bookmark-minus::before{content:"\F09CC"}.mdi-bookmark-minus-outline::before{content:"\F09CD"}.mdi-bookmark-multiple::before{content:"\F0E15"}.mdi-bookmark-multiple-outline::before{content:"\F0E16"}.mdi-bookmark-music::before{content:"\F00C2"}.mdi-bookmark-music-outline::before{content:"\F1379"}.mdi-bookmark-off::before{content:"\F09CE"}.mdi-bookmark-off-outline::before{content:"\F09CF"}.mdi-bookmark-outline::before{content:"\F00C3"}.mdi-bookmark-plus::before{content:"\F00C5"}.mdi-bookmark-plus-outline::before{content:"\F00C4"}.mdi-bookmark-remove::before{content:"\F00C6"}.mdi-bookmark-remove-outline::before{content:"\F137A"}.mdi-bookshelf::before{content:"\F125F"}.mdi-boom-gate::before{content:"\F0E86"}.mdi-boom-gate-alert::before{content:"\F0E87"}.mdi-boom-gate-alert-outline::before{content:"\F0E88"}.mdi-boom-gate-arrow-down::before{content:"\F0E89"}.mdi-boom-gate-arrow-down-outline::before{content:"\F0E8A"}.mdi-boom-gate-arrow-up::before{content:"\F0E8C"}.mdi-boom-gate-arrow-up-outline::before{content:"\F0E8D"}.mdi-boom-gate-outline::before{content:"\F0E8B"}.mdi-boom-gate-up::before{content:"\F17F9"}.mdi-boom-gate-up-outline::before{content:"\F17FA"}.mdi-boombox::before{content:"\F05DC"}.mdi-boomerang::before{content:"\F10CF"}.mdi-bootstrap::before{content:"\F06C6"}.mdi-border-all::before{content:"\F00C7"}.mdi-border-all-variant::before{content:"\F08A1"}.mdi-border-bottom::before{content:"\F00C8"}.mdi-border-bottom-variant::before{content:"\F08A2"}.mdi-border-color::before{content:"\F00C9"}.mdi-border-horizontal::before{content:"\F00CA"}.mdi-border-inside::before{content:"\F00CB"}.mdi-border-left::before{content:"\F00CC"}.mdi-border-left-variant::before{content:"\F08A3"}.mdi-border-none::before{content:"\F00CD"}.mdi-border-none-variant::before{content:"\F08A4"}.mdi-border-outside::before{content:"\F00CE"}.mdi-border-radius::before{content:"\F1AF4"}.mdi-border-right::before{content:"\F00CF"}.mdi-border-right-variant::before{content:"\F08A5"}.mdi-border-style::before{content:"\F00D0"}.mdi-border-top::before{content:"\F00D1"}.mdi-border-top-variant::before{content:"\F08A6"}.mdi-border-vertical::before{content:"\F00D2"}.mdi-bottle-soda::before{content:"\F1070"}.mdi-bottle-soda-classic::before{content:"\F1071"}.mdi-bottle-soda-classic-outline::before{content:"\F1363"}.mdi-bottle-soda-outline::before{content:"\F1072"}.mdi-bottle-tonic::before{content:"\F112E"}.mdi-bottle-tonic-outline::before{content:"\F112F"}.mdi-bottle-tonic-plus::before{content:"\F1130"}.mdi-bottle-tonic-plus-outline::before{content:"\F1131"}.mdi-bottle-tonic-skull::before{content:"\F1132"}.mdi-bottle-tonic-skull-outline::before{content:"\F1133"}.mdi-bottle-wine::before{content:"\F0854"}.mdi-bottle-wine-outline::before{content:"\F1310"}.mdi-bow-arrow::before{content:"\F1841"}.mdi-bow-tie::before{content:"\F0678"}.mdi-bowl::before{content:"\F028E"}.mdi-bowl-mix::before{content:"\F0617"}.mdi-bowl-mix-outline::before{content:"\F02E4"}.mdi-bowl-outline::before{content:"\F02A9"}.mdi-bowling::before{content:"\F00D3"}.mdi-box::before{content:"\F00D4"}.mdi-box-cutter::before{content:"\F00D5"}.mdi-box-cutter-off::before{content:"\F0B4A"}.mdi-box-shadow::before{content:"\F0637"}.mdi-boxing-glove::before{content:"\F0B65"}.mdi-braille::before{content:"\F09D0"}.mdi-brain::before{content:"\F09D1"}.mdi-bread-slice::before{content:"\F0CEE"}.mdi-bread-slice-outline::before{content:"\F0CEF"}.mdi-bridge::before{content:"\F0618"}.mdi-briefcase::before{content:"\F00D6"}.mdi-briefcase-account::before{content:"\F0CF0"}.mdi-briefcase-account-outline::before{content:"\F0CF1"}.mdi-briefcase-arrow-left-right::before{content:"\F1A8D"}.mdi-briefcase-arrow-left-right-outline::before{content:"\F1A8E"}.mdi-briefcase-arrow-up-down::before{content:"\F1A8F"}.mdi-briefcase-arrow-up-down-outline::before{content:"\F1A90"}.mdi-briefcase-check::before{content:"\F00D7"}.mdi-briefcase-check-outline::before{content:"\F131E"}.mdi-briefcase-clock::before{content:"\F10D0"}.mdi-briefcase-clock-outline::before{content:"\F10D1"}.mdi-briefcase-download::before{content:"\F00D8"}.mdi-briefcase-download-outline::before{content:"\F0C3D"}.mdi-briefcase-edit::before{content:"\F0A98"}.mdi-briefcase-edit-outline::before{content:"\F0C3E"}.mdi-briefcase-eye::before{content:"\F17D9"}.mdi-briefcase-eye-outline::before{content:"\F17DA"}.mdi-briefcase-minus::before{content:"\F0A2A"}.mdi-briefcase-minus-outline::before{content:"\F0C3F"}.mdi-briefcase-off::before{content:"\F1658"}.mdi-briefcase-off-outline::before{content:"\F1659"}.mdi-briefcase-outline::before{content:"\F0814"}.mdi-briefcase-plus::before{content:"\F0A2B"}.mdi-briefcase-plus-outline::before{content:"\F0C40"}.mdi-briefcase-remove::before{content:"\F0A2C"}.mdi-briefcase-remove-outline::before{content:"\F0C41"}.mdi-briefcase-search::before{content:"\F0A2D"}.mdi-briefcase-search-outline::before{content:"\F0C42"}.mdi-briefcase-upload::before{content:"\F00D9"}.mdi-briefcase-upload-outline::before{content:"\F0C43"}.mdi-briefcase-variant::before{content:"\F1494"}.mdi-briefcase-variant-off::before{content:"\F165A"}.mdi-briefcase-variant-off-outline::before{content:"\F165B"}.mdi-briefcase-variant-outline::before{content:"\F1495"}.mdi-brightness-1::before{content:"\F00DA"}.mdi-brightness-2::before{content:"\F00DB"}.mdi-brightness-3::before{content:"\F00DC"}.mdi-brightness-4::before{content:"\F00DD"}.mdi-brightness-5::before{content:"\F00DE"}.mdi-brightness-6::before{content:"\F00DF"}.mdi-brightness-7::before{content:"\F00E0"}.mdi-brightness-auto::before{content:"\F00E1"}.mdi-brightness-percent::before{content:"\F0CF2"}.mdi-broadcast::before{content:"\F1720"}.mdi-broadcast-off::before{content:"\F1721"}.mdi-broom::before{content:"\F00E2"}.mdi-brush::before{content:"\F00E3"}.mdi-brush-off::before{content:"\F1771"}.mdi-brush-outline::before{content:"\F1A0D"}.mdi-brush-variant::before{content:"\F1813"}.mdi-bucket::before{content:"\F1415"}.mdi-bucket-outline::before{content:"\F1416"}.mdi-buffet::before{content:"\F0578"}.mdi-bug::before{content:"\F00E4"}.mdi-bug-check::before{content:"\F0A2E"}.mdi-bug-check-outline::before{content:"\F0A2F"}.mdi-bug-outline::before{content:"\F0A30"}.mdi-bug-pause::before{content:"\F1AF5"}.mdi-bug-pause-outline::before{content:"\F1AF6"}.mdi-bug-play::before{content:"\F1AF7"}.mdi-bug-play-outline::before{content:"\F1AF8"}.mdi-bug-stop::before{content:"\F1AF9"}.mdi-bug-stop-outline::before{content:"\F1AFA"}.mdi-bugle::before{content:"\F0DB4"}.mdi-bulkhead-light::before{content:"\F1A2F"}.mdi-bulldozer::before{content:"\F0B22"}.mdi-bullet::before{content:"\F0CF3"}.mdi-bulletin-board::before{content:"\F00E5"}.mdi-bullhorn::before{content:"\F00E6"}.mdi-bullhorn-outline::before{content:"\F0B23"}.mdi-bullhorn-variant::before{content:"\F196E"}.mdi-bullhorn-variant-outline::before{content:"\F196F"}.mdi-bullseye::before{content:"\F05DD"}.mdi-bullseye-arrow::before{content:"\F08C9"}.mdi-bulma::before{content:"\F12E7"}.mdi-bunk-bed::before{content:"\F1302"}.mdi-bunk-bed-outline::before{content:"\F0097"}.mdi-bus::before{content:"\F00E7"}.mdi-bus-alert::before{content:"\F0A99"}.mdi-bus-articulated-end::before{content:"\F079C"}.mdi-bus-articulated-front::before{content:"\F079D"}.mdi-bus-clock::before{content:"\F08CA"}.mdi-bus-double-decker::before{content:"\F079E"}.mdi-bus-electric::before{content:"\F191D"}.mdi-bus-marker::before{content:"\F1212"}.mdi-bus-multiple::before{content:"\F0F3F"}.mdi-bus-school::before{content:"\F079F"}.mdi-bus-side::before{content:"\F07A0"}.mdi-bus-sign::before{content:"\F1CC1"}.mdi-bus-stop::before{content:"\F1012"}.mdi-bus-stop-covered::before{content:"\F1013"}.mdi-bus-stop-uncovered::before{content:"\F1014"}.mdi-bus-wrench::before{content:"\F1CC2"}.mdi-butterfly::before{content:"\F1589"}.mdi-butterfly-outline::before{content:"\F158A"}.mdi-button-cursor::before{content:"\F1B4F"}.mdi-button-pointer::before{content:"\F1B50"}.mdi-cabin-a-frame::before{content:"\F188C"}.mdi-cable-data::before{content:"\F1394"}.mdi-cached::before{content:"\F00E8"}.mdi-cactus::before{content:"\F0DB5"}.mdi-cake::before{content:"\F00E9"}.mdi-cake-layered::before{content:"\F00EA"}.mdi-cake-variant::before{content:"\F00EB"}.mdi-cake-variant-outline::before{content:"\F17F0"}.mdi-calculator::before{content:"\F00EC"}.mdi-calculator-variant::before{content:"\F0A9A"}.mdi-calculator-variant-outline::before{content:"\F15A6"}.mdi-calendar::before{content:"\F00ED"}.mdi-calendar-account::before{content:"\F0ED7"}.mdi-calendar-account-outline::before{content:"\F0ED8"}.mdi-calendar-alert::before{content:"\F0A31"}.mdi-calendar-alert-outline::before{content:"\F1B62"}.mdi-calendar-arrow-left::before{content:"\F1134"}.mdi-calendar-arrow-right::before{content:"\F1135"}.mdi-calendar-badge::before{content:"\F1B9D"}.mdi-calendar-badge-outline::before{content:"\F1B9E"}.mdi-calendar-blank::before{content:"\F00EE"}.mdi-calendar-blank-multiple::before{content:"\F1073"}.mdi-calendar-blank-outline::before{content:"\F0B66"}.mdi-calendar-check::before{content:"\F00EF"}.mdi-calendar-check-outline::before{content:"\F0C44"}.mdi-calendar-clock::before{content:"\F00F0"}.mdi-calendar-clock-outline::before{content:"\F16E1"}.mdi-calendar-collapse-horizontal::before{content:"\F189D"}.mdi-calendar-collapse-horizontal-outline::before{content:"\F1B63"}.mdi-calendar-cursor::before{content:"\F157B"}.mdi-calendar-cursor-outline::before{content:"\F1B64"}.mdi-calendar-edit::before{content:"\F08A7"}.mdi-calendar-edit-outline::before{content:"\F1B65"}.mdi-calendar-end::before{content:"\F166C"}.mdi-calendar-end-outline::before{content:"\F1B66"}.mdi-calendar-expand-horizontal::before{content:"\F189E"}.mdi-calendar-expand-horizontal-outline::before{content:"\F1B67"}.mdi-calendar-export::before{content:"\F0B24"}.mdi-calendar-export-outline::before{content:"\F1B68"}.mdi-calendar-filter::before{content:"\F1A32"}.mdi-calendar-filter-outline::before{content:"\F1A33"}.mdi-calendar-heart::before{content:"\F09D2"}.mdi-calendar-heart-outline::before{content:"\F1B69"}.mdi-calendar-import::before{content:"\F0B25"}.mdi-calendar-import-outline::before{content:"\F1B6A"}.mdi-calendar-lock::before{content:"\F1641"}.mdi-calendar-lock-open::before{content:"\F1B5B"}.mdi-calendar-lock-open-outline::before{content:"\F1B5C"}.mdi-calendar-lock-outline::before{content:"\F1642"}.mdi-calendar-minus::before{content:"\F0D5C"}.mdi-calendar-minus-outline::before{content:"\F1B6B"}.mdi-calendar-month::before{content:"\F0E17"}.mdi-calendar-month-outline::before{content:"\F0E18"}.mdi-calendar-multiple::before{content:"\F00F1"}.mdi-calendar-multiple-check::before{content:"\F00F2"}.mdi-calendar-multiselect::before{content:"\F0A32"}.mdi-calendar-multiselect-outline::before{content:"\F1B55"}.mdi-calendar-outline::before{content:"\F0B67"}.mdi-calendar-plus::before{content:"\F00F3"}.mdi-calendar-plus-outline::before{content:"\F1B6C"}.mdi-calendar-question::before{content:"\F0692"}.mdi-calendar-question-outline::before{content:"\F1B6D"}.mdi-calendar-range::before{content:"\F0679"}.mdi-calendar-range-outline::before{content:"\F0B68"}.mdi-calendar-refresh::before{content:"\F01E1"}.mdi-calendar-refresh-outline::before{content:"\F0203"}.mdi-calendar-remove::before{content:"\F00F4"}.mdi-calendar-remove-outline::before{content:"\F0C45"}.mdi-calendar-search::before{content:"\F094C"}.mdi-calendar-search-outline::before{content:"\F1B6E"}.mdi-calendar-star::before{content:"\F09D3"}.mdi-calendar-star-four-points::before{content:"\F1C1F"}.mdi-calendar-star-outline::before{content:"\F1B53"}.mdi-calendar-start::before{content:"\F166D"}.mdi-calendar-start-outline::before{content:"\F1B6F"}.mdi-calendar-sync::before{content:"\F0E8E"}.mdi-calendar-sync-outline::before{content:"\F0E8F"}.mdi-calendar-text::before{content:"\F00F5"}.mdi-calendar-text-outline::before{content:"\F0C46"}.mdi-calendar-today::before{content:"\F00F6"}.mdi-calendar-today-outline::before{content:"\F1A30"}.mdi-calendar-week::before{content:"\F0A33"}.mdi-calendar-week-begin::before{content:"\F0A34"}.mdi-calendar-week-begin-outline::before{content:"\F1A31"}.mdi-calendar-week-outline::before{content:"\F1A34"}.mdi-calendar-weekend::before{content:"\F0ED9"}.mdi-calendar-weekend-outline::before{content:"\F0EDA"}.mdi-call-made::before{content:"\F00F7"}.mdi-call-merge::before{content:"\F00F8"}.mdi-call-missed::before{content:"\F00F9"}.mdi-call-received::before{content:"\F00FA"}.mdi-call-split::before{content:"\F00FB"}.mdi-camcorder::before{content:"\F00FC"}.mdi-camcorder-off::before{content:"\F00FF"}.mdi-camera::before{content:"\F0100"}.mdi-camera-account::before{content:"\F08CB"}.mdi-camera-burst::before{content:"\F0693"}.mdi-camera-control::before{content:"\F0B69"}.mdi-camera-document::before{content:"\F1871"}.mdi-camera-document-off::before{content:"\F1872"}.mdi-camera-enhance::before{content:"\F0101"}.mdi-camera-enhance-outline::before{content:"\F0B6A"}.mdi-camera-flip::before{content:"\F15D9"}.mdi-camera-flip-outline::before{content:"\F15DA"}.mdi-camera-front::before{content:"\F0102"}.mdi-camera-front-variant::before{content:"\F0103"}.mdi-camera-gopro::before{content:"\F07A1"}.mdi-camera-image::before{content:"\F08CC"}.mdi-camera-iris::before{content:"\F0104"}.mdi-camera-lock::before{content:"\F1A14"}.mdi-camera-lock-open::before{content:"\F1C0D"}.mdi-camera-lock-open-outline::before{content:"\F1C0E"}.mdi-camera-lock-outline::before{content:"\F1A15"}.mdi-camera-marker::before{content:"\F19A7"}.mdi-camera-marker-outline::before{content:"\F19A8"}.mdi-camera-metering-center::before{content:"\F07A2"}.mdi-camera-metering-matrix::before{content:"\F07A3"}.mdi-camera-metering-partial::before{content:"\F07A4"}.mdi-camera-metering-spot::before{content:"\F07A5"}.mdi-camera-off::before{content:"\F05DF"}.mdi-camera-off-outline::before{content:"\F19BF"}.mdi-camera-outline::before{content:"\F0D5D"}.mdi-camera-party-mode::before{content:"\F0105"}.mdi-camera-plus::before{content:"\F0EDB"}.mdi-camera-plus-outline::before{content:"\F0EDC"}.mdi-camera-rear::before{content:"\F0106"}.mdi-camera-rear-variant::before{content:"\F0107"}.mdi-camera-retake::before{content:"\F0E19"}.mdi-camera-retake-outline::before{content:"\F0E1A"}.mdi-camera-switch::before{content:"\F0108"}.mdi-camera-switch-outline::before{content:"\F084A"}.mdi-camera-timer::before{content:"\F0109"}.mdi-camera-wireless::before{content:"\F0DB6"}.mdi-camera-wireless-outline::before{content:"\F0DB7"}.mdi-campfire::before{content:"\F0EDD"}.mdi-cancel::before{content:"\F073A"}.mdi-candelabra::before{content:"\F17D2"}.mdi-candelabra-fire::before{content:"\F17D3"}.mdi-candle::before{content:"\F05E2"}.mdi-candy::before{content:"\F1970"}.mdi-candy-off::before{content:"\F1971"}.mdi-candy-off-outline::before{content:"\F1972"}.mdi-candy-outline::before{content:"\F1973"}.mdi-candycane::before{content:"\F010A"}.mdi-cannabis::before{content:"\F07A6"}.mdi-cannabis-off::before{content:"\F166E"}.mdi-caps-lock::before{content:"\F0A9B"}.mdi-car::before{content:"\F010B"}.mdi-car-2-plus::before{content:"\F1015"}.mdi-car-3-plus::before{content:"\F1016"}.mdi-car-arrow-left::before{content:"\F13B2"}.mdi-car-arrow-right::before{content:"\F13B3"}.mdi-car-back::before{content:"\F0E1B"}.mdi-car-battery::before{content:"\F010C"}.mdi-car-brake-abs::before{content:"\F0C47"}.mdi-car-brake-alert::before{content:"\F0C48"}.mdi-car-brake-fluid-level::before{content:"\F1909"}.mdi-car-brake-hold::before{content:"\F0D5E"}.mdi-car-brake-low-pressure::before{content:"\F190A"}.mdi-car-brake-parking::before{content:"\F0D5F"}.mdi-car-brake-retarder::before{content:"\F1017"}.mdi-car-brake-temperature::before{content:"\F190B"}.mdi-car-brake-worn-linings::before{content:"\F190C"}.mdi-car-child-seat::before{content:"\F0FA3"}.mdi-car-clock::before{content:"\F1974"}.mdi-car-clutch::before{content:"\F1018"}.mdi-car-cog::before{content:"\F13CC"}.mdi-car-connected::before{content:"\F010D"}.mdi-car-convertible::before{content:"\F07A7"}.mdi-car-coolant-level::before{content:"\F1019"}.mdi-car-cruise-control::before{content:"\F0D60"}.mdi-car-defrost-front::before{content:"\F0D61"}.mdi-car-defrost-rear::before{content:"\F0D62"}.mdi-car-door::before{content:"\F0B6B"}.mdi-car-door-lock::before{content:"\F109D"}.mdi-car-door-lock-open::before{content:"\F1C81"}.mdi-car-electric::before{content:"\F0B6C"}.mdi-car-electric-outline::before{content:"\F15B5"}.mdi-car-emergency::before{content:"\F160F"}.mdi-car-esp::before{content:"\F0C49"}.mdi-car-estate::before{content:"\F07A8"}.mdi-car-hatchback::before{content:"\F07A9"}.mdi-car-info::before{content:"\F11BE"}.mdi-car-key::before{content:"\F0B6D"}.mdi-car-lifted-pickup::before{content:"\F152D"}.mdi-car-light-alert::before{content:"\F190D"}.mdi-car-light-dimmed::before{content:"\F0C4A"}.mdi-car-light-fog::before{content:"\F0C4B"}.mdi-car-light-high::before{content:"\F0C4C"}.mdi-car-limousine::before{content:"\F08CD"}.mdi-car-multiple::before{content:"\F0B6E"}.mdi-car-off::before{content:"\F0E1C"}.mdi-car-outline::before{content:"\F14ED"}.mdi-car-parking-lights::before{content:"\F0D63"}.mdi-car-pickup::before{content:"\F07AA"}.mdi-car-search::before{content:"\F1B8D"}.mdi-car-search-outline::before{content:"\F1B8E"}.mdi-car-seat::before{content:"\F0FA4"}.mdi-car-seat-cooler::before{content:"\F0FA5"}.mdi-car-seat-heater::before{content:"\F0FA6"}.mdi-car-select::before{content:"\F1879"}.mdi-car-settings::before{content:"\F13CD"}.mdi-car-shift-pattern::before{content:"\F0F40"}.mdi-car-side::before{content:"\F07AB"}.mdi-car-speed-limiter::before{content:"\F190E"}.mdi-car-sports::before{content:"\F07AC"}.mdi-car-tire-alert::before{content:"\F0C4D"}.mdi-car-traction-control::before{content:"\F0D64"}.mdi-car-turbocharger::before{content:"\F101A"}.mdi-car-wash::before{content:"\F010E"}.mdi-car-windshield::before{content:"\F101B"}.mdi-car-windshield-outline::before{content:"\F101C"}.mdi-car-wireless::before{content:"\F1878"}.mdi-car-wrench::before{content:"\F1814"}.mdi-carabiner::before{content:"\F14C0"}.mdi-caravan::before{content:"\F07AD"}.mdi-card::before{content:"\F0B6F"}.mdi-card-account-details::before{content:"\F05D2"}.mdi-card-account-details-outline::before{content:"\F0DAB"}.mdi-card-account-details-star::before{content:"\F02A3"}.mdi-card-account-details-star-outline::before{content:"\F06DB"}.mdi-card-account-mail::before{content:"\F018E"}.mdi-card-account-mail-outline::before{content:"\F0E98"}.mdi-card-account-phone::before{content:"\F0E99"}.mdi-card-account-phone-outline::before{content:"\F0E9A"}.mdi-card-bulleted::before{content:"\F0B70"}.mdi-card-bulleted-off::before{content:"\F0B71"}.mdi-card-bulleted-off-outline::before{content:"\F0B72"}.mdi-card-bulleted-outline::before{content:"\F0B73"}.mdi-card-bulleted-settings::before{content:"\F0B74"}.mdi-card-bulleted-settings-outline::before{content:"\F0B75"}.mdi-card-minus::before{content:"\F1600"}.mdi-card-minus-outline::before{content:"\F1601"}.mdi-card-multiple::before{content:"\F17F1"}.mdi-card-multiple-outline::before{content:"\F17F2"}.mdi-card-off::before{content:"\F1602"}.mdi-card-off-outline::before{content:"\F1603"}.mdi-card-outline::before{content:"\F0B76"}.mdi-card-plus::before{content:"\F11FF"}.mdi-card-plus-outline::before{content:"\F1200"}.mdi-card-remove::before{content:"\F1604"}.mdi-card-remove-outline::before{content:"\F1605"}.mdi-card-search::before{content:"\F1074"}.mdi-card-search-outline::before{content:"\F1075"}.mdi-card-text::before{content:"\F0B77"}.mdi-card-text-outline::before{content:"\F0B78"}.mdi-cards::before{content:"\F0638"}.mdi-cards-club::before{content:"\F08CE"}.mdi-cards-club-outline::before{content:"\F189F"}.mdi-cards-diamond::before{content:"\F08CF"}.mdi-cards-diamond-outline::before{content:"\F101D"}.mdi-cards-heart::before{content:"\F08D0"}.mdi-cards-heart-outline::before{content:"\F18A0"}.mdi-cards-outline::before{content:"\F0639"}.mdi-cards-playing::before{content:"\F18A1"}.mdi-cards-playing-club::before{content:"\F18A2"}.mdi-cards-playing-club-multiple::before{content:"\F18A3"}.mdi-cards-playing-club-multiple-outline::before{content:"\F18A4"}.mdi-cards-playing-club-outline::before{content:"\F18A5"}.mdi-cards-playing-diamond::before{content:"\F18A6"}.mdi-cards-playing-diamond-multiple::before{content:"\F18A7"}.mdi-cards-playing-diamond-multiple-outline::before{content:"\F18A8"}.mdi-cards-playing-diamond-outline::before{content:"\F18A9"}.mdi-cards-playing-heart::before{content:"\F18AA"}.mdi-cards-playing-heart-multiple::before{content:"\F18AB"}.mdi-cards-playing-heart-multiple-outline::before{content:"\F18AC"}.mdi-cards-playing-heart-outline::before{content:"\F18AD"}.mdi-cards-playing-outline::before{content:"\F063A"}.mdi-cards-playing-spade::before{content:"\F18AE"}.mdi-cards-playing-spade-multiple::before{content:"\F18AF"}.mdi-cards-playing-spade-multiple-outline::before{content:"\F18B0"}.mdi-cards-playing-spade-outline::before{content:"\F18B1"}.mdi-cards-spade::before{content:"\F08D1"}.mdi-cards-spade-outline::before{content:"\F18B2"}.mdi-cards-variant::before{content:"\F06C7"}.mdi-carrot::before{content:"\F010F"}.mdi-cart::before{content:"\F0110"}.mdi-cart-arrow-down::before{content:"\F0D66"}.mdi-cart-arrow-right::before{content:"\F0C4E"}.mdi-cart-arrow-up::before{content:"\F0D67"}.mdi-cart-check::before{content:"\F15EA"}.mdi-cart-heart::before{content:"\F18E0"}.mdi-cart-minus::before{content:"\F0D68"}.mdi-cart-off::before{content:"\F066B"}.mdi-cart-outline::before{content:"\F0111"}.mdi-cart-percent::before{content:"\F1BAE"}.mdi-cart-plus::before{content:"\F0112"}.mdi-cart-remove::before{content:"\F0D69"}.mdi-cart-variant::before{content:"\F15EB"}.mdi-case-sensitive-alt::before{content:"\F0113"}.mdi-cash::before{content:"\F0114"}.mdi-cash-100::before{content:"\F0115"}.mdi-cash-check::before{content:"\F14EE"}.mdi-cash-clock::before{content:"\F1A91"}.mdi-cash-edit::before{content:"\F1CAB"}.mdi-cash-fast::before{content:"\F185C"}.mdi-cash-lock::before{content:"\F14EA"}.mdi-cash-lock-open::before{content:"\F14EB"}.mdi-cash-marker::before{content:"\F0DB8"}.mdi-cash-minus::before{content:"\F1260"}.mdi-cash-multiple::before{content:"\F0116"}.mdi-cash-off::before{content:"\F1C79"}.mdi-cash-plus::before{content:"\F1261"}.mdi-cash-refund::before{content:"\F0A9C"}.mdi-cash-register::before{content:"\F0CF4"}.mdi-cash-remove::before{content:"\F1262"}.mdi-cash-sync::before{content:"\F1A92"}.mdi-cassette::before{content:"\F09D4"}.mdi-cast::before{content:"\F0118"}.mdi-cast-audio::before{content:"\F101E"}.mdi-cast-audio-variant::before{content:"\F1749"}.mdi-cast-connected::before{content:"\F0119"}.mdi-cast-education::before{content:"\F0E1D"}.mdi-cast-off::before{content:"\F078A"}.mdi-cast-variant::before{content:"\F001F"}.mdi-castle::before{content:"\F011A"}.mdi-cat::before{content:"\F011B"}.mdi-cctv::before{content:"\F07AE"}.mdi-cctv-off::before{content:"\F185F"}.mdi-ceiling-fan::before{content:"\F1797"}.mdi-ceiling-fan-light::before{content:"\F1798"}.mdi-ceiling-light::before{content:"\F0769"}.mdi-ceiling-light-multiple::before{content:"\F18DD"}.mdi-ceiling-light-multiple-outline::before{content:"\F18DE"}.mdi-ceiling-light-outline::before{content:"\F17C7"}.mdi-cellphone::before{content:"\F011C"}.mdi-cellphone-arrow-down::before{content:"\F09D5"}.mdi-cellphone-arrow-down-variant::before{content:"\F19C5"}.mdi-cellphone-basic::before{content:"\F011E"}.mdi-cellphone-charging::before{content:"\F1397"}.mdi-cellphone-check::before{content:"\F17FD"}.mdi-cellphone-cog::before{content:"\F0951"}.mdi-cellphone-dock::before{content:"\F011F"}.mdi-cellphone-information::before{content:"\F0F41"}.mdi-cellphone-key::before{content:"\F094E"}.mdi-cellphone-link::before{content:"\F0121"}.mdi-cellphone-link-off::before{content:"\F0122"}.mdi-cellphone-lock::before{content:"\F094F"}.mdi-cellphone-marker::before{content:"\F183A"}.mdi-cellphone-message::before{content:"\F08D3"}.mdi-cellphone-message-off::before{content:"\F10D2"}.mdi-cellphone-nfc::before{content:"\F0E90"}.mdi-cellphone-nfc-off::before{content:"\F12D8"}.mdi-cellphone-off::before{content:"\F0950"}.mdi-cellphone-play::before{content:"\F101F"}.mdi-cellphone-remove::before{content:"\F094D"}.mdi-cellphone-screenshot::before{content:"\F0A35"}.mdi-cellphone-settings::before{content:"\F0123"}.mdi-cellphone-sound::before{content:"\F0952"}.mdi-cellphone-text::before{content:"\F08D2"}.mdi-cellphone-wireless::before{content:"\F0815"}.mdi-centos::before{content:"\F111A"}.mdi-certificate::before{content:"\F0124"}.mdi-certificate-outline::before{content:"\F1188"}.mdi-chair-rolling::before{content:"\F0F48"}.mdi-chair-school::before{content:"\F0125"}.mdi-chandelier::before{content:"\F1793"}.mdi-charity::before{content:"\F0C4F"}.mdi-charity-search::before{content:"\F1C82"}.mdi-chart-arc::before{content:"\F0126"}.mdi-chart-areaspline::before{content:"\F0127"}.mdi-chart-areaspline-variant::before{content:"\F0E91"}.mdi-chart-bar::before{content:"\F0128"}.mdi-chart-bar-stacked::before{content:"\F076A"}.mdi-chart-bell-curve::before{content:"\F0C50"}.mdi-chart-bell-curve-cumulative::before{content:"\F0FA7"}.mdi-chart-box::before{content:"\F154D"}.mdi-chart-box-multiple::before{content:"\F1CCD"}.mdi-chart-box-multiple-outline::before{content:"\F1CCE"}.mdi-chart-box-outline::before{content:"\F154E"}.mdi-chart-box-plus-outline::before{content:"\F154F"}.mdi-chart-bubble::before{content:"\F05E3"}.mdi-chart-donut::before{content:"\F07AF"}.mdi-chart-donut-variant::before{content:"\F07B0"}.mdi-chart-gantt::before{content:"\F066C"}.mdi-chart-histogram::before{content:"\F0129"}.mdi-chart-line::before{content:"\F012A"}.mdi-chart-line-stacked::before{content:"\F076B"}.mdi-chart-line-variant::before{content:"\F07B1"}.mdi-chart-multiline::before{content:"\F08D4"}.mdi-chart-multiple::before{content:"\F1213"}.mdi-chart-pie::before{content:"\F012B"}.mdi-chart-pie-outline::before{content:"\F1BDF"}.mdi-chart-ppf::before{content:"\F1380"}.mdi-chart-sankey::before{content:"\F11DF"}.mdi-chart-sankey-variant::before{content:"\F11E0"}.mdi-chart-scatter-plot::before{content:"\F0E92"}.mdi-chart-scatter-plot-hexbin::before{content:"\F066D"}.mdi-chart-timeline::before{content:"\F066E"}.mdi-chart-timeline-variant::before{content:"\F0E93"}.mdi-chart-timeline-variant-shimmer::before{content:"\F15B6"}.mdi-chart-tree::before{content:"\F0E94"}.mdi-chart-waterfall::before{content:"\F1918"}.mdi-chat::before{content:"\F0B79"}.mdi-chat-alert::before{content:"\F0B7A"}.mdi-chat-alert-outline::before{content:"\F12C9"}.mdi-chat-minus::before{content:"\F1410"}.mdi-chat-minus-outline::before{content:"\F1413"}.mdi-chat-outline::before{content:"\F0EDE"}.mdi-chat-plus::before{content:"\F140F"}.mdi-chat-plus-outline::before{content:"\F1412"}.mdi-chat-processing::before{content:"\F0B7B"}.mdi-chat-processing-outline::before{content:"\F12CA"}.mdi-chat-question::before{content:"\F1738"}.mdi-chat-question-outline::before{content:"\F1739"}.mdi-chat-remove::before{content:"\F1411"}.mdi-chat-remove-outline::before{content:"\F1414"}.mdi-chat-sleep::before{content:"\F12D1"}.mdi-chat-sleep-outline::before{content:"\F12D2"}.mdi-check::before{content:"\F012C"}.mdi-check-all::before{content:"\F012D"}.mdi-check-bold::before{content:"\F0E1E"}.mdi-check-circle::before{content:"\F05E0"}.mdi-check-circle-outline::before{content:"\F05E1"}.mdi-check-decagram::before{content:"\F0791"}.mdi-check-decagram-outline::before{content:"\F1740"}.mdi-check-network::before{content:"\F0C53"}.mdi-check-network-outline::before{content:"\F0C54"}.mdi-check-outline::before{content:"\F0855"}.mdi-check-underline::before{content:"\F0E1F"}.mdi-check-underline-circle::before{content:"\F0E20"}.mdi-check-underline-circle-outline::before{content:"\F0E21"}.mdi-checkbook::before{content:"\F0A9D"}.mdi-checkbook-arrow-left::before{content:"\F1C1D"}.mdi-checkbook-arrow-right::before{content:"\F1C1E"}.mdi-checkbox-blank::before{content:"\F012E"}.mdi-checkbox-blank-badge::before{content:"\F1176"}.mdi-checkbox-blank-badge-outline::before{content:"\F0117"}.mdi-checkbox-blank-circle::before{content:"\F012F"}.mdi-checkbox-blank-circle-outline::before{content:"\F0130"}.mdi-checkbox-blank-off::before{content:"\F12EC"}.mdi-checkbox-blank-off-outline::before{content:"\F12ED"}.mdi-checkbox-blank-outline::before{content:"\F0131"}.mdi-checkbox-intermediate::before{content:"\F0856"}.mdi-checkbox-intermediate-variant::before{content:"\F1B54"}.mdi-checkbox-marked::before{content:"\F0132"}.mdi-checkbox-marked-circle::before{content:"\F0133"}.mdi-checkbox-marked-circle-auto-outline::before{content:"\F1C26"}.mdi-checkbox-marked-circle-minus-outline::before{content:"\F1C27"}.mdi-checkbox-marked-circle-outline::before{content:"\F0134"}.mdi-checkbox-marked-circle-plus-outline::before{content:"\F1927"}.mdi-checkbox-marked-outline::before{content:"\F0135"}.mdi-checkbox-multiple-blank::before{content:"\F0136"}.mdi-checkbox-multiple-blank-circle::before{content:"\F063B"}.mdi-checkbox-multiple-blank-circle-outline::before{content:"\F063C"}.mdi-checkbox-multiple-blank-outline::before{content:"\F0137"}.mdi-checkbox-multiple-marked::before{content:"\F0138"}.mdi-checkbox-multiple-marked-circle::before{content:"\F063D"}.mdi-checkbox-multiple-marked-circle-outline::before{content:"\F063E"}.mdi-checkbox-multiple-marked-outline::before{content:"\F0139"}.mdi-checkbox-multiple-outline::before{content:"\F0C51"}.mdi-checkbox-outline::before{content:"\F0C52"}.mdi-checkerboard::before{content:"\F013A"}.mdi-checkerboard-minus::before{content:"\F1202"}.mdi-checkerboard-plus::before{content:"\F1201"}.mdi-checkerboard-remove::before{content:"\F1203"}.mdi-cheese::before{content:"\F12B9"}.mdi-cheese-off::before{content:"\F13EE"}.mdi-chef-hat::before{content:"\F0B7C"}.mdi-chemical-weapon::before{content:"\F013B"}.mdi-chess-bishop::before{content:"\F085C"}.mdi-chess-king::before{content:"\F0857"}.mdi-chess-knight::before{content:"\F0858"}.mdi-chess-pawn::before{content:"\F0859"}.mdi-chess-queen::before{content:"\F085A"}.mdi-chess-rook::before{content:"\F085B"}.mdi-chevron-double-down::before{content:"\F013C"}.mdi-chevron-double-left::before{content:"\F013D"}.mdi-chevron-double-right::before{content:"\F013E"}.mdi-chevron-double-up::before{content:"\F013F"}.mdi-chevron-down::before{content:"\F0140"}.mdi-chevron-down-box::before{content:"\F09D6"}.mdi-chevron-down-box-outline::before{content:"\F09D7"}.mdi-chevron-down-circle::before{content:"\F0B26"}.mdi-chevron-down-circle-outline::before{content:"\F0B27"}.mdi-chevron-left::before{content:"\F0141"}.mdi-chevron-left-box::before{content:"\F09D8"}.mdi-chevron-left-box-outline::before{content:"\F09D9"}.mdi-chevron-left-circle::before{content:"\F0B28"}.mdi-chevron-left-circle-outline::before{content:"\F0B29"}.mdi-chevron-right::before{content:"\F0142"}.mdi-chevron-right-box::before{content:"\F09DA"}.mdi-chevron-right-box-outline::before{content:"\F09DB"}.mdi-chevron-right-circle::before{content:"\F0B2A"}.mdi-chevron-right-circle-outline::before{content:"\F0B2B"}.mdi-chevron-triple-down::before{content:"\F0DB9"}.mdi-chevron-triple-left::before{content:"\F0DBA"}.mdi-chevron-triple-right::before{content:"\F0DBB"}.mdi-chevron-triple-up::before{content:"\F0DBC"}.mdi-chevron-up::before{content:"\F0143"}.mdi-chevron-up-box::before{content:"\F09DC"}.mdi-chevron-up-box-outline::before{content:"\F09DD"}.mdi-chevron-up-circle::before{content:"\F0B2C"}.mdi-chevron-up-circle-outline::before{content:"\F0B2D"}.mdi-chili-alert::before{content:"\F17EA"}.mdi-chili-alert-outline::before{content:"\F17EB"}.mdi-chili-hot::before{content:"\F07B2"}.mdi-chili-hot-outline::before{content:"\F17EC"}.mdi-chili-medium::before{content:"\F07B3"}.mdi-chili-medium-outline::before{content:"\F17ED"}.mdi-chili-mild::before{content:"\F07B4"}.mdi-chili-mild-outline::before{content:"\F17EE"}.mdi-chili-off::before{content:"\F1467"}.mdi-chili-off-outline::before{content:"\F17EF"}.mdi-chip::before{content:"\F061A"}.mdi-church::before{content:"\F0144"}.mdi-church-outline::before{content:"\F1B02"}.mdi-cigar::before{content:"\F1189"}.mdi-cigar-off::before{content:"\F141B"}.mdi-circle::before{content:"\F0765"}.mdi-circle-box::before{content:"\F15DC"}.mdi-circle-box-outline::before{content:"\F15DD"}.mdi-circle-double::before{content:"\F0E95"}.mdi-circle-edit-outline::before{content:"\F08D5"}.mdi-circle-expand::before{content:"\F0E96"}.mdi-circle-half::before{content:"\F1395"}.mdi-circle-half-full::before{content:"\F1396"}.mdi-circle-medium::before{content:"\F09DE"}.mdi-circle-multiple::before{content:"\F0B38"}.mdi-circle-multiple-outline::before{content:"\F0695"}.mdi-circle-off-outline::before{content:"\F10D3"}.mdi-circle-opacity::before{content:"\F1853"}.mdi-circle-outline::before{content:"\F0766"}.mdi-circle-slice-1::before{content:"\F0A9E"}.mdi-circle-slice-2::before{content:"\F0A9F"}.mdi-circle-slice-3::before{content:"\F0AA0"}.mdi-circle-slice-4::before{content:"\F0AA1"}.mdi-circle-slice-5::before{content:"\F0AA2"}.mdi-circle-slice-6::before{content:"\F0AA3"}.mdi-circle-slice-7::before{content:"\F0AA4"}.mdi-circle-slice-8::before{content:"\F0AA5"}.mdi-circle-small::before{content:"\F09DF"}.mdi-circular-saw::before{content:"\F0E22"}.mdi-city::before{content:"\F0146"}.mdi-city-switch::before{content:"\F1C28"}.mdi-city-variant::before{content:"\F0A36"}.mdi-city-variant-outline::before{content:"\F0A37"}.mdi-clipboard::before{content:"\F0147"}.mdi-clipboard-account::before{content:"\F0148"}.mdi-clipboard-account-outline::before{content:"\F0C55"}.mdi-clipboard-alert::before{content:"\F0149"}.mdi-clipboard-alert-outline::before{content:"\F0CF7"}.mdi-clipboard-arrow-down::before{content:"\F014A"}.mdi-clipboard-arrow-down-outline::before{content:"\F0C56"}.mdi-clipboard-arrow-left::before{content:"\F014B"}.mdi-clipboard-arrow-left-outline::before{content:"\F0CF8"}.mdi-clipboard-arrow-right::before{content:"\F0CF9"}.mdi-clipboard-arrow-right-outline::before{content:"\F0CFA"}.mdi-clipboard-arrow-up::before{content:"\F0C57"}.mdi-clipboard-arrow-up-outline::before{content:"\F0C58"}.mdi-clipboard-check::before{content:"\F014E"}.mdi-clipboard-check-multiple::before{content:"\F1263"}.mdi-clipboard-check-multiple-outline::before{content:"\F1264"}.mdi-clipboard-check-outline::before{content:"\F08A8"}.mdi-clipboard-clock::before{content:"\F16E2"}.mdi-clipboard-clock-outline::before{content:"\F16E3"}.mdi-clipboard-edit::before{content:"\F14E5"}.mdi-clipboard-edit-outline::before{content:"\F14E6"}.mdi-clipboard-file::before{content:"\F1265"}.mdi-clipboard-file-outline::before{content:"\F1266"}.mdi-clipboard-flow::before{content:"\F06C8"}.mdi-clipboard-flow-outline::before{content:"\F1117"}.mdi-clipboard-list::before{content:"\F10D4"}.mdi-clipboard-list-outline::before{content:"\F10D5"}.mdi-clipboard-minus::before{content:"\F1618"}.mdi-clipboard-minus-outline::before{content:"\F1619"}.mdi-clipboard-multiple::before{content:"\F1267"}.mdi-clipboard-multiple-outline::before{content:"\F1268"}.mdi-clipboard-off::before{content:"\F161A"}.mdi-clipboard-off-outline::before{content:"\F161B"}.mdi-clipboard-outline::before{content:"\F014C"}.mdi-clipboard-play::before{content:"\F0C59"}.mdi-clipboard-play-multiple::before{content:"\F1269"}.mdi-clipboard-play-multiple-outline::before{content:"\F126A"}.mdi-clipboard-play-outline::before{content:"\F0C5A"}.mdi-clipboard-plus::before{content:"\F0751"}.mdi-clipboard-plus-outline::before{content:"\F131F"}.mdi-clipboard-pulse::before{content:"\F085D"}.mdi-clipboard-pulse-outline::before{content:"\F085E"}.mdi-clipboard-remove::before{content:"\F161C"}.mdi-clipboard-remove-outline::before{content:"\F161D"}.mdi-clipboard-search::before{content:"\F161E"}.mdi-clipboard-search-outline::before{content:"\F161F"}.mdi-clipboard-text::before{content:"\F014D"}.mdi-clipboard-text-clock::before{content:"\F18F9"}.mdi-clipboard-text-clock-outline::before{content:"\F18FA"}.mdi-clipboard-text-multiple::before{content:"\F126B"}.mdi-clipboard-text-multiple-outline::before{content:"\F126C"}.mdi-clipboard-text-off::before{content:"\F1620"}.mdi-clipboard-text-off-outline::before{content:"\F1621"}.mdi-clipboard-text-outline::before{content:"\F0A38"}.mdi-clipboard-text-play::before{content:"\F0C5B"}.mdi-clipboard-text-play-outline::before{content:"\F0C5C"}.mdi-clipboard-text-search::before{content:"\F1622"}.mdi-clipboard-text-search-outline::before{content:"\F1623"}.mdi-clippy::before{content:"\F014F"}.mdi-clock::before{content:"\F0954"}.mdi-clock-alert::before{content:"\F0955"}.mdi-clock-alert-outline::before{content:"\F05CE"}.mdi-clock-check::before{content:"\F0FA8"}.mdi-clock-check-outline::before{content:"\F0FA9"}.mdi-clock-digital::before{content:"\F0E97"}.mdi-clock-edit::before{content:"\F19BA"}.mdi-clock-edit-outline::before{content:"\F19BB"}.mdi-clock-end::before{content:"\F0151"}.mdi-clock-fast::before{content:"\F0152"}.mdi-clock-in::before{content:"\F0153"}.mdi-clock-minus::before{content:"\F1863"}.mdi-clock-minus-outline::before{content:"\F1864"}.mdi-clock-out::before{content:"\F0154"}.mdi-clock-outline::before{content:"\F0150"}.mdi-clock-plus::before{content:"\F1861"}.mdi-clock-plus-outline::before{content:"\F1862"}.mdi-clock-remove::before{content:"\F1865"}.mdi-clock-remove-outline::before{content:"\F1866"}.mdi-clock-star-four-points::before{content:"\F1C29"}.mdi-clock-star-four-points-outline::before{content:"\F1C2A"}.mdi-clock-start::before{content:"\F0155"}.mdi-clock-time-eight::before{content:"\F1446"}.mdi-clock-time-eight-outline::before{content:"\F1452"}.mdi-clock-time-eleven::before{content:"\F1449"}.mdi-clock-time-eleven-outline::before{content:"\F1455"}.mdi-clock-time-five::before{content:"\F1443"}.mdi-clock-time-five-outline::before{content:"\F144F"}.mdi-clock-time-four::before{content:"\F1442"}.mdi-clock-time-four-outline::before{content:"\F144E"}.mdi-clock-time-nine::before{content:"\F1447"}.mdi-clock-time-nine-outline::before{content:"\F1453"}.mdi-clock-time-one::before{content:"\F143F"}.mdi-clock-time-one-outline::before{content:"\F144B"}.mdi-clock-time-seven::before{content:"\F1445"}.mdi-clock-time-seven-outline::before{content:"\F1451"}.mdi-clock-time-six::before{content:"\F1444"}.mdi-clock-time-six-outline::before{content:"\F1450"}.mdi-clock-time-ten::before{content:"\F1448"}.mdi-clock-time-ten-outline::before{content:"\F1454"}.mdi-clock-time-three::before{content:"\F1441"}.mdi-clock-time-three-outline::before{content:"\F144D"}.mdi-clock-time-twelve::before{content:"\F144A"}.mdi-clock-time-twelve-outline::before{content:"\F1456"}.mdi-clock-time-two::before{content:"\F1440"}.mdi-clock-time-two-outline::before{content:"\F144C"}.mdi-close::before{content:"\F0156"}.mdi-close-box::before{content:"\F0157"}.mdi-close-box-multiple::before{content:"\F0C5D"}.mdi-close-box-multiple-outline::before{content:"\F0C5E"}.mdi-close-box-outline::before{content:"\F0158"}.mdi-close-circle::before{content:"\F0159"}.mdi-close-circle-multiple::before{content:"\F062A"}.mdi-close-circle-multiple-outline::before{content:"\F0883"}.mdi-close-circle-outline::before{content:"\F015A"}.mdi-close-network::before{content:"\F015B"}.mdi-close-network-outline::before{content:"\F0C5F"}.mdi-close-octagon::before{content:"\F015C"}.mdi-close-octagon-outline::before{content:"\F015D"}.mdi-close-outline::before{content:"\F06C9"}.mdi-close-thick::before{content:"\F1398"}.mdi-closed-caption::before{content:"\F015E"}.mdi-closed-caption-outline::before{content:"\F0DBD"}.mdi-cloud::before{content:"\F015F"}.mdi-cloud-alert::before{content:"\F09E0"}.mdi-cloud-alert-outline::before{content:"\F1BE0"}.mdi-cloud-arrow-down::before{content:"\F1BE1"}.mdi-cloud-arrow-down-outline::before{content:"\F1BE2"}.mdi-cloud-arrow-left::before{content:"\F1BE3"}.mdi-cloud-arrow-left-outline::before{content:"\F1BE4"}.mdi-cloud-arrow-right::before{content:"\F1BE5"}.mdi-cloud-arrow-right-outline::before{content:"\F1BE6"}.mdi-cloud-arrow-up::before{content:"\F1BE7"}.mdi-cloud-arrow-up-outline::before{content:"\F1BE8"}.mdi-cloud-braces::before{content:"\F07B5"}.mdi-cloud-cancel::before{content:"\F1BE9"}.mdi-cloud-cancel-outline::before{content:"\F1BEA"}.mdi-cloud-check::before{content:"\F1BEB"}.mdi-cloud-check-outline::before{content:"\F1BEC"}.mdi-cloud-check-variant::before{content:"\F0160"}.mdi-cloud-check-variant-outline::before{content:"\F12CC"}.mdi-cloud-circle::before{content:"\F0161"}.mdi-cloud-circle-outline::before{content:"\F1BED"}.mdi-cloud-clock::before{content:"\F1BEE"}.mdi-cloud-clock-outline::before{content:"\F1BEF"}.mdi-cloud-cog::before{content:"\F1BF0"}.mdi-cloud-cog-outline::before{content:"\F1BF1"}.mdi-cloud-download::before{content:"\F0162"}.mdi-cloud-download-outline::before{content:"\F0B7D"}.mdi-cloud-key::before{content:"\F1CA1"}.mdi-cloud-key-outline::before{content:"\F1CA2"}.mdi-cloud-lock::before{content:"\F11F1"}.mdi-cloud-lock-open::before{content:"\F1BF2"}.mdi-cloud-lock-open-outline::before{content:"\F1BF3"}.mdi-cloud-lock-outline::before{content:"\F11F2"}.mdi-cloud-minus::before{content:"\F1BF4"}.mdi-cloud-minus-outline::before{content:"\F1BF5"}.mdi-cloud-off::before{content:"\F1BF6"}.mdi-cloud-off-outline::before{content:"\F0164"}.mdi-cloud-outline::before{content:"\F0163"}.mdi-cloud-percent::before{content:"\F1A35"}.mdi-cloud-percent-outline::before{content:"\F1A36"}.mdi-cloud-plus::before{content:"\F1BF7"}.mdi-cloud-plus-outline::before{content:"\F1BF8"}.mdi-cloud-print::before{content:"\F0165"}.mdi-cloud-print-outline::before{content:"\F0166"}.mdi-cloud-question::before{content:"\F0A39"}.mdi-cloud-question-outline::before{content:"\F1BF9"}.mdi-cloud-refresh::before{content:"\F1BFA"}.mdi-cloud-refresh-outline::before{content:"\F1BFB"}.mdi-cloud-refresh-variant::before{content:"\F052A"}.mdi-cloud-refresh-variant-outline::before{content:"\F1BFC"}.mdi-cloud-remove::before{content:"\F1BFD"}.mdi-cloud-remove-outline::before{content:"\F1BFE"}.mdi-cloud-search::before{content:"\F0956"}.mdi-cloud-search-outline::before{content:"\F0957"}.mdi-cloud-sync::before{content:"\F063F"}.mdi-cloud-sync-outline::before{content:"\F12D6"}.mdi-cloud-tags::before{content:"\F07B6"}.mdi-cloud-upload::before{content:"\F0167"}.mdi-cloud-upload-outline::before{content:"\F0B7E"}.mdi-clouds::before{content:"\F1B95"}.mdi-clover::before{content:"\F0816"}.mdi-clover-outline::before{content:"\F1C62"}.mdi-coach-lamp::before{content:"\F1020"}.mdi-coach-lamp-variant::before{content:"\F1A37"}.mdi-coat-rack::before{content:"\F109E"}.mdi-code-array::before{content:"\F0168"}.mdi-code-block-braces::before{content:"\F1C83"}.mdi-code-block-brackets::before{content:"\F1C84"}.mdi-code-block-parentheses::before{content:"\F1C85"}.mdi-code-block-tags::before{content:"\F1C86"}.mdi-code-braces::before{content:"\F0169"}.mdi-code-braces-box::before{content:"\F10D6"}.mdi-code-brackets::before{content:"\F016A"}.mdi-code-equal::before{content:"\F016B"}.mdi-code-greater-than::before{content:"\F016C"}.mdi-code-greater-than-or-equal::before{content:"\F016D"}.mdi-code-json::before{content:"\F0626"}.mdi-code-less-than::before{content:"\F016E"}.mdi-code-less-than-or-equal::before{content:"\F016F"}.mdi-code-not-equal::before{content:"\F0170"}.mdi-code-not-equal-variant::before{content:"\F0171"}.mdi-code-parentheses::before{content:"\F0172"}.mdi-code-parentheses-box::before{content:"\F10D7"}.mdi-code-string::before{content:"\F0173"}.mdi-code-tags::before{content:"\F0174"}.mdi-code-tags-check::before{content:"\F0694"}.mdi-codepen::before{content:"\F0175"}.mdi-coffee::before{content:"\F0176"}.mdi-coffee-maker::before{content:"\F109F"}.mdi-coffee-maker-check::before{content:"\F1931"}.mdi-coffee-maker-check-outline::before{content:"\F1932"}.mdi-coffee-maker-outline::before{content:"\F181B"}.mdi-coffee-off::before{content:"\F0FAA"}.mdi-coffee-off-outline::before{content:"\F0FAB"}.mdi-coffee-outline::before{content:"\F06CA"}.mdi-coffee-to-go::before{content:"\F0177"}.mdi-coffee-to-go-outline::before{content:"\F130E"}.mdi-coffin::before{content:"\F0B7F"}.mdi-cog::before{content:"\F0493"}.mdi-cog-box::before{content:"\F0494"}.mdi-cog-clockwise::before{content:"\F11DD"}.mdi-cog-counterclockwise::before{content:"\F11DE"}.mdi-cog-off::before{content:"\F13CE"}.mdi-cog-off-outline::before{content:"\F13CF"}.mdi-cog-outline::before{content:"\F08BB"}.mdi-cog-pause::before{content:"\F1933"}.mdi-cog-pause-outline::before{content:"\F1934"}.mdi-cog-play::before{content:"\F1935"}.mdi-cog-play-outline::before{content:"\F1936"}.mdi-cog-refresh::before{content:"\F145E"}.mdi-cog-refresh-outline::before{content:"\F145F"}.mdi-cog-stop::before{content:"\F1937"}.mdi-cog-stop-outline::before{content:"\F1938"}.mdi-cog-sync::before{content:"\F1460"}.mdi-cog-sync-outline::before{content:"\F1461"}.mdi-cog-transfer::before{content:"\F105B"}.mdi-cog-transfer-outline::before{content:"\F105C"}.mdi-cogs::before{content:"\F08D6"}.mdi-collage::before{content:"\F0640"}.mdi-collapse-all::before{content:"\F0AA6"}.mdi-collapse-all-outline::before{content:"\F0AA7"}.mdi-color-helper::before{content:"\F0179"}.mdi-comma::before{content:"\F0E23"}.mdi-comma-box::before{content:"\F0E2B"}.mdi-comma-box-outline::before{content:"\F0E24"}.mdi-comma-circle::before{content:"\F0E25"}.mdi-comma-circle-outline::before{content:"\F0E26"}.mdi-comment::before{content:"\F017A"}.mdi-comment-account::before{content:"\F017B"}.mdi-comment-account-outline::before{content:"\F017C"}.mdi-comment-alert::before{content:"\F017D"}.mdi-comment-alert-outline::before{content:"\F017E"}.mdi-comment-arrow-left::before{content:"\F09E1"}.mdi-comment-arrow-left-outline::before{content:"\F09E2"}.mdi-comment-arrow-right::before{content:"\F09E3"}.mdi-comment-arrow-right-outline::before{content:"\F09E4"}.mdi-comment-bookmark::before{content:"\F15AE"}.mdi-comment-bookmark-outline::before{content:"\F15AF"}.mdi-comment-check::before{content:"\F017F"}.mdi-comment-check-outline::before{content:"\F0180"}.mdi-comment-edit::before{content:"\F11BF"}.mdi-comment-edit-outline::before{content:"\F12C4"}.mdi-comment-eye::before{content:"\F0A3A"}.mdi-comment-eye-outline::before{content:"\F0A3B"}.mdi-comment-flash::before{content:"\F15B0"}.mdi-comment-flash-outline::before{content:"\F15B1"}.mdi-comment-minus::before{content:"\F15DF"}.mdi-comment-minus-outline::before{content:"\F15E0"}.mdi-comment-multiple::before{content:"\F085F"}.mdi-comment-multiple-outline::before{content:"\F0181"}.mdi-comment-off::before{content:"\F15E1"}.mdi-comment-off-outline::before{content:"\F15E2"}.mdi-comment-outline::before{content:"\F0182"}.mdi-comment-plus::before{content:"\F09E5"}.mdi-comment-plus-outline::before{content:"\F0183"}.mdi-comment-processing::before{content:"\F0184"}.mdi-comment-processing-outline::before{content:"\F0185"}.mdi-comment-question::before{content:"\F0817"}.mdi-comment-question-outline::before{content:"\F0186"}.mdi-comment-quote::before{content:"\F1021"}.mdi-comment-quote-outline::before{content:"\F1022"}.mdi-comment-remove::before{content:"\F05DE"}.mdi-comment-remove-outline::before{content:"\F0187"}.mdi-comment-search::before{content:"\F0A3C"}.mdi-comment-search-outline::before{content:"\F0A3D"}.mdi-comment-text::before{content:"\F0188"}.mdi-comment-text-multiple::before{content:"\F0860"}.mdi-comment-text-multiple-outline::before{content:"\F0861"}.mdi-comment-text-outline::before{content:"\F0189"}.mdi-compare::before{content:"\F018A"}.mdi-compare-horizontal::before{content:"\F1492"}.mdi-compare-remove::before{content:"\F18B3"}.mdi-compare-vertical::before{content:"\F1493"}.mdi-compass::before{content:"\F018B"}.mdi-compass-off::before{content:"\F0B80"}.mdi-compass-off-outline::before{content:"\F0B81"}.mdi-compass-outline::before{content:"\F018C"}.mdi-compass-rose::before{content:"\F1382"}.mdi-compost::before{content:"\F1A38"}.mdi-cone::before{content:"\F194C"}.mdi-cone-off::before{content:"\F194D"}.mdi-connection::before{content:"\F1616"}.mdi-console::before{content:"\F018D"}.mdi-console-line::before{content:"\F07B7"}.mdi-console-network::before{content:"\F08A9"}.mdi-console-network-outline::before{content:"\F0C60"}.mdi-consolidate::before{content:"\F10D8"}.mdi-contactless-payment::before{content:"\F0D6A"}.mdi-contactless-payment-circle::before{content:"\F0321"}.mdi-contactless-payment-circle-outline::before{content:"\F0408"}.mdi-contacts::before{content:"\F06CB"}.mdi-contacts-outline::before{content:"\F05B8"}.mdi-contain::before{content:"\F0A3E"}.mdi-contain-end::before{content:"\F0A3F"}.mdi-contain-start::before{content:"\F0A40"}.mdi-content-copy::before{content:"\F018F"}.mdi-content-cut::before{content:"\F0190"}.mdi-content-duplicate::before{content:"\F0191"}.mdi-content-paste::before{content:"\F0192"}.mdi-content-save::before{content:"\F0193"}.mdi-content-save-alert::before{content:"\F0F42"}.mdi-content-save-alert-outline::before{content:"\F0F43"}.mdi-content-save-all::before{content:"\F0194"}.mdi-content-save-all-outline::before{content:"\F0F44"}.mdi-content-save-check::before{content:"\F18EA"}.mdi-content-save-check-outline::before{content:"\F18EB"}.mdi-content-save-cog::before{content:"\F145B"}.mdi-content-save-cog-outline::before{content:"\F145C"}.mdi-content-save-edit::before{content:"\F0CFB"}.mdi-content-save-edit-outline::before{content:"\F0CFC"}.mdi-content-save-minus::before{content:"\F1B43"}.mdi-content-save-minus-outline::before{content:"\F1B44"}.mdi-content-save-move::before{content:"\F0E27"}.mdi-content-save-move-outline::before{content:"\F0E28"}.mdi-content-save-off::before{content:"\F1643"}.mdi-content-save-off-outline::before{content:"\F1644"}.mdi-content-save-outline::before{content:"\F0818"}.mdi-content-save-plus::before{content:"\F1B41"}.mdi-content-save-plus-outline::before{content:"\F1B42"}.mdi-content-save-settings::before{content:"\F061B"}.mdi-content-save-settings-outline::before{content:"\F0B2E"}.mdi-contrast::before{content:"\F0195"}.mdi-contrast-box::before{content:"\F0196"}.mdi-contrast-circle::before{content:"\F0197"}.mdi-controller::before{content:"\F02B4"}.mdi-controller-classic::before{content:"\F0B82"}.mdi-controller-classic-outline::before{content:"\F0B83"}.mdi-controller-off::before{content:"\F02B5"}.mdi-cookie::before{content:"\F0198"}.mdi-cookie-alert::before{content:"\F16D0"}.mdi-cookie-alert-outline::before{content:"\F16D1"}.mdi-cookie-check::before{content:"\F16D2"}.mdi-cookie-check-outline::before{content:"\F16D3"}.mdi-cookie-clock::before{content:"\F16E4"}.mdi-cookie-clock-outline::before{content:"\F16E5"}.mdi-cookie-cog::before{content:"\F16D4"}.mdi-cookie-cog-outline::before{content:"\F16D5"}.mdi-cookie-edit::before{content:"\F16E6"}.mdi-cookie-edit-outline::before{content:"\F16E7"}.mdi-cookie-lock::before{content:"\F16E8"}.mdi-cookie-lock-outline::before{content:"\F16E9"}.mdi-cookie-minus::before{content:"\F16DA"}.mdi-cookie-minus-outline::before{content:"\F16DB"}.mdi-cookie-off::before{content:"\F16EA"}.mdi-cookie-off-outline::before{content:"\F16EB"}.mdi-cookie-outline::before{content:"\F16DE"}.mdi-cookie-plus::before{content:"\F16D6"}.mdi-cookie-plus-outline::before{content:"\F16D7"}.mdi-cookie-refresh::before{content:"\F16EC"}.mdi-cookie-refresh-outline::before{content:"\F16ED"}.mdi-cookie-remove::before{content:"\F16D8"}.mdi-cookie-remove-outline::before{content:"\F16D9"}.mdi-cookie-settings::before{content:"\F16DC"}.mdi-cookie-settings-outline::before{content:"\F16DD"}.mdi-coolant-temperature::before{content:"\F03C8"}.mdi-copyleft::before{content:"\F1939"}.mdi-copyright::before{content:"\F05E6"}.mdi-cordova::before{content:"\F0958"}.mdi-corn::before{content:"\F07B8"}.mdi-corn-off::before{content:"\F13EF"}.mdi-cosine-wave::before{content:"\F1479"}.mdi-counter::before{content:"\F0199"}.mdi-countertop::before{content:"\F181C"}.mdi-countertop-outline::before{content:"\F181D"}.mdi-cow::before{content:"\F019A"}.mdi-cow-off::before{content:"\F18FC"}.mdi-cpu-32-bit::before{content:"\F0EDF"}.mdi-cpu-64-bit::before{content:"\F0EE0"}.mdi-cradle::before{content:"\F198B"}.mdi-cradle-outline::before{content:"\F1991"}.mdi-crane::before{content:"\F0862"}.mdi-creation::before{content:"\F0674"}.mdi-creation-outline::before{content:"\F1C2B"}.mdi-creative-commons::before{content:"\F0D6B"}.mdi-credit-card::before{content:"\F0FEF"}.mdi-credit-card-check::before{content:"\F13D0"}.mdi-credit-card-check-outline::before{content:"\F13D1"}.mdi-credit-card-chip::before{content:"\F190F"}.mdi-credit-card-chip-outline::before{content:"\F1910"}.mdi-credit-card-clock::before{content:"\F0EE1"}.mdi-credit-card-clock-outline::before{content:"\F0EE2"}.mdi-credit-card-edit::before{content:"\F17D7"}.mdi-credit-card-edit-outline::before{content:"\F17D8"}.mdi-credit-card-fast::before{content:"\F1911"}.mdi-credit-card-fast-outline::before{content:"\F1912"}.mdi-credit-card-lock::before{content:"\F18E7"}.mdi-credit-card-lock-outline::before{content:"\F18E8"}.mdi-credit-card-marker::before{content:"\F06A8"}.mdi-credit-card-marker-outline::before{content:"\F0DBE"}.mdi-credit-card-minus::before{content:"\F0FAC"}.mdi-credit-card-minus-outline::before{content:"\F0FAD"}.mdi-credit-card-multiple::before{content:"\F0FF0"}.mdi-credit-card-multiple-outline::before{content:"\F019C"}.mdi-credit-card-off::before{content:"\F0FF1"}.mdi-credit-card-off-outline::before{content:"\F05E4"}.mdi-credit-card-outline::before{content:"\F019B"}.mdi-credit-card-plus::before{content:"\F0FF2"}.mdi-credit-card-plus-outline::before{content:"\F0676"}.mdi-credit-card-refresh::before{content:"\F1645"}.mdi-credit-card-refresh-outline::before{content:"\F1646"}.mdi-credit-card-refund::before{content:"\F0FF3"}.mdi-credit-card-refund-outline::before{content:"\F0AA8"}.mdi-credit-card-remove::before{content:"\F0FAE"}.mdi-credit-card-remove-outline::before{content:"\F0FAF"}.mdi-credit-card-scan::before{content:"\F0FF4"}.mdi-credit-card-scan-outline::before{content:"\F019D"}.mdi-credit-card-search::before{content:"\F1647"}.mdi-credit-card-search-outline::before{content:"\F1648"}.mdi-credit-card-settings::before{content:"\F0FF5"}.mdi-credit-card-settings-outline::before{content:"\F08D7"}.mdi-credit-card-sync::before{content:"\F1649"}.mdi-credit-card-sync-outline::before{content:"\F164A"}.mdi-credit-card-wireless::before{content:"\F0802"}.mdi-credit-card-wireless-off::before{content:"\F057A"}.mdi-credit-card-wireless-off-outline::before{content:"\F057B"}.mdi-credit-card-wireless-outline::before{content:"\F0D6C"}.mdi-cricket::before{content:"\F0D6D"}.mdi-crop::before{content:"\F019E"}.mdi-crop-free::before{content:"\F019F"}.mdi-crop-landscape::before{content:"\F01A0"}.mdi-crop-portrait::before{content:"\F01A1"}.mdi-crop-rotate::before{content:"\F0696"}.mdi-crop-square::before{content:"\F01A2"}.mdi-cross::before{content:"\F0953"}.mdi-cross-bolnisi::before{content:"\F0CED"}.mdi-cross-celtic::before{content:"\F0CF5"}.mdi-cross-outline::before{content:"\F0CF6"}.mdi-crosshairs::before{content:"\F01A3"}.mdi-crosshairs-gps::before{content:"\F01A4"}.mdi-crosshairs-off::before{content:"\F0F45"}.mdi-crosshairs-question::before{content:"\F1136"}.mdi-crowd::before{content:"\F1975"}.mdi-crown::before{content:"\F01A5"}.mdi-crown-circle::before{content:"\F17DC"}.mdi-crown-circle-outline::before{content:"\F17DD"}.mdi-crown-outline::before{content:"\F11D0"}.mdi-cryengine::before{content:"\F0959"}.mdi-crystal-ball::before{content:"\F0B2F"}.mdi-cube::before{content:"\F01A6"}.mdi-cube-off::before{content:"\F141C"}.mdi-cube-off-outline::before{content:"\F141D"}.mdi-cube-outline::before{content:"\F01A7"}.mdi-cube-scan::before{content:"\F0B84"}.mdi-cube-send::before{content:"\F01A8"}.mdi-cube-unfolded::before{content:"\F01A9"}.mdi-cup::before{content:"\F01AA"}.mdi-cup-off::before{content:"\F05E5"}.mdi-cup-off-outline::before{content:"\F137D"}.mdi-cup-outline::before{content:"\F130F"}.mdi-cup-water::before{content:"\F01AB"}.mdi-cupboard::before{content:"\F0F46"}.mdi-cupboard-outline::before{content:"\F0F47"}.mdi-cupcake::before{content:"\F095A"}.mdi-curling::before{content:"\F0863"}.mdi-currency-bdt::before{content:"\F0864"}.mdi-currency-brl::before{content:"\F0B85"}.mdi-currency-btc::before{content:"\F01AC"}.mdi-currency-cny::before{content:"\F07BA"}.mdi-currency-eth::before{content:"\F07BB"}.mdi-currency-eur::before{content:"\F01AD"}.mdi-currency-eur-off::before{content:"\F1315"}.mdi-currency-fra::before{content:"\F1A39"}.mdi-currency-gbp::before{content:"\F01AE"}.mdi-currency-ils::before{content:"\F0C61"}.mdi-currency-inr::before{content:"\F01AF"}.mdi-currency-jpy::before{content:"\F07BC"}.mdi-currency-krw::before{content:"\F07BD"}.mdi-currency-kzt::before{content:"\F0865"}.mdi-currency-mnt::before{content:"\F1512"}.mdi-currency-ngn::before{content:"\F01B0"}.mdi-currency-php::before{content:"\F09E6"}.mdi-currency-rial::before{content:"\F0E9C"}.mdi-currency-rub::before{content:"\F01B1"}.mdi-currency-rupee::before{content:"\F1976"}.mdi-currency-sign::before{content:"\F07BE"}.mdi-currency-thb::before{content:"\F1C05"}.mdi-currency-try::before{content:"\F01B2"}.mdi-currency-twd::before{content:"\F07BF"}.mdi-currency-uah::before{content:"\F1B9B"}.mdi-currency-usd::before{content:"\F01C1"}.mdi-currency-usd-off::before{content:"\F067A"}.mdi-current-ac::before{content:"\F1480"}.mdi-current-dc::before{content:"\F095C"}.mdi-cursor-default::before{content:"\F01C0"}.mdi-cursor-default-click::before{content:"\F0CFD"}.mdi-cursor-default-click-outline::before{content:"\F0CFE"}.mdi-cursor-default-gesture::before{content:"\F1127"}.mdi-cursor-default-gesture-outline::before{content:"\F1128"}.mdi-cursor-default-outline::before{content:"\F01BF"}.mdi-cursor-move::before{content:"\F01BE"}.mdi-cursor-pointer::before{content:"\F01BD"}.mdi-cursor-text::before{content:"\F05E7"}.mdi-curtains::before{content:"\F1846"}.mdi-curtains-closed::before{content:"\F1847"}.mdi-cylinder::before{content:"\F194E"}.mdi-cylinder-off::before{content:"\F194F"}.mdi-dance-ballroom::before{content:"\F15FB"}.mdi-dance-pole::before{content:"\F1578"}.mdi-data-matrix::before{content:"\F153C"}.mdi-data-matrix-edit::before{content:"\F153D"}.mdi-data-matrix-minus::before{content:"\F153E"}.mdi-data-matrix-plus::before{content:"\F153F"}.mdi-data-matrix-remove::before{content:"\F1540"}.mdi-data-matrix-scan::before{content:"\F1541"}.mdi-database::before{content:"\F01BC"}.mdi-database-alert::before{content:"\F163A"}.mdi-database-alert-outline::before{content:"\F1624"}.mdi-database-arrow-down::before{content:"\F163B"}.mdi-database-arrow-down-outline::before{content:"\F1625"}.mdi-database-arrow-left::before{content:"\F163C"}.mdi-database-arrow-left-outline::before{content:"\F1626"}.mdi-database-arrow-right::before{content:"\F163D"}.mdi-database-arrow-right-outline::before{content:"\F1627"}.mdi-database-arrow-up::before{content:"\F163E"}.mdi-database-arrow-up-outline::before{content:"\F1628"}.mdi-database-check::before{content:"\F0AA9"}.mdi-database-check-outline::before{content:"\F1629"}.mdi-database-clock::before{content:"\F163F"}.mdi-database-clock-outline::before{content:"\F162A"}.mdi-database-cog::before{content:"\F164B"}.mdi-database-cog-outline::before{content:"\F164C"}.mdi-database-edit::before{content:"\F0B86"}.mdi-database-edit-outline::before{content:"\F162B"}.mdi-database-export::before{content:"\F095E"}.mdi-database-export-outline::before{content:"\F162C"}.mdi-database-eye::before{content:"\F191F"}.mdi-database-eye-off::before{content:"\F1920"}.mdi-database-eye-off-outline::before{content:"\F1921"}.mdi-database-eye-outline::before{content:"\F1922"}.mdi-database-import::before{content:"\F095D"}.mdi-database-import-outline::before{content:"\F162D"}.mdi-database-lock::before{content:"\F0AAA"}.mdi-database-lock-outline::before{content:"\F162E"}.mdi-database-marker::before{content:"\F12F6"}.mdi-database-marker-outline::before{content:"\F162F"}.mdi-database-minus::before{content:"\F01BB"}.mdi-database-minus-outline::before{content:"\F1630"}.mdi-database-off::before{content:"\F1640"}.mdi-database-off-outline::before{content:"\F1631"}.mdi-database-outline::before{content:"\F1632"}.mdi-database-plus::before{content:"\F01BA"}.mdi-database-plus-outline::before{content:"\F1633"}.mdi-database-refresh::before{content:"\F05C2"}.mdi-database-refresh-outline::before{content:"\F1634"}.mdi-database-remove::before{content:"\F0D00"}.mdi-database-remove-outline::before{content:"\F1635"}.mdi-database-search::before{content:"\F0866"}.mdi-database-search-outline::before{content:"\F1636"}.mdi-database-settings::before{content:"\F0D01"}.mdi-database-settings-outline::before{content:"\F1637"}.mdi-database-sync::before{content:"\F0CFF"}.mdi-database-sync-outline::before{content:"\F1638"}.mdi-death-star::before{content:"\F08D8"}.mdi-death-star-variant::before{content:"\F08D9"}.mdi-deathly-hallows::before{content:"\F0B87"}.mdi-debian::before{content:"\F08DA"}.mdi-debug-step-into::before{content:"\F01B9"}.mdi-debug-step-out::before{content:"\F01B8"}.mdi-debug-step-over::before{content:"\F01B7"}.mdi-decagram::before{content:"\F076C"}.mdi-decagram-outline::before{content:"\F076D"}.mdi-decimal::before{content:"\F10A1"}.mdi-decimal-comma::before{content:"\F10A2"}.mdi-decimal-comma-decrease::before{content:"\F10A3"}.mdi-decimal-comma-increase::before{content:"\F10A4"}.mdi-decimal-decrease::before{content:"\F01B6"}.mdi-decimal-increase::before{content:"\F01B5"}.mdi-delete::before{content:"\F01B4"}.mdi-delete-alert::before{content:"\F10A5"}.mdi-delete-alert-outline::before{content:"\F10A6"}.mdi-delete-circle::before{content:"\F0683"}.mdi-delete-circle-outline::before{content:"\F0B88"}.mdi-delete-clock::before{content:"\F1556"}.mdi-delete-clock-outline::before{content:"\F1557"}.mdi-delete-empty::before{content:"\F06CC"}.mdi-delete-empty-outline::before{content:"\F0E9D"}.mdi-delete-forever::before{content:"\F05E8"}.mdi-delete-forever-outline::before{content:"\F0B89"}.mdi-delete-off::before{content:"\F10A7"}.mdi-delete-off-outline::before{content:"\F10A8"}.mdi-delete-outline::before{content:"\F09E7"}.mdi-delete-restore::before{content:"\F0819"}.mdi-delete-sweep::before{content:"\F05E9"}.mdi-delete-sweep-outline::before{content:"\F0C62"}.mdi-delete-variant::before{content:"\F01B3"}.mdi-delta::before{content:"\F01C2"}.mdi-desk::before{content:"\F1239"}.mdi-desk-lamp::before{content:"\F095F"}.mdi-desk-lamp-off::before{content:"\F1B1F"}.mdi-desk-lamp-on::before{content:"\F1B20"}.mdi-deskphone::before{content:"\F01C3"}.mdi-desktop-classic::before{content:"\F07C0"}.mdi-desktop-tower::before{content:"\F01C5"}.mdi-desktop-tower-monitor::before{content:"\F0AAB"}.mdi-details::before{content:"\F01C6"}.mdi-dev-to::before{content:"\F0D6E"}.mdi-developer-board::before{content:"\F0697"}.mdi-deviantart::before{content:"\F01C7"}.mdi-devices::before{content:"\F0FB0"}.mdi-dharmachakra::before{content:"\F094B"}.mdi-diabetes::before{content:"\F1126"}.mdi-dialpad::before{content:"\F061C"}.mdi-diameter::before{content:"\F0C63"}.mdi-diameter-outline::before{content:"\F0C64"}.mdi-diameter-variant::before{content:"\F0C65"}.mdi-diamond::before{content:"\F0B8A"}.mdi-diamond-outline::before{content:"\F0B8B"}.mdi-diamond-stone::before{content:"\F01C8"}.mdi-diaper-outline::before{content:"\F1CCF"}.mdi-dice-1::before{content:"\F01CA"}.mdi-dice-1-outline::before{content:"\F114A"}.mdi-dice-2::before{content:"\F01CB"}.mdi-dice-2-outline::before{content:"\F114B"}.mdi-dice-3::before{content:"\F01CC"}.mdi-dice-3-outline::before{content:"\F114C"}.mdi-dice-4::before{content:"\F01CD"}.mdi-dice-4-outline::before{content:"\F114D"}.mdi-dice-5::before{content:"\F01CE"}.mdi-dice-5-outline::before{content:"\F114E"}.mdi-dice-6::before{content:"\F01CF"}.mdi-dice-6-outline::before{content:"\F114F"}.mdi-dice-d10::before{content:"\F1153"}.mdi-dice-d10-outline::before{content:"\F076F"}.mdi-dice-d12::before{content:"\F1154"}.mdi-dice-d12-outline::before{content:"\F0867"}.mdi-dice-d20::before{content:"\F1155"}.mdi-dice-d20-outline::before{content:"\F05EA"}.mdi-dice-d4::before{content:"\F1150"}.mdi-dice-d4-outline::before{content:"\F05EB"}.mdi-dice-d6::before{content:"\F1151"}.mdi-dice-d6-outline::before{content:"\F05ED"}.mdi-dice-d8::before{content:"\F1152"}.mdi-dice-d8-outline::before{content:"\F05EC"}.mdi-dice-multiple::before{content:"\F076E"}.mdi-dice-multiple-outline::before{content:"\F1156"}.mdi-digital-ocean::before{content:"\F1237"}.mdi-dip-switch::before{content:"\F07C1"}.mdi-directions::before{content:"\F01D0"}.mdi-directions-fork::before{content:"\F0641"}.mdi-disc::before{content:"\F05EE"}.mdi-disc-alert::before{content:"\F01D1"}.mdi-disc-player::before{content:"\F0960"}.mdi-dishwasher::before{content:"\F0AAC"}.mdi-dishwasher-alert::before{content:"\F11B8"}.mdi-dishwasher-off::before{content:"\F11B9"}.mdi-disqus::before{content:"\F01D2"}.mdi-distribute-horizontal-center::before{content:"\F11C9"}.mdi-distribute-horizontal-left::before{content:"\F11C8"}.mdi-distribute-horizontal-right::before{content:"\F11CA"}.mdi-distribute-vertical-bottom::before{content:"\F11CB"}.mdi-distribute-vertical-center::before{content:"\F11CC"}.mdi-distribute-vertical-top::before{content:"\F11CD"}.mdi-diversify::before{content:"\F1877"}.mdi-diving::before{content:"\F1977"}.mdi-diving-flippers::before{content:"\F0DBF"}.mdi-diving-helmet::before{content:"\F0DC0"}.mdi-diving-scuba::before{content:"\F1B77"}.mdi-diving-scuba-flag::before{content:"\F0DC2"}.mdi-diving-scuba-mask::before{content:"\F0DC1"}.mdi-diving-scuba-tank::before{content:"\F0DC3"}.mdi-diving-scuba-tank-multiple::before{content:"\F0DC4"}.mdi-diving-snorkel::before{content:"\F0DC5"}.mdi-division::before{content:"\F01D4"}.mdi-division-box::before{content:"\F01D5"}.mdi-dlna::before{content:"\F0A41"}.mdi-dna::before{content:"\F0684"}.mdi-dns::before{content:"\F01D6"}.mdi-dns-outline::before{content:"\F0B8C"}.mdi-dock-bottom::before{content:"\F10A9"}.mdi-dock-left::before{content:"\F10AA"}.mdi-dock-right::before{content:"\F10AB"}.mdi-dock-top::before{content:"\F1513"}.mdi-dock-window::before{content:"\F10AC"}.mdi-docker::before{content:"\F0868"}.mdi-doctor::before{content:"\F0A42"}.mdi-dog::before{content:"\F0A43"}.mdi-dog-service::before{content:"\F0AAD"}.mdi-dog-side::before{content:"\F0A44"}.mdi-dog-side-off::before{content:"\F16EE"}.mdi-dolby::before{content:"\F06B3"}.mdi-dolly::before{content:"\F0E9E"}.mdi-dolphin::before{content:"\F18B4"}.mdi-domain::before{content:"\F01D7"}.mdi-domain-off::before{content:"\F0D6F"}.mdi-domain-plus::before{content:"\F10AD"}.mdi-domain-remove::before{content:"\F10AE"}.mdi-domain-switch::before{content:"\F1C2C"}.mdi-dome-light::before{content:"\F141E"}.mdi-domino-mask::before{content:"\F1023"}.mdi-donkey::before{content:"\F07C2"}.mdi-door::before{content:"\F081A"}.mdi-door-closed::before{content:"\F081B"}.mdi-door-closed-cancel::before{content:"\F1C93"}.mdi-door-closed-lock::before{content:"\F10AF"}.mdi-door-open::before{content:"\F081C"}.mdi-door-sliding::before{content:"\F181E"}.mdi-door-sliding-lock::before{content:"\F181F"}.mdi-door-sliding-open::before{content:"\F1820"}.mdi-doorbell::before{content:"\F12E6"}.mdi-doorbell-video::before{content:"\F0869"}.mdi-dot-net::before{content:"\F0AAE"}.mdi-dots-circle::before{content:"\F1978"}.mdi-dots-grid::before{content:"\F15FC"}.mdi-dots-hexagon::before{content:"\F15FF"}.mdi-dots-horizontal::before{content:"\F01D8"}.mdi-dots-horizontal-circle::before{content:"\F07C3"}.mdi-dots-horizontal-circle-outline::before{content:"\F0B8D"}.mdi-dots-square::before{content:"\F15FD"}.mdi-dots-triangle::before{content:"\F15FE"}.mdi-dots-vertical::before{content:"\F01D9"}.mdi-dots-vertical-circle::before{content:"\F07C4"}.mdi-dots-vertical-circle-outline::before{content:"\F0B8E"}.mdi-download::before{content:"\F01DA"}.mdi-download-box::before{content:"\F1462"}.mdi-download-box-outline::before{content:"\F1463"}.mdi-download-circle::before{content:"\F1464"}.mdi-download-circle-outline::before{content:"\F1465"}.mdi-download-lock::before{content:"\F1320"}.mdi-download-lock-outline::before{content:"\F1321"}.mdi-download-multiple::before{content:"\F09E9"}.mdi-download-multiple-outline::before{content:"\F1CD0"}.mdi-download-network::before{content:"\F06F4"}.mdi-download-network-outline::before{content:"\F0C66"}.mdi-download-off::before{content:"\F10B0"}.mdi-download-off-outline::before{content:"\F10B1"}.mdi-download-outline::before{content:"\F0B8F"}.mdi-drag::before{content:"\F01DB"}.mdi-drag-horizontal::before{content:"\F01DC"}.mdi-drag-horizontal-variant::before{content:"\F12F0"}.mdi-drag-variant::before{content:"\F0B90"}.mdi-drag-vertical::before{content:"\F01DD"}.mdi-drag-vertical-variant::before{content:"\F12F1"}.mdi-drama-masks::before{content:"\F0D02"}.mdi-draw::before{content:"\F0F49"}.mdi-draw-pen::before{content:"\F19B9"}.mdi-drawing::before{content:"\F01DE"}.mdi-drawing-box::before{content:"\F01DF"}.mdi-dresser::before{content:"\F0F4A"}.mdi-dresser-outline::before{content:"\F0F4B"}.mdi-drone::before{content:"\F01E2"}.mdi-dropbox::before{content:"\F01E3"}.mdi-drupal::before{content:"\F01E4"}.mdi-duck::before{content:"\F01E5"}.mdi-dumbbell::before{content:"\F01E6"}.mdi-dump-truck::before{content:"\F0C67"}.mdi-ear-hearing::before{content:"\F07C5"}.mdi-ear-hearing-loop::before{content:"\F1AEE"}.mdi-ear-hearing-off::before{content:"\F0A45"}.mdi-earbuds::before{content:"\F184F"}.mdi-earbuds-off::before{content:"\F1850"}.mdi-earbuds-off-outline::before{content:"\F1851"}.mdi-earbuds-outline::before{content:"\F1852"}.mdi-earth::before{content:"\F01E7"}.mdi-earth-arrow-down::before{content:"\F1C87"}.mdi-earth-arrow-left::before{content:"\F1C88"}.mdi-earth-arrow-right::before{content:"\F1311"}.mdi-earth-arrow-up::before{content:"\F1C89"}.mdi-earth-box::before{content:"\F06CD"}.mdi-earth-box-minus::before{content:"\F1407"}.mdi-earth-box-off::before{content:"\F06CE"}.mdi-earth-box-plus::before{content:"\F1406"}.mdi-earth-box-remove::before{content:"\F1408"}.mdi-earth-minus::before{content:"\F1404"}.mdi-earth-off::before{content:"\F01E8"}.mdi-earth-plus::before{content:"\F1403"}.mdi-earth-remove::before{content:"\F1405"}.mdi-egg::before{content:"\F0AAF"}.mdi-egg-easter::before{content:"\F0AB0"}.mdi-egg-fried::before{content:"\F184A"}.mdi-egg-off::before{content:"\F13F0"}.mdi-egg-off-outline::before{content:"\F13F1"}.mdi-egg-outline::before{content:"\F13F2"}.mdi-eiffel-tower::before{content:"\F156B"}.mdi-eight-track::before{content:"\F09EA"}.mdi-eject::before{content:"\F01EA"}.mdi-eject-circle::before{content:"\F1B23"}.mdi-eject-circle-outline::before{content:"\F1B24"}.mdi-eject-outline::before{content:"\F0B91"}.mdi-electric-switch::before{content:"\F0E9F"}.mdi-electric-switch-closed::before{content:"\F10D9"}.mdi-electron-framework::before{content:"\F1024"}.mdi-elephant::before{content:"\F07C6"}.mdi-elevation-decline::before{content:"\F01EB"}.mdi-elevation-rise::before{content:"\F01EC"}.mdi-elevator::before{content:"\F01ED"}.mdi-elevator-down::before{content:"\F12C2"}.mdi-elevator-passenger::before{content:"\F1381"}.mdi-elevator-passenger-off::before{content:"\F1979"}.mdi-elevator-passenger-off-outline::before{content:"\F197A"}.mdi-elevator-passenger-outline::before{content:"\F197B"}.mdi-elevator-up::before{content:"\F12C1"}.mdi-ellipse::before{content:"\F0EA0"}.mdi-ellipse-outline::before{content:"\F0EA1"}.mdi-email::before{content:"\F01EE"}.mdi-email-alert::before{content:"\F06CF"}.mdi-email-alert-outline::before{content:"\F0D42"}.mdi-email-arrow-left::before{content:"\F10DA"}.mdi-email-arrow-left-outline::before{content:"\F10DB"}.mdi-email-arrow-right::before{content:"\F10DC"}.mdi-email-arrow-right-outline::before{content:"\F10DD"}.mdi-email-box::before{content:"\F0D03"}.mdi-email-check::before{content:"\F0AB1"}.mdi-email-check-outline::before{content:"\F0AB2"}.mdi-email-edit::before{content:"\F0EE3"}.mdi-email-edit-outline::before{content:"\F0EE4"}.mdi-email-fast::before{content:"\F186F"}.mdi-email-fast-outline::before{content:"\F1870"}.mdi-email-heart-outline::before{content:"\F1C5B"}.mdi-email-lock::before{content:"\F01F1"}.mdi-email-lock-outline::before{content:"\F1B61"}.mdi-email-mark-as-unread::before{content:"\F0B92"}.mdi-email-minus::before{content:"\F0EE5"}.mdi-email-minus-outline::before{content:"\F0EE6"}.mdi-email-multiple::before{content:"\F0EE7"}.mdi-email-multiple-outline::before{content:"\F0EE8"}.mdi-email-newsletter::before{content:"\F0FB1"}.mdi-email-off::before{content:"\F13E3"}.mdi-email-off-outline::before{content:"\F13E4"}.mdi-email-open::before{content:"\F01EF"}.mdi-email-open-heart-outline::before{content:"\F1C5C"}.mdi-email-open-multiple::before{content:"\F0EE9"}.mdi-email-open-multiple-outline::before{content:"\F0EEA"}.mdi-email-open-outline::before{content:"\F05EF"}.mdi-email-outline::before{content:"\F01F0"}.mdi-email-plus::before{content:"\F09EB"}.mdi-email-plus-outline::before{content:"\F09EC"}.mdi-email-remove::before{content:"\F1661"}.mdi-email-remove-outline::before{content:"\F1662"}.mdi-email-seal::before{content:"\F195B"}.mdi-email-seal-outline::before{content:"\F195C"}.mdi-email-search::before{content:"\F0961"}.mdi-email-search-outline::before{content:"\F0962"}.mdi-email-sync::before{content:"\F12C7"}.mdi-email-sync-outline::before{content:"\F12C8"}.mdi-email-variant::before{content:"\F05F0"}.mdi-ember::before{content:"\F0B30"}.mdi-emby::before{content:"\F06B4"}.mdi-emoticon::before{content:"\F0C68"}.mdi-emoticon-angry::before{content:"\F0C69"}.mdi-emoticon-angry-outline::before{content:"\F0C6A"}.mdi-emoticon-confused::before{content:"\F10DE"}.mdi-emoticon-confused-outline::before{content:"\F10DF"}.mdi-emoticon-cool::before{content:"\F0C6B"}.mdi-emoticon-cool-outline::before{content:"\F01F3"}.mdi-emoticon-cry::before{content:"\F0C6C"}.mdi-emoticon-cry-outline::before{content:"\F0C6D"}.mdi-emoticon-dead::before{content:"\F0C6E"}.mdi-emoticon-dead-outline::before{content:"\F069B"}.mdi-emoticon-devil::before{content:"\F0C6F"}.mdi-emoticon-devil-outline::before{content:"\F01F4"}.mdi-emoticon-excited::before{content:"\F0C70"}.mdi-emoticon-excited-outline::before{content:"\F069C"}.mdi-emoticon-frown::before{content:"\F0F4C"}.mdi-emoticon-frown-outline::before{content:"\F0F4D"}.mdi-emoticon-happy::before{content:"\F0C71"}.mdi-emoticon-happy-outline::before{content:"\F01F5"}.mdi-emoticon-kiss::before{content:"\F0C72"}.mdi-emoticon-kiss-outline::before{content:"\F0C73"}.mdi-emoticon-lol::before{content:"\F1214"}.mdi-emoticon-lol-outline::before{content:"\F1215"}.mdi-emoticon-minus::before{content:"\F1CB2"}.mdi-emoticon-minus-outline::before{content:"\F1CB3"}.mdi-emoticon-neutral::before{content:"\F0C74"}.mdi-emoticon-neutral-outline::before{content:"\F01F6"}.mdi-emoticon-outline::before{content:"\F01F2"}.mdi-emoticon-plus::before{content:"\F1CB4"}.mdi-emoticon-plus-outline::before{content:"\F1CB5"}.mdi-emoticon-poop::before{content:"\F01F7"}.mdi-emoticon-poop-outline::before{content:"\F0C75"}.mdi-emoticon-remove::before{content:"\F1CB6"}.mdi-emoticon-remove-outline::before{content:"\F1CB7"}.mdi-emoticon-sad::before{content:"\F0C76"}.mdi-emoticon-sad-outline::before{content:"\F01F8"}.mdi-emoticon-sick::before{content:"\F157C"}.mdi-emoticon-sick-outline::before{content:"\F157D"}.mdi-emoticon-tongue::before{content:"\F01F9"}.mdi-emoticon-tongue-outline::before{content:"\F0C77"}.mdi-emoticon-wink::before{content:"\F0C78"}.mdi-emoticon-wink-outline::before{content:"\F0C79"}.mdi-engine::before{content:"\F01FA"}.mdi-engine-off::before{content:"\F0A46"}.mdi-engine-off-outline::before{content:"\F0A47"}.mdi-engine-outline::before{content:"\F01FB"}.mdi-epsilon::before{content:"\F10E0"}.mdi-equal::before{content:"\F01FC"}.mdi-equal-box::before{content:"\F01FD"}.mdi-equalizer::before{content:"\F0EA2"}.mdi-equalizer-outline::before{content:"\F0EA3"}.mdi-eraser::before{content:"\F01FE"}.mdi-eraser-variant::before{content:"\F0642"}.mdi-escalator::before{content:"\F01FF"}.mdi-escalator-box::before{content:"\F1399"}.mdi-escalator-down::before{content:"\F12C0"}.mdi-escalator-up::before{content:"\F12BF"}.mdi-eslint::before{content:"\F0C7A"}.mdi-et::before{content:"\F0AB3"}.mdi-ethereum::before{content:"\F086A"}.mdi-ethernet::before{content:"\F0200"}.mdi-ethernet-cable::before{content:"\F0201"}.mdi-ethernet-cable-off::before{content:"\F0202"}.mdi-ethernet-off::before{content:"\F1CD1"}.mdi-ev-plug-ccs1::before{content:"\F1519"}.mdi-ev-plug-ccs2::before{content:"\F151A"}.mdi-ev-plug-chademo::before{content:"\F151B"}.mdi-ev-plug-tesla::before{content:"\F151C"}.mdi-ev-plug-type1::before{content:"\F151D"}.mdi-ev-plug-type2::before{content:"\F151E"}.mdi-ev-station::before{content:"\F05F1"}.mdi-evernote::before{content:"\F0204"}.mdi-excavator::before{content:"\F1025"}.mdi-exclamation::before{content:"\F0205"}.mdi-exclamation-thick::before{content:"\F1238"}.mdi-exit-run::before{content:"\F0A48"}.mdi-exit-to-app::before{content:"\F0206"}.mdi-expand-all::before{content:"\F0AB4"}.mdi-expand-all-outline::before{content:"\F0AB5"}.mdi-expansion-card::before{content:"\F08AE"}.mdi-expansion-card-variant::before{content:"\F0FB2"}.mdi-exponent::before{content:"\F0963"}.mdi-exponent-box::before{content:"\F0964"}.mdi-export::before{content:"\F0207"}.mdi-export-variant::before{content:"\F0B93"}.mdi-eye::before{content:"\F0208"}.mdi-eye-arrow-left::before{content:"\F18FD"}.mdi-eye-arrow-left-outline::before{content:"\F18FE"}.mdi-eye-arrow-right::before{content:"\F18FF"}.mdi-eye-arrow-right-outline::before{content:"\F1900"}.mdi-eye-check::before{content:"\F0D04"}.mdi-eye-check-outline::before{content:"\F0D05"}.mdi-eye-circle::before{content:"\F0B94"}.mdi-eye-circle-outline::before{content:"\F0B95"}.mdi-eye-closed::before{content:"\F1CA3"}.mdi-eye-lock::before{content:"\F1C06"}.mdi-eye-lock-open::before{content:"\F1C07"}.mdi-eye-lock-open-outline::before{content:"\F1C08"}.mdi-eye-lock-outline::before{content:"\F1C09"}.mdi-eye-minus::before{content:"\F1026"}.mdi-eye-minus-outline::before{content:"\F1027"}.mdi-eye-off::before{content:"\F0209"}.mdi-eye-off-outline::before{content:"\F06D1"}.mdi-eye-outline::before{content:"\F06D0"}.mdi-eye-plus::before{content:"\F086B"}.mdi-eye-plus-outline::before{content:"\F086C"}.mdi-eye-refresh::before{content:"\F197C"}.mdi-eye-refresh-outline::before{content:"\F197D"}.mdi-eye-remove::before{content:"\F15E3"}.mdi-eye-remove-outline::before{content:"\F15E4"}.mdi-eye-settings::before{content:"\F086D"}.mdi-eye-settings-outline::before{content:"\F086E"}.mdi-eyedropper::before{content:"\F020A"}.mdi-eyedropper-minus::before{content:"\F13DD"}.mdi-eyedropper-off::before{content:"\F13DF"}.mdi-eyedropper-plus::before{content:"\F13DC"}.mdi-eyedropper-remove::before{content:"\F13DE"}.mdi-eyedropper-variant::before{content:"\F020B"}.mdi-face-agent::before{content:"\F0D70"}.mdi-face-man::before{content:"\F0643"}.mdi-face-man-outline::before{content:"\F0B96"}.mdi-face-man-profile::before{content:"\F0644"}.mdi-face-man-shimmer::before{content:"\F15CC"}.mdi-face-man-shimmer-outline::before{content:"\F15CD"}.mdi-face-mask::before{content:"\F1586"}.mdi-face-mask-outline::before{content:"\F1587"}.mdi-face-recognition::before{content:"\F0C7B"}.mdi-face-woman::before{content:"\F1077"}.mdi-face-woman-outline::before{content:"\F1078"}.mdi-face-woman-profile::before{content:"\F1076"}.mdi-face-woman-shimmer::before{content:"\F15CE"}.mdi-face-woman-shimmer-outline::before{content:"\F15CF"}.mdi-facebook::before{content:"\F020C"}.mdi-facebook-gaming::before{content:"\F07DD"}.mdi-facebook-messenger::before{content:"\F020E"}.mdi-facebook-workplace::before{content:"\F0B31"}.mdi-factory::before{content:"\F020F"}.mdi-family-tree::before{content:"\F160E"}.mdi-fan::before{content:"\F0210"}.mdi-fan-alert::before{content:"\F146C"}.mdi-fan-auto::before{content:"\F171D"}.mdi-fan-chevron-down::before{content:"\F146D"}.mdi-fan-chevron-up::before{content:"\F146E"}.mdi-fan-clock::before{content:"\F1A3A"}.mdi-fan-minus::before{content:"\F1470"}.mdi-fan-off::before{content:"\F081D"}.mdi-fan-plus::before{content:"\F146F"}.mdi-fan-remove::before{content:"\F1471"}.mdi-fan-speed-1::before{content:"\F1472"}.mdi-fan-speed-2::before{content:"\F1473"}.mdi-fan-speed-3::before{content:"\F1474"}.mdi-fast-forward::before{content:"\F0211"}.mdi-fast-forward-10::before{content:"\F0D71"}.mdi-fast-forward-15::before{content:"\F193A"}.mdi-fast-forward-30::before{content:"\F0D06"}.mdi-fast-forward-45::before{content:"\F1B12"}.mdi-fast-forward-5::before{content:"\F11F8"}.mdi-fast-forward-60::before{content:"\F160B"}.mdi-fast-forward-outline::before{content:"\F06D2"}.mdi-faucet::before{content:"\F1B29"}.mdi-faucet-variant::before{content:"\F1B2A"}.mdi-fax::before{content:"\F0212"}.mdi-feather::before{content:"\F06D3"}.mdi-feature-search::before{content:"\F0A49"}.mdi-feature-search-outline::before{content:"\F0A4A"}.mdi-fedora::before{content:"\F08DB"}.mdi-fence::before{content:"\F179A"}.mdi-fence-electric::before{content:"\F17F6"}.mdi-fencing::before{content:"\F14C1"}.mdi-ferris-wheel::before{content:"\F0EA4"}.mdi-ferry::before{content:"\F0213"}.mdi-file::before{content:"\F0214"}.mdi-file-account::before{content:"\F073B"}.mdi-file-account-outline::before{content:"\F1028"}.mdi-file-alert::before{content:"\F0A4B"}.mdi-file-alert-outline::before{content:"\F0A4C"}.mdi-file-arrow-left-right::before{content:"\F1A93"}.mdi-file-arrow-left-right-outline::before{content:"\F1A94"}.mdi-file-arrow-up-down::before{content:"\F1A95"}.mdi-file-arrow-up-down-outline::before{content:"\F1A96"}.mdi-file-cabinet::before{content:"\F0AB6"}.mdi-file-cad::before{content:"\F0EEB"}.mdi-file-cad-box::before{content:"\F0EEC"}.mdi-file-cancel::before{content:"\F0DC6"}.mdi-file-cancel-outline::before{content:"\F0DC7"}.mdi-file-certificate::before{content:"\F1186"}.mdi-file-certificate-outline::before{content:"\F1187"}.mdi-file-chart::before{content:"\F0215"}.mdi-file-chart-check::before{content:"\F19C6"}.mdi-file-chart-check-outline::before{content:"\F19C7"}.mdi-file-chart-outline::before{content:"\F1029"}.mdi-file-check::before{content:"\F0216"}.mdi-file-check-outline::before{content:"\F0E29"}.mdi-file-clock::before{content:"\F12E1"}.mdi-file-clock-outline::before{content:"\F12E2"}.mdi-file-cloud::before{content:"\F0217"}.mdi-file-cloud-outline::before{content:"\F102A"}.mdi-file-code::before{content:"\F022E"}.mdi-file-code-outline::before{content:"\F102B"}.mdi-file-cog::before{content:"\F107B"}.mdi-file-cog-outline::before{content:"\F107C"}.mdi-file-compare::before{content:"\F08AA"}.mdi-file-delimited::before{content:"\F0218"}.mdi-file-delimited-outline::before{content:"\F0EA5"}.mdi-file-document::before{content:"\F0219"}.mdi-file-document-alert::before{content:"\F1A97"}.mdi-file-document-alert-outline::before{content:"\F1A98"}.mdi-file-document-arrow-right::before{content:"\F1C0F"}.mdi-file-document-arrow-right-outline::before{content:"\F1C10"}.mdi-file-document-check::before{content:"\F1A99"}.mdi-file-document-check-outline::before{content:"\F1A9A"}.mdi-file-document-edit::before{content:"\F0DC8"}.mdi-file-document-edit-outline::before{content:"\F0DC9"}.mdi-file-document-minus::before{content:"\F1A9B"}.mdi-file-document-minus-outline::before{content:"\F1A9C"}.mdi-file-document-multiple::before{content:"\F1517"}.mdi-file-document-multiple-outline::before{content:"\F1518"}.mdi-file-document-outline::before{content:"\F09EE"}.mdi-file-document-plus::before{content:"\F1A9D"}.mdi-file-document-plus-outline::before{content:"\F1A9E"}.mdi-file-document-refresh::before{content:"\F1C7A"}.mdi-file-document-refresh-outline::before{content:"\F1C7B"}.mdi-file-document-remove::before{content:"\F1A9F"}.mdi-file-document-remove-outline::before{content:"\F1AA0"}.mdi-file-download::before{content:"\F0965"}.mdi-file-download-outline::before{content:"\F0966"}.mdi-file-edit::before{content:"\F11E7"}.mdi-file-edit-outline::before{content:"\F11E8"}.mdi-file-excel::before{content:"\F021B"}.mdi-file-excel-box::before{content:"\F021C"}.mdi-file-excel-box-outline::before{content:"\F102C"}.mdi-file-excel-outline::before{content:"\F102D"}.mdi-file-export::before{content:"\F021D"}.mdi-file-export-outline::before{content:"\F102E"}.mdi-file-eye::before{content:"\F0DCA"}.mdi-file-eye-outline::before{content:"\F0DCB"}.mdi-file-find::before{content:"\F021E"}.mdi-file-find-outline::before{content:"\F0B97"}.mdi-file-gif-box::before{content:"\F0D78"}.mdi-file-hidden::before{content:"\F0613"}.mdi-file-image::before{content:"\F021F"}.mdi-file-image-marker::before{content:"\F1772"}.mdi-file-image-marker-outline::before{content:"\F1773"}.mdi-file-image-minus::before{content:"\F193B"}.mdi-file-image-minus-outline::before{content:"\F193C"}.mdi-file-image-outline::before{content:"\F0EB0"}.mdi-file-image-plus::before{content:"\F193D"}.mdi-file-image-plus-outline::before{content:"\F193E"}.mdi-file-image-remove::before{content:"\F193F"}.mdi-file-image-remove-outline::before{content:"\F1940"}.mdi-file-import::before{content:"\F0220"}.mdi-file-import-outline::before{content:"\F102F"}.mdi-file-jpg-box::before{content:"\F0225"}.mdi-file-key::before{content:"\F1184"}.mdi-file-key-outline::before{content:"\F1185"}.mdi-file-link::before{content:"\F1177"}.mdi-file-link-outline::before{content:"\F1178"}.mdi-file-lock::before{content:"\F0221"}.mdi-file-lock-open::before{content:"\F19C8"}.mdi-file-lock-open-outline::before{content:"\F19C9"}.mdi-file-lock-outline::before{content:"\F1030"}.mdi-file-marker::before{content:"\F1774"}.mdi-file-marker-outline::before{content:"\F1775"}.mdi-file-minus::before{content:"\F1AA1"}.mdi-file-minus-outline::before{content:"\F1AA2"}.mdi-file-move::before{content:"\F0AB9"}.mdi-file-move-outline::before{content:"\F1031"}.mdi-file-multiple::before{content:"\F0222"}.mdi-file-multiple-outline::before{content:"\F1032"}.mdi-file-music::before{content:"\F0223"}.mdi-file-music-outline::before{content:"\F0E2A"}.mdi-file-outline::before{content:"\F0224"}.mdi-file-pdf-box::before{content:"\F0226"}.mdi-file-percent::before{content:"\F081E"}.mdi-file-percent-outline::before{content:"\F1033"}.mdi-file-phone::before{content:"\F1179"}.mdi-file-phone-outline::before{content:"\F117A"}.mdi-file-plus::before{content:"\F0752"}.mdi-file-plus-outline::before{content:"\F0EED"}.mdi-file-png-box::before{content:"\F0E2D"}.mdi-file-powerpoint::before{content:"\F0227"}.mdi-file-powerpoint-box::before{content:"\F0228"}.mdi-file-powerpoint-box-outline::before{content:"\F1034"}.mdi-file-powerpoint-outline::before{content:"\F1035"}.mdi-file-presentation-box::before{content:"\F0229"}.mdi-file-question::before{content:"\F086F"}.mdi-file-question-outline::before{content:"\F1036"}.mdi-file-refresh::before{content:"\F0918"}.mdi-file-refresh-outline::before{content:"\F0541"}.mdi-file-remove::before{content:"\F0B98"}.mdi-file-remove-outline::before{content:"\F1037"}.mdi-file-replace::before{content:"\F0B32"}.mdi-file-replace-outline::before{content:"\F0B33"}.mdi-file-restore::before{content:"\F0670"}.mdi-file-restore-outline::before{content:"\F1038"}.mdi-file-rotate-left::before{content:"\F1A3B"}.mdi-file-rotate-left-outline::before{content:"\F1A3C"}.mdi-file-rotate-right::before{content:"\F1A3D"}.mdi-file-rotate-right-outline::before{content:"\F1A3E"}.mdi-file-search::before{content:"\F0C7C"}.mdi-file-search-outline::before{content:"\F0C7D"}.mdi-file-send::before{content:"\F022A"}.mdi-file-send-outline::before{content:"\F1039"}.mdi-file-settings::before{content:"\F1079"}.mdi-file-settings-outline::before{content:"\F107A"}.mdi-file-sign::before{content:"\F19C3"}.mdi-file-star::before{content:"\F103A"}.mdi-file-star-four-points::before{content:"\F1C2D"}.mdi-file-star-four-points-outline::before{content:"\F1C2E"}.mdi-file-star-outline::before{content:"\F103B"}.mdi-file-swap::before{content:"\F0FB4"}.mdi-file-swap-outline::before{content:"\F0FB5"}.mdi-file-sync::before{content:"\F1216"}.mdi-file-sync-outline::before{content:"\F1217"}.mdi-file-table::before{content:"\F0C7E"}.mdi-file-table-box::before{content:"\F10E1"}.mdi-file-table-box-multiple::before{content:"\F10E2"}.mdi-file-table-box-multiple-outline::before{content:"\F10E3"}.mdi-file-table-box-outline::before{content:"\F10E4"}.mdi-file-table-outline::before{content:"\F0C7F"}.mdi-file-tree::before{content:"\F0645"}.mdi-file-tree-outline::before{content:"\F13D2"}.mdi-file-undo::before{content:"\F08DC"}.mdi-file-undo-outline::before{content:"\F103C"}.mdi-file-upload::before{content:"\F0A4D"}.mdi-file-upload-outline::before{content:"\F0A4E"}.mdi-file-video::before{content:"\F022B"}.mdi-file-video-outline::before{content:"\F0E2C"}.mdi-file-word::before{content:"\F022C"}.mdi-file-word-box::before{content:"\F022D"}.mdi-file-word-box-outline::before{content:"\F103D"}.mdi-file-word-outline::before{content:"\F103E"}.mdi-file-xml-box::before{content:"\F1B4B"}.mdi-film::before{content:"\F022F"}.mdi-filmstrip::before{content:"\F0230"}.mdi-filmstrip-box::before{content:"\F0332"}.mdi-filmstrip-box-multiple::before{content:"\F0D18"}.mdi-filmstrip-off::before{content:"\F0231"}.mdi-filter::before{content:"\F0232"}.mdi-filter-check::before{content:"\F18EC"}.mdi-filter-check-outline::before{content:"\F18ED"}.mdi-filter-cog::before{content:"\F1AA3"}.mdi-filter-cog-outline::before{content:"\F1AA4"}.mdi-filter-menu::before{content:"\F10E5"}.mdi-filter-menu-outline::before{content:"\F10E6"}.mdi-filter-minus::before{content:"\F0EEE"}.mdi-filter-minus-outline::before{content:"\F0EEF"}.mdi-filter-multiple::before{content:"\F1A3F"}.mdi-filter-multiple-outline::before{content:"\F1A40"}.mdi-filter-off::before{content:"\F14EF"}.mdi-filter-off-outline::before{content:"\F14F0"}.mdi-filter-outline::before{content:"\F0233"}.mdi-filter-plus::before{content:"\F0EF0"}.mdi-filter-plus-outline::before{content:"\F0EF1"}.mdi-filter-remove::before{content:"\F0234"}.mdi-filter-remove-outline::before{content:"\F0235"}.mdi-filter-settings::before{content:"\F1AA5"}.mdi-filter-settings-outline::before{content:"\F1AA6"}.mdi-filter-variant::before{content:"\F0236"}.mdi-filter-variant-minus::before{content:"\F1112"}.mdi-filter-variant-plus::before{content:"\F1113"}.mdi-filter-variant-remove::before{content:"\F103F"}.mdi-finance::before{content:"\F081F"}.mdi-find-replace::before{content:"\F06D4"}.mdi-fingerprint::before{content:"\F0237"}.mdi-fingerprint-off::before{content:"\F0EB1"}.mdi-fire::before{content:"\F0238"}.mdi-fire-alert::before{content:"\F15D7"}.mdi-fire-circle::before{content:"\F1807"}.mdi-fire-extinguisher::before{content:"\F0EF2"}.mdi-fire-hydrant::before{content:"\F1137"}.mdi-fire-hydrant-alert::before{content:"\F1138"}.mdi-fire-hydrant-off::before{content:"\F1139"}.mdi-fire-off::before{content:"\F1722"}.mdi-fire-station::before{content:"\F1CC3"}.mdi-fire-truck::before{content:"\F08AB"}.mdi-firebase::before{content:"\F0967"}.mdi-firefox::before{content:"\F0239"}.mdi-fireplace::before{content:"\F0E2E"}.mdi-fireplace-off::before{content:"\F0E2F"}.mdi-firewire::before{content:"\F05BE"}.mdi-firework::before{content:"\F0E30"}.mdi-firework-off::before{content:"\F1723"}.mdi-fish::before{content:"\F023A"}.mdi-fish-off::before{content:"\F13F3"}.mdi-fishbowl::before{content:"\F0EF3"}.mdi-fishbowl-outline::before{content:"\F0EF4"}.mdi-fit-to-page::before{content:"\F0EF5"}.mdi-fit-to-page-outline::before{content:"\F0EF6"}.mdi-fit-to-screen::before{content:"\F18F4"}.mdi-fit-to-screen-outline::before{content:"\F18F5"}.mdi-flag::before{content:"\F023B"}.mdi-flag-checkered::before{content:"\F023C"}.mdi-flag-minus::before{content:"\F0B99"}.mdi-flag-minus-outline::before{content:"\F10B2"}.mdi-flag-off::before{content:"\F18EE"}.mdi-flag-off-outline::before{content:"\F18EF"}.mdi-flag-outline::before{content:"\F023D"}.mdi-flag-plus::before{content:"\F0B9A"}.mdi-flag-plus-outline::before{content:"\F10B3"}.mdi-flag-remove::before{content:"\F0B9B"}.mdi-flag-remove-outline::before{content:"\F10B4"}.mdi-flag-triangle::before{content:"\F023F"}.mdi-flag-variant::before{content:"\F0240"}.mdi-flag-variant-minus::before{content:"\F1BB4"}.mdi-flag-variant-minus-outline::before{content:"\F1BB5"}.mdi-flag-variant-off::before{content:"\F1BB0"}.mdi-flag-variant-off-outline::before{content:"\F1BB1"}.mdi-flag-variant-outline::before{content:"\F023E"}.mdi-flag-variant-plus::before{content:"\F1BB2"}.mdi-flag-variant-plus-outline::before{content:"\F1BB3"}.mdi-flag-variant-remove::before{content:"\F1BB6"}.mdi-flag-variant-remove-outline::before{content:"\F1BB7"}.mdi-flare::before{content:"\F0D72"}.mdi-flash::before{content:"\F0241"}.mdi-flash-alert::before{content:"\F0EF7"}.mdi-flash-alert-outline::before{content:"\F0EF8"}.mdi-flash-auto::before{content:"\F0242"}.mdi-flash-off::before{content:"\F0243"}.mdi-flash-off-outline::before{content:"\F1B45"}.mdi-flash-outline::before{content:"\F06D5"}.mdi-flash-red-eye::before{content:"\F067B"}.mdi-flash-triangle::before{content:"\F1B1D"}.mdi-flash-triangle-outline::before{content:"\F1B1E"}.mdi-flashlight::before{content:"\F0244"}.mdi-flashlight-off::before{content:"\F0245"}.mdi-flask::before{content:"\F0093"}.mdi-flask-empty::before{content:"\F0094"}.mdi-flask-empty-minus::before{content:"\F123A"}.mdi-flask-empty-minus-outline::before{content:"\F123B"}.mdi-flask-empty-off::before{content:"\F13F4"}.mdi-flask-empty-off-outline::before{content:"\F13F5"}.mdi-flask-empty-outline::before{content:"\F0095"}.mdi-flask-empty-plus::before{content:"\F123C"}.mdi-flask-empty-plus-outline::before{content:"\F123D"}.mdi-flask-empty-remove::before{content:"\F123E"}.mdi-flask-empty-remove-outline::before{content:"\F123F"}.mdi-flask-minus::before{content:"\F1240"}.mdi-flask-minus-outline::before{content:"\F1241"}.mdi-flask-off::before{content:"\F13F6"}.mdi-flask-off-outline::before{content:"\F13F7"}.mdi-flask-outline::before{content:"\F0096"}.mdi-flask-plus::before{content:"\F1242"}.mdi-flask-plus-outline::before{content:"\F1243"}.mdi-flask-remove::before{content:"\F1244"}.mdi-flask-remove-outline::before{content:"\F1245"}.mdi-flask-round-bottom::before{content:"\F124B"}.mdi-flask-round-bottom-empty::before{content:"\F124C"}.mdi-flask-round-bottom-empty-outline::before{content:"\F124D"}.mdi-flask-round-bottom-outline::before{content:"\F124E"}.mdi-fleur-de-lis::before{content:"\F1303"}.mdi-flip-horizontal::before{content:"\F10E7"}.mdi-flip-to-back::before{content:"\F0247"}.mdi-flip-to-front::before{content:"\F0248"}.mdi-flip-vertical::before{content:"\F10E8"}.mdi-floor-lamp::before{content:"\F08DD"}.mdi-floor-lamp-dual::before{content:"\F1040"}.mdi-floor-lamp-dual-outline::before{content:"\F17CE"}.mdi-floor-lamp-outline::before{content:"\F17C8"}.mdi-floor-lamp-torchiere::before{content:"\F1747"}.mdi-floor-lamp-torchiere-outline::before{content:"\F17D6"}.mdi-floor-lamp-torchiere-variant::before{content:"\F1041"}.mdi-floor-lamp-torchiere-variant-outline::before{content:"\F17CF"}.mdi-floor-plan::before{content:"\F0821"}.mdi-floppy::before{content:"\F0249"}.mdi-floppy-variant::before{content:"\F09EF"}.mdi-flower::before{content:"\F024A"}.mdi-flower-outline::before{content:"\F09F0"}.mdi-flower-pollen::before{content:"\F1885"}.mdi-flower-pollen-outline::before{content:"\F1886"}.mdi-flower-poppy::before{content:"\F0D08"}.mdi-flower-tulip::before{content:"\F09F1"}.mdi-flower-tulip-outline::before{content:"\F09F2"}.mdi-focus-auto::before{content:"\F0F4E"}.mdi-focus-field::before{content:"\F0F4F"}.mdi-focus-field-horizontal::before{content:"\F0F50"}.mdi-focus-field-vertical::before{content:"\F0F51"}.mdi-folder::before{content:"\F024B"}.mdi-folder-account::before{content:"\F024C"}.mdi-folder-account-outline::before{content:"\F0B9C"}.mdi-folder-alert::before{content:"\F0DCC"}.mdi-folder-alert-outline::before{content:"\F0DCD"}.mdi-folder-arrow-down::before{content:"\F19E8"}.mdi-folder-arrow-down-outline::before{content:"\F19E9"}.mdi-folder-arrow-left::before{content:"\F19EA"}.mdi-folder-arrow-left-outline::before{content:"\F19EB"}.mdi-folder-arrow-left-right::before{content:"\F19EC"}.mdi-folder-arrow-left-right-outline::before{content:"\F19ED"}.mdi-folder-arrow-right::before{content:"\F19EE"}.mdi-folder-arrow-right-outline::before{content:"\F19EF"}.mdi-folder-arrow-up::before{content:"\F19F0"}.mdi-folder-arrow-up-down::before{content:"\F19F1"}.mdi-folder-arrow-up-down-outline::before{content:"\F19F2"}.mdi-folder-arrow-up-outline::before{content:"\F19F3"}.mdi-folder-cancel::before{content:"\F19F4"}.mdi-folder-cancel-outline::before{content:"\F19F5"}.mdi-folder-check::before{content:"\F197E"}.mdi-folder-check-outline::before{content:"\F197F"}.mdi-folder-clock::before{content:"\F0ABA"}.mdi-folder-clock-outline::before{content:"\F0ABB"}.mdi-folder-cog::before{content:"\F107F"}.mdi-folder-cog-outline::before{content:"\F1080"}.mdi-folder-download::before{content:"\F024D"}.mdi-folder-download-outline::before{content:"\F10E9"}.mdi-folder-edit::before{content:"\F08DE"}.mdi-folder-edit-outline::before{content:"\F0DCE"}.mdi-folder-eye::before{content:"\F178A"}.mdi-folder-eye-outline::before{content:"\F178B"}.mdi-folder-file::before{content:"\F19F6"}.mdi-folder-file-outline::before{content:"\F19F7"}.mdi-folder-google-drive::before{content:"\F024E"}.mdi-folder-heart::before{content:"\F10EA"}.mdi-folder-heart-outline::before{content:"\F10EB"}.mdi-folder-hidden::before{content:"\F179E"}.mdi-folder-home::before{content:"\F10B5"}.mdi-folder-home-outline::before{content:"\F10B6"}.mdi-folder-image::before{content:"\F024F"}.mdi-folder-information::before{content:"\F10B7"}.mdi-folder-information-outline::before{content:"\F10B8"}.mdi-folder-key::before{content:"\F08AC"}.mdi-folder-key-network::before{content:"\F08AD"}.mdi-folder-key-network-outline::before{content:"\F0C80"}.mdi-folder-key-outline::before{content:"\F10EC"}.mdi-folder-lock::before{content:"\F0250"}.mdi-folder-lock-open::before{content:"\F0251"}.mdi-folder-lock-open-outline::before{content:"\F1AA7"}.mdi-folder-lock-outline::before{content:"\F1AA8"}.mdi-folder-marker::before{content:"\F126D"}.mdi-folder-marker-outline::before{content:"\F126E"}.mdi-folder-minus::before{content:"\F1B49"}.mdi-folder-minus-outline::before{content:"\F1B4A"}.mdi-folder-move::before{content:"\F0252"}.mdi-folder-move-outline::before{content:"\F1246"}.mdi-folder-multiple::before{content:"\F0253"}.mdi-folder-multiple-image::before{content:"\F0254"}.mdi-folder-multiple-outline::before{content:"\F0255"}.mdi-folder-multiple-plus::before{content:"\F147E"}.mdi-folder-multiple-plus-outline::before{content:"\F147F"}.mdi-folder-music::before{content:"\F1359"}.mdi-folder-music-outline::before{content:"\F135A"}.mdi-folder-network::before{content:"\F0870"}.mdi-folder-network-outline::before{content:"\F0C81"}.mdi-folder-off::before{content:"\F19F8"}.mdi-folder-off-outline::before{content:"\F19F9"}.mdi-folder-open::before{content:"\F0770"}.mdi-folder-open-outline::before{content:"\F0DCF"}.mdi-folder-outline::before{content:"\F0256"}.mdi-folder-play::before{content:"\F19FA"}.mdi-folder-play-outline::before{content:"\F19FB"}.mdi-folder-plus::before{content:"\F0257"}.mdi-folder-plus-outline::before{content:"\F0B9D"}.mdi-folder-pound::before{content:"\F0D09"}.mdi-folder-pound-outline::before{content:"\F0D0A"}.mdi-folder-question::before{content:"\F19CA"}.mdi-folder-question-outline::before{content:"\F19CB"}.mdi-folder-refresh::before{content:"\F0749"}.mdi-folder-refresh-outline::before{content:"\F0542"}.mdi-folder-remove::before{content:"\F0258"}.mdi-folder-remove-outline::before{content:"\F0B9E"}.mdi-folder-search::before{content:"\F0968"}.mdi-folder-search-outline::before{content:"\F0969"}.mdi-folder-settings::before{content:"\F107D"}.mdi-folder-settings-outline::before{content:"\F107E"}.mdi-folder-star::before{content:"\F069D"}.mdi-folder-star-multiple::before{content:"\F13D3"}.mdi-folder-star-multiple-outline::before{content:"\F13D4"}.mdi-folder-star-outline::before{content:"\F0B9F"}.mdi-folder-swap::before{content:"\F0FB6"}.mdi-folder-swap-outline::before{content:"\F0FB7"}.mdi-folder-sync::before{content:"\F0D0B"}.mdi-folder-sync-outline::before{content:"\F0D0C"}.mdi-folder-table::before{content:"\F12E3"}.mdi-folder-table-outline::before{content:"\F12E4"}.mdi-folder-text::before{content:"\F0C82"}.mdi-folder-text-outline::before{content:"\F0C83"}.mdi-folder-upload::before{content:"\F0259"}.mdi-folder-upload-outline::before{content:"\F10ED"}.mdi-folder-wrench::before{content:"\F19FC"}.mdi-folder-wrench-outline::before{content:"\F19FD"}.mdi-folder-zip::before{content:"\F06EB"}.mdi-folder-zip-outline::before{content:"\F07B9"}.mdi-font-awesome::before{content:"\F003A"}.mdi-food::before{content:"\F025A"}.mdi-food-apple::before{content:"\F025B"}.mdi-food-apple-outline::before{content:"\F0C84"}.mdi-food-croissant::before{content:"\F07C8"}.mdi-food-drumstick::before{content:"\F141F"}.mdi-food-drumstick-off::before{content:"\F1468"}.mdi-food-drumstick-off-outline::before{content:"\F1469"}.mdi-food-drumstick-outline::before{content:"\F1420"}.mdi-food-fork-drink::before{content:"\F05F2"}.mdi-food-halal::before{content:"\F1572"}.mdi-food-hot-dog::before{content:"\F184B"}.mdi-food-kosher::before{content:"\F1573"}.mdi-food-off::before{content:"\F05F3"}.mdi-food-off-outline::before{content:"\F1915"}.mdi-food-outline::before{content:"\F1916"}.mdi-food-steak::before{content:"\F146A"}.mdi-food-steak-off::before{content:"\F146B"}.mdi-food-takeout-box::before{content:"\F1836"}.mdi-food-takeout-box-outline::before{content:"\F1837"}.mdi-food-turkey::before{content:"\F171C"}.mdi-food-variant::before{content:"\F025C"}.mdi-food-variant-off::before{content:"\F13E5"}.mdi-foot-print::before{content:"\F0F52"}.mdi-football::before{content:"\F025D"}.mdi-football-australian::before{content:"\F025E"}.mdi-football-helmet::before{content:"\F025F"}.mdi-forest::before{content:"\F1897"}.mdi-forest-outline::before{content:"\F1C63"}.mdi-forklift::before{content:"\F07C9"}.mdi-form-dropdown::before{content:"\F1400"}.mdi-form-select::before{content:"\F1401"}.mdi-form-textarea::before{content:"\F1095"}.mdi-form-textbox::before{content:"\F060E"}.mdi-form-textbox-lock::before{content:"\F135D"}.mdi-form-textbox-password::before{content:"\F07F5"}.mdi-format-align-bottom::before{content:"\F0753"}.mdi-format-align-center::before{content:"\F0260"}.mdi-format-align-justify::before{content:"\F0261"}.mdi-format-align-left::before{content:"\F0262"}.mdi-format-align-middle::before{content:"\F0754"}.mdi-format-align-right::before{content:"\F0263"}.mdi-format-align-top::before{content:"\F0755"}.mdi-format-annotation-minus::before{content:"\F0ABC"}.mdi-format-annotation-plus::before{content:"\F0646"}.mdi-format-bold::before{content:"\F0264"}.mdi-format-clear::before{content:"\F0265"}.mdi-format-color-fill::before{content:"\F0266"}.mdi-format-color-highlight::before{content:"\F0E31"}.mdi-format-color-marker-cancel::before{content:"\F1313"}.mdi-format-color-text::before{content:"\F069E"}.mdi-format-columns::before{content:"\F08DF"}.mdi-format-float-center::before{content:"\F0267"}.mdi-format-float-left::before{content:"\F0268"}.mdi-format-float-none::before{content:"\F0269"}.mdi-format-float-right::before{content:"\F026A"}.mdi-format-font::before{content:"\F06D6"}.mdi-format-font-size-decrease::before{content:"\F09F3"}.mdi-format-font-size-increase::before{content:"\F09F4"}.mdi-format-header-1::before{content:"\F026B"}.mdi-format-header-2::before{content:"\F026C"}.mdi-format-header-3::before{content:"\F026D"}.mdi-format-header-4::before{content:"\F026E"}.mdi-format-header-5::before{content:"\F026F"}.mdi-format-header-6::before{content:"\F0270"}.mdi-format-header-decrease::before{content:"\F0271"}.mdi-format-header-equal::before{content:"\F0272"}.mdi-format-header-increase::before{content:"\F0273"}.mdi-format-header-pound::before{content:"\F0274"}.mdi-format-horizontal-align-center::before{content:"\F061E"}.mdi-format-horizontal-align-left::before{content:"\F061F"}.mdi-format-horizontal-align-right::before{content:"\F0620"}.mdi-format-indent-decrease::before{content:"\F0275"}.mdi-format-indent-increase::before{content:"\F0276"}.mdi-format-italic::before{content:"\F0277"}.mdi-format-letter-case::before{content:"\F0B34"}.mdi-format-letter-case-lower::before{content:"\F0B35"}.mdi-format-letter-case-upper::before{content:"\F0B36"}.mdi-format-letter-ends-with::before{content:"\F0FB8"}.mdi-format-letter-matches::before{content:"\F0FB9"}.mdi-format-letter-spacing::before{content:"\F1956"}.mdi-format-letter-spacing-variant::before{content:"\F1AFB"}.mdi-format-letter-starts-with::before{content:"\F0FBA"}.mdi-format-line-height::before{content:"\F1AFC"}.mdi-format-line-spacing::before{content:"\F0278"}.mdi-format-line-style::before{content:"\F05C8"}.mdi-format-line-weight::before{content:"\F05C9"}.mdi-format-list-bulleted::before{content:"\F0279"}.mdi-format-list-bulleted-square::before{content:"\F0DD0"}.mdi-format-list-bulleted-triangle::before{content:"\F0EB2"}.mdi-format-list-bulleted-type::before{content:"\F027A"}.mdi-format-list-checkbox::before{content:"\F096A"}.mdi-format-list-checks::before{content:"\F0756"}.mdi-format-list-group::before{content:"\F1860"}.mdi-format-list-group-plus::before{content:"\F1B56"}.mdi-format-list-numbered::before{content:"\F027B"}.mdi-format-list-numbered-rtl::before{content:"\F0D0D"}.mdi-format-list-text::before{content:"\F126F"}.mdi-format-overline::before{content:"\F0EB3"}.mdi-format-page-break::before{content:"\F06D7"}.mdi-format-page-split::before{content:"\F1917"}.mdi-format-paint::before{content:"\F027C"}.mdi-format-paragraph::before{content:"\F027D"}.mdi-format-paragraph-spacing::before{content:"\F1AFD"}.mdi-format-pilcrow::before{content:"\F06D8"}.mdi-format-pilcrow-arrow-left::before{content:"\F0286"}.mdi-format-pilcrow-arrow-right::before{content:"\F0285"}.mdi-format-quote-close::before{content:"\F027E"}.mdi-format-quote-close-outline::before{content:"\F11A8"}.mdi-format-quote-open::before{content:"\F0757"}.mdi-format-quote-open-outline::before{content:"\F11A7"}.mdi-format-rotate-90::before{content:"\F06AA"}.mdi-format-section::before{content:"\F069F"}.mdi-format-size::before{content:"\F027F"}.mdi-format-strikethrough::before{content:"\F0280"}.mdi-format-strikethrough-variant::before{content:"\F0281"}.mdi-format-subscript::before{content:"\F0282"}.mdi-format-superscript::before{content:"\F0283"}.mdi-format-text::before{content:"\F0284"}.mdi-format-text-rotation-angle-down::before{content:"\F0FBB"}.mdi-format-text-rotation-angle-up::before{content:"\F0FBC"}.mdi-format-text-rotation-down::before{content:"\F0D73"}.mdi-format-text-rotation-down-vertical::before{content:"\F0FBD"}.mdi-format-text-rotation-none::before{content:"\F0D74"}.mdi-format-text-rotation-up::before{content:"\F0FBE"}.mdi-format-text-rotation-vertical::before{content:"\F0FBF"}.mdi-format-text-variant::before{content:"\F0E32"}.mdi-format-text-variant-outline::before{content:"\F150F"}.mdi-format-text-wrapping-clip::before{content:"\F0D0E"}.mdi-format-text-wrapping-overflow::before{content:"\F0D0F"}.mdi-format-text-wrapping-wrap::before{content:"\F0D10"}.mdi-format-textbox::before{content:"\F0D11"}.mdi-format-title::before{content:"\F05F4"}.mdi-format-underline::before{content:"\F0287"}.mdi-format-underline-wavy::before{content:"\F18E9"}.mdi-format-vertical-align-bottom::before{content:"\F0621"}.mdi-format-vertical-align-center::before{content:"\F0622"}.mdi-format-vertical-align-top::before{content:"\F0623"}.mdi-format-wrap-inline::before{content:"\F0288"}.mdi-format-wrap-square::before{content:"\F0289"}.mdi-format-wrap-tight::before{content:"\F028A"}.mdi-format-wrap-top-bottom::before{content:"\F028B"}.mdi-forum::before{content:"\F028C"}.mdi-forum-minus::before{content:"\F1AA9"}.mdi-forum-minus-outline::before{content:"\F1AAA"}.mdi-forum-outline::before{content:"\F0822"}.mdi-forum-plus::before{content:"\F1AAB"}.mdi-forum-plus-outline::before{content:"\F1AAC"}.mdi-forum-remove::before{content:"\F1AAD"}.mdi-forum-remove-outline::before{content:"\F1AAE"}.mdi-forward::before{content:"\F028D"}.mdi-forwardburger::before{content:"\F0D75"}.mdi-fountain::before{content:"\F096B"}.mdi-fountain-pen::before{content:"\F0D12"}.mdi-fountain-pen-tip::before{content:"\F0D13"}.mdi-fraction-one-half::before{content:"\F1992"}.mdi-freebsd::before{content:"\F08E0"}.mdi-french-fries::before{content:"\F1957"}.mdi-frequently-asked-questions::before{content:"\F0EB4"}.mdi-fridge::before{content:"\F0290"}.mdi-fridge-alert::before{content:"\F11B1"}.mdi-fridge-alert-outline::before{content:"\F11B2"}.mdi-fridge-bottom::before{content:"\F0292"}.mdi-fridge-industrial::before{content:"\F15EE"}.mdi-fridge-industrial-alert::before{content:"\F15EF"}.mdi-fridge-industrial-alert-outline::before{content:"\F15F0"}.mdi-fridge-industrial-off::before{content:"\F15F1"}.mdi-fridge-industrial-off-outline::before{content:"\F15F2"}.mdi-fridge-industrial-outline::before{content:"\F15F3"}.mdi-fridge-off::before{content:"\F11AF"}.mdi-fridge-off-outline::before{content:"\F11B0"}.mdi-fridge-outline::before{content:"\F028F"}.mdi-fridge-top::before{content:"\F0291"}.mdi-fridge-variant::before{content:"\F15F4"}.mdi-fridge-variant-alert::before{content:"\F15F5"}.mdi-fridge-variant-alert-outline::before{content:"\F15F6"}.mdi-fridge-variant-off::before{content:"\F15F7"}.mdi-fridge-variant-off-outline::before{content:"\F15F8"}.mdi-fridge-variant-outline::before{content:"\F15F9"}.mdi-fruit-cherries::before{content:"\F1042"}.mdi-fruit-cherries-off::before{content:"\F13F8"}.mdi-fruit-citrus::before{content:"\F1043"}.mdi-fruit-citrus-off::before{content:"\F13F9"}.mdi-fruit-grapes::before{content:"\F1044"}.mdi-fruit-grapes-outline::before{content:"\F1045"}.mdi-fruit-pear::before{content:"\F1A0E"}.mdi-fruit-pineapple::before{content:"\F1046"}.mdi-fruit-watermelon::before{content:"\F1047"}.mdi-fuel::before{content:"\F07CA"}.mdi-fuel-cell::before{content:"\F18B5"}.mdi-fullscreen::before{content:"\F0293"}.mdi-fullscreen-exit::before{content:"\F0294"}.mdi-function::before{content:"\F0295"}.mdi-function-variant::before{content:"\F0871"}.mdi-furigana-horizontal::before{content:"\F1081"}.mdi-furigana-vertical::before{content:"\F1082"}.mdi-fuse::before{content:"\F0C85"}.mdi-fuse-alert::before{content:"\F142D"}.mdi-fuse-blade::before{content:"\F0C86"}.mdi-fuse-off::before{content:"\F142C"}.mdi-gamepad::before{content:"\F0296"}.mdi-gamepad-circle::before{content:"\F0E33"}.mdi-gamepad-circle-down::before{content:"\F0E34"}.mdi-gamepad-circle-left::before{content:"\F0E35"}.mdi-gamepad-circle-outline::before{content:"\F0E36"}.mdi-gamepad-circle-right::before{content:"\F0E37"}.mdi-gamepad-circle-up::before{content:"\F0E38"}.mdi-gamepad-down::before{content:"\F0E39"}.mdi-gamepad-left::before{content:"\F0E3A"}.mdi-gamepad-outline::before{content:"\F1919"}.mdi-gamepad-right::before{content:"\F0E3B"}.mdi-gamepad-round::before{content:"\F0E3C"}.mdi-gamepad-round-down::before{content:"\F0E3D"}.mdi-gamepad-round-left::before{content:"\F0E3E"}.mdi-gamepad-round-outline::before{content:"\F0E3F"}.mdi-gamepad-round-right::before{content:"\F0E40"}.mdi-gamepad-round-up::before{content:"\F0E41"}.mdi-gamepad-square::before{content:"\F0EB5"}.mdi-gamepad-square-outline::before{content:"\F0EB6"}.mdi-gamepad-up::before{content:"\F0E42"}.mdi-gamepad-variant::before{content:"\F0297"}.mdi-gamepad-variant-outline::before{content:"\F0EB7"}.mdi-gamma::before{content:"\F10EE"}.mdi-gantry-crane::before{content:"\F0DD1"}.mdi-garage::before{content:"\F06D9"}.mdi-garage-alert::before{content:"\F0872"}.mdi-garage-alert-variant::before{content:"\F12D5"}.mdi-garage-lock::before{content:"\F17FB"}.mdi-garage-open::before{content:"\F06DA"}.mdi-garage-open-variant::before{content:"\F12D4"}.mdi-garage-variant::before{content:"\F12D3"}.mdi-garage-variant-lock::before{content:"\F17FC"}.mdi-gas-burner::before{content:"\F1A1B"}.mdi-gas-cylinder::before{content:"\F0647"}.mdi-gas-station::before{content:"\F0298"}.mdi-gas-station-in-use::before{content:"\F1CC4"}.mdi-gas-station-in-use-outline::before{content:"\F1CC5"}.mdi-gas-station-off::before{content:"\F1409"}.mdi-gas-station-off-outline::before{content:"\F140A"}.mdi-gas-station-outline::before{content:"\F0EB8"}.mdi-gate::before{content:"\F0299"}.mdi-gate-alert::before{content:"\F17F8"}.mdi-gate-and::before{content:"\F08E1"}.mdi-gate-arrow-left::before{content:"\F17F7"}.mdi-gate-arrow-right::before{content:"\F1169"}.mdi-gate-buffer::before{content:"\F1AFE"}.mdi-gate-nand::before{content:"\F08E2"}.mdi-gate-nor::before{content:"\F08E3"}.mdi-gate-not::before{content:"\F08E4"}.mdi-gate-open::before{content:"\F116A"}.mdi-gate-or::before{content:"\F08E5"}.mdi-gate-xnor::before{content:"\F08E6"}.mdi-gate-xor::before{content:"\F08E7"}.mdi-gatsby::before{content:"\F0E43"}.mdi-gauge::before{content:"\F029A"}.mdi-gauge-empty::before{content:"\F0873"}.mdi-gauge-full::before{content:"\F0874"}.mdi-gauge-low::before{content:"\F0875"}.mdi-gavel::before{content:"\F029B"}.mdi-gender-female::before{content:"\F029C"}.mdi-gender-male::before{content:"\F029D"}.mdi-gender-male-female::before{content:"\F029E"}.mdi-gender-male-female-variant::before{content:"\F113F"}.mdi-gender-non-binary::before{content:"\F1140"}.mdi-gender-transgender::before{content:"\F029F"}.mdi-generator-mobile::before{content:"\F1C8A"}.mdi-generator-portable::before{content:"\F1C8B"}.mdi-generator-stationary::before{content:"\F1C8C"}.mdi-gentoo::before{content:"\F08E8"}.mdi-gesture::before{content:"\F07CB"}.mdi-gesture-double-tap::before{content:"\F073C"}.mdi-gesture-pinch::before{content:"\F0ABD"}.mdi-gesture-spread::before{content:"\F0ABE"}.mdi-gesture-swipe::before{content:"\F0D76"}.mdi-gesture-swipe-down::before{content:"\F073D"}.mdi-gesture-swipe-horizontal::before{content:"\F0ABF"}.mdi-gesture-swipe-left::before{content:"\F073E"}.mdi-gesture-swipe-right::before{content:"\F073F"}.mdi-gesture-swipe-up::before{content:"\F0740"}.mdi-gesture-swipe-vertical::before{content:"\F0AC0"}.mdi-gesture-tap::before{content:"\F0741"}.mdi-gesture-tap-box::before{content:"\F12A9"}.mdi-gesture-tap-button::before{content:"\F12A8"}.mdi-gesture-tap-hold::before{content:"\F0D77"}.mdi-gesture-two-double-tap::before{content:"\F0742"}.mdi-gesture-two-tap::before{content:"\F0743"}.mdi-ghost::before{content:"\F02A0"}.mdi-ghost-off::before{content:"\F09F5"}.mdi-ghost-off-outline::before{content:"\F165C"}.mdi-ghost-outline::before{content:"\F165D"}.mdi-gift::before{content:"\F0E44"}.mdi-gift-off::before{content:"\F16EF"}.mdi-gift-off-outline::before{content:"\F16F0"}.mdi-gift-open::before{content:"\F16F1"}.mdi-gift-open-outline::before{content:"\F16F2"}.mdi-gift-outline::before{content:"\F02A1"}.mdi-git::before{content:"\F02A2"}.mdi-github::before{content:"\F02A4"}.mdi-gitlab::before{content:"\F0BA0"}.mdi-glass-cocktail::before{content:"\F0356"}.mdi-glass-cocktail-off::before{content:"\F15E6"}.mdi-glass-flute::before{content:"\F02A5"}.mdi-glass-fragile::before{content:"\F1873"}.mdi-glass-mug::before{content:"\F02A6"}.mdi-glass-mug-off::before{content:"\F15E7"}.mdi-glass-mug-variant::before{content:"\F1116"}.mdi-glass-mug-variant-off::before{content:"\F15E8"}.mdi-glass-pint-outline::before{content:"\F130D"}.mdi-glass-stange::before{content:"\F02A7"}.mdi-glass-tulip::before{content:"\F02A8"}.mdi-glass-wine::before{content:"\F0876"}.mdi-glasses::before{content:"\F02AA"}.mdi-globe-light::before{content:"\F066F"}.mdi-globe-light-outline::before{content:"\F12D7"}.mdi-globe-model::before{content:"\F08E9"}.mdi-gmail::before{content:"\F02AB"}.mdi-gnome::before{content:"\F02AC"}.mdi-go-kart::before{content:"\F0D79"}.mdi-go-kart-track::before{content:"\F0D7A"}.mdi-gog::before{content:"\F0BA1"}.mdi-gold::before{content:"\F124F"}.mdi-golf::before{content:"\F0823"}.mdi-golf-cart::before{content:"\F11A4"}.mdi-golf-tee::before{content:"\F1083"}.mdi-gondola::before{content:"\F0686"}.mdi-goodreads::before{content:"\F0D7B"}.mdi-google::before{content:"\F02AD"}.mdi-google-ads::before{content:"\F0C87"}.mdi-google-analytics::before{content:"\F07CC"}.mdi-google-assistant::before{content:"\F07CD"}.mdi-google-cardboard::before{content:"\F02AE"}.mdi-google-chrome::before{content:"\F02AF"}.mdi-google-circles::before{content:"\F02B0"}.mdi-google-circles-communities::before{content:"\F02B1"}.mdi-google-circles-extended::before{content:"\F02B2"}.mdi-google-circles-group::before{content:"\F02B3"}.mdi-google-classroom::before{content:"\F02C0"}.mdi-google-cloud::before{content:"\F11F6"}.mdi-google-downasaur::before{content:"\F1362"}.mdi-google-drive::before{content:"\F02B6"}.mdi-google-earth::before{content:"\F02B7"}.mdi-google-fit::before{content:"\F096C"}.mdi-google-glass::before{content:"\F02B8"}.mdi-google-hangouts::before{content:"\F02C9"}.mdi-google-keep::before{content:"\F06DC"}.mdi-google-lens::before{content:"\F09F6"}.mdi-google-maps::before{content:"\F05F5"}.mdi-google-my-business::before{content:"\F1048"}.mdi-google-nearby::before{content:"\F02B9"}.mdi-google-play::before{content:"\F02BC"}.mdi-google-plus::before{content:"\F02BD"}.mdi-google-podcast::before{content:"\F0EB9"}.mdi-google-spreadsheet::before{content:"\F09F7"}.mdi-google-street-view::before{content:"\F0C88"}.mdi-google-translate::before{content:"\F02BF"}.mdi-gradient-horizontal::before{content:"\F174A"}.mdi-gradient-vertical::before{content:"\F06A0"}.mdi-grain::before{content:"\F0D7C"}.mdi-graph::before{content:"\F1049"}.mdi-graph-outline::before{content:"\F104A"}.mdi-graphql::before{content:"\F0877"}.mdi-grass::before{content:"\F1510"}.mdi-grave-stone::before{content:"\F0BA2"}.mdi-grease-pencil::before{content:"\F0648"}.mdi-greater-than::before{content:"\F096D"}.mdi-greater-than-or-equal::before{content:"\F096E"}.mdi-greenhouse::before{content:"\F002D"}.mdi-grid::before{content:"\F02C1"}.mdi-grid-large::before{content:"\F0758"}.mdi-grid-off::before{content:"\F02C2"}.mdi-grill::before{content:"\F0E45"}.mdi-grill-outline::before{content:"\F118A"}.mdi-group::before{content:"\F02C3"}.mdi-guitar-acoustic::before{content:"\F0771"}.mdi-guitar-electric::before{content:"\F02C4"}.mdi-guitar-pick::before{content:"\F02C5"}.mdi-guitar-pick-outline::before{content:"\F02C6"}.mdi-guy-fawkes-mask::before{content:"\F0825"}.mdi-gymnastics::before{content:"\F1A41"}.mdi-hail::before{content:"\F0AC1"}.mdi-hair-dryer::before{content:"\F10EF"}.mdi-hair-dryer-outline::before{content:"\F10F0"}.mdi-halloween::before{content:"\F0BA3"}.mdi-hamburger::before{content:"\F0685"}.mdi-hamburger-check::before{content:"\F1776"}.mdi-hamburger-minus::before{content:"\F1777"}.mdi-hamburger-off::before{content:"\F1778"}.mdi-hamburger-plus::before{content:"\F1779"}.mdi-hamburger-remove::before{content:"\F177A"}.mdi-hammer::before{content:"\F08EA"}.mdi-hammer-screwdriver::before{content:"\F1322"}.mdi-hammer-sickle::before{content:"\F1887"}.mdi-hammer-wrench::before{content:"\F1323"}.mdi-hand-back-left::before{content:"\F0E46"}.mdi-hand-back-left-off::before{content:"\F1830"}.mdi-hand-back-left-off-outline::before{content:"\F1832"}.mdi-hand-back-left-outline::before{content:"\F182C"}.mdi-hand-back-right::before{content:"\F0E47"}.mdi-hand-back-right-off::before{content:"\F1831"}.mdi-hand-back-right-off-outline::before{content:"\F1833"}.mdi-hand-back-right-outline::before{content:"\F182D"}.mdi-hand-clap::before{content:"\F194B"}.mdi-hand-clap-off::before{content:"\F1A42"}.mdi-hand-coin::before{content:"\F188F"}.mdi-hand-coin-outline::before{content:"\F1890"}.mdi-hand-cycle::before{content:"\F1B9C"}.mdi-hand-extended::before{content:"\F18B6"}.mdi-hand-extended-outline::before{content:"\F18B7"}.mdi-hand-front-left::before{content:"\F182B"}.mdi-hand-front-left-outline::before{content:"\F182E"}.mdi-hand-front-right::before{content:"\F0A4F"}.mdi-hand-front-right-outline::before{content:"\F182F"}.mdi-hand-heart::before{content:"\F10F1"}.mdi-hand-heart-outline::before{content:"\F157E"}.mdi-hand-okay::before{content:"\F0A50"}.mdi-hand-peace::before{content:"\F0A51"}.mdi-hand-peace-variant::before{content:"\F0A52"}.mdi-hand-pointing-down::before{content:"\F0A53"}.mdi-hand-pointing-left::before{content:"\F0A54"}.mdi-hand-pointing-right::before{content:"\F02C7"}.mdi-hand-pointing-up::before{content:"\F0A55"}.mdi-hand-saw::before{content:"\F0E48"}.mdi-hand-wash::before{content:"\F157F"}.mdi-hand-wash-outline::before{content:"\F1580"}.mdi-hand-water::before{content:"\F139F"}.mdi-hand-wave::before{content:"\F1821"}.mdi-hand-wave-outline::before{content:"\F1822"}.mdi-handball::before{content:"\F0F53"}.mdi-handcuffs::before{content:"\F113E"}.mdi-hands-pray::before{content:"\F0579"}.mdi-handshake::before{content:"\F1218"}.mdi-handshake-outline::before{content:"\F15A1"}.mdi-hanger::before{content:"\F02C8"}.mdi-hard-hat::before{content:"\F096F"}.mdi-harddisk::before{content:"\F02CA"}.mdi-harddisk-plus::before{content:"\F104B"}.mdi-harddisk-remove::before{content:"\F104C"}.mdi-hat-fedora::before{content:"\F0BA4"}.mdi-hazard-lights::before{content:"\F0C89"}.mdi-hdmi-port::before{content:"\F1BB8"}.mdi-hdr::before{content:"\F0D7D"}.mdi-hdr-off::before{content:"\F0D7E"}.mdi-head::before{content:"\F135E"}.mdi-head-alert::before{content:"\F1338"}.mdi-head-alert-outline::before{content:"\F1339"}.mdi-head-check::before{content:"\F133A"}.mdi-head-check-outline::before{content:"\F133B"}.mdi-head-cog::before{content:"\F133C"}.mdi-head-cog-outline::before{content:"\F133D"}.mdi-head-dots-horizontal::before{content:"\F133E"}.mdi-head-dots-horizontal-outline::before{content:"\F133F"}.mdi-head-flash::before{content:"\F1340"}.mdi-head-flash-outline::before{content:"\F1341"}.mdi-head-heart::before{content:"\F1342"}.mdi-head-heart-outline::before{content:"\F1343"}.mdi-head-lightbulb::before{content:"\F1344"}.mdi-head-lightbulb-outline::before{content:"\F1345"}.mdi-head-minus::before{content:"\F1346"}.mdi-head-minus-outline::before{content:"\F1347"}.mdi-head-outline::before{content:"\F135F"}.mdi-head-plus::before{content:"\F1348"}.mdi-head-plus-outline::before{content:"\F1349"}.mdi-head-question::before{content:"\F134A"}.mdi-head-question-outline::before{content:"\F134B"}.mdi-head-remove::before{content:"\F134C"}.mdi-head-remove-outline::before{content:"\F134D"}.mdi-head-snowflake::before{content:"\F134E"}.mdi-head-snowflake-outline::before{content:"\F134F"}.mdi-head-sync::before{content:"\F1350"}.mdi-head-sync-outline::before{content:"\F1351"}.mdi-headphones::before{content:"\F02CB"}.mdi-headphones-bluetooth::before{content:"\F0970"}.mdi-headphones-box::before{content:"\F02CC"}.mdi-headphones-off::before{content:"\F07CE"}.mdi-headphones-settings::before{content:"\F02CD"}.mdi-headset::before{content:"\F02CE"}.mdi-headset-dock::before{content:"\F02CF"}.mdi-headset-off::before{content:"\F02D0"}.mdi-heart::before{content:"\F02D1"}.mdi-heart-box::before{content:"\F02D2"}.mdi-heart-box-outline::before{content:"\F02D3"}.mdi-heart-broken::before{content:"\F02D4"}.mdi-heart-broken-outline::before{content:"\F0D14"}.mdi-heart-circle::before{content:"\F0971"}.mdi-heart-circle-outline::before{content:"\F0972"}.mdi-heart-cog::before{content:"\F1663"}.mdi-heart-cog-outline::before{content:"\F1664"}.mdi-heart-flash::before{content:"\F0EF9"}.mdi-heart-half::before{content:"\F06DF"}.mdi-heart-half-full::before{content:"\F06DE"}.mdi-heart-half-outline::before{content:"\F06E0"}.mdi-heart-minus::before{content:"\F142F"}.mdi-heart-minus-outline::before{content:"\F1432"}.mdi-heart-multiple::before{content:"\F0A56"}.mdi-heart-multiple-outline::before{content:"\F0A57"}.mdi-heart-off::before{content:"\F0759"}.mdi-heart-off-outline::before{content:"\F1434"}.mdi-heart-outline::before{content:"\F02D5"}.mdi-heart-plus::before{content:"\F142E"}.mdi-heart-plus-outline::before{content:"\F1431"}.mdi-heart-pulse::before{content:"\F05F6"}.mdi-heart-remove::before{content:"\F1430"}.mdi-heart-remove-outline::before{content:"\F1433"}.mdi-heart-search::before{content:"\F1C8D"}.mdi-heart-settings::before{content:"\F1665"}.mdi-heart-settings-outline::before{content:"\F1666"}.mdi-heat-pump::before{content:"\F1A43"}.mdi-heat-pump-outline::before{content:"\F1A44"}.mdi-heat-wave::before{content:"\F1A45"}.mdi-heating-coil::before{content:"\F1AAF"}.mdi-helicopter::before{content:"\F0AC2"}.mdi-help::before{content:"\F02D6"}.mdi-help-box::before{content:"\F078B"}.mdi-help-box-multiple::before{content:"\F1C0A"}.mdi-help-box-multiple-outline::before{content:"\F1C0B"}.mdi-help-box-outline::before{content:"\F1C0C"}.mdi-help-circle::before{content:"\F02D7"}.mdi-help-circle-outline::before{content:"\F0625"}.mdi-help-network::before{content:"\F06F5"}.mdi-help-network-outline::before{content:"\F0C8A"}.mdi-help-rhombus::before{content:"\F0BA5"}.mdi-help-rhombus-outline::before{content:"\F0BA6"}.mdi-hexadecimal::before{content:"\F12A7"}.mdi-hexagon::before{content:"\F02D8"}.mdi-hexagon-multiple::before{content:"\F06E1"}.mdi-hexagon-multiple-outline::before{content:"\F10F2"}.mdi-hexagon-outline::before{content:"\F02D9"}.mdi-hexagon-slice-1::before{content:"\F0AC3"}.mdi-hexagon-slice-2::before{content:"\F0AC4"}.mdi-hexagon-slice-3::before{content:"\F0AC5"}.mdi-hexagon-slice-4::before{content:"\F0AC6"}.mdi-hexagon-slice-5::before{content:"\F0AC7"}.mdi-hexagon-slice-6::before{content:"\F0AC8"}.mdi-hexagram::before{content:"\F0AC9"}.mdi-hexagram-outline::before{content:"\F0ACA"}.mdi-high-definition::before{content:"\F07CF"}.mdi-high-definition-box::before{content:"\F0878"}.mdi-highway::before{content:"\F05F7"}.mdi-hiking::before{content:"\F0D7F"}.mdi-history::before{content:"\F02DA"}.mdi-hockey-puck::before{content:"\F0879"}.mdi-hockey-sticks::before{content:"\F087A"}.mdi-hololens::before{content:"\F02DB"}.mdi-home::before{content:"\F02DC"}.mdi-home-account::before{content:"\F0826"}.mdi-home-alert::before{content:"\F087B"}.mdi-home-alert-outline::before{content:"\F15D0"}.mdi-home-analytics::before{content:"\F0EBA"}.mdi-home-assistant::before{content:"\F07D0"}.mdi-home-automation::before{content:"\F07D1"}.mdi-home-battery::before{content:"\F1901"}.mdi-home-battery-outline::before{content:"\F1902"}.mdi-home-circle::before{content:"\F07D2"}.mdi-home-circle-outline::before{content:"\F104D"}.mdi-home-city::before{content:"\F0D15"}.mdi-home-city-outline::before{content:"\F0D16"}.mdi-home-clock::before{content:"\F1A12"}.mdi-home-clock-outline::before{content:"\F1A13"}.mdi-home-edit::before{content:"\F1159"}.mdi-home-edit-outline::before{content:"\F115A"}.mdi-home-export-outline::before{content:"\F0F9B"}.mdi-home-flood::before{content:"\F0EFA"}.mdi-home-floor-0::before{content:"\F0DD2"}.mdi-home-floor-1::before{content:"\F0D80"}.mdi-home-floor-2::before{content:"\F0D81"}.mdi-home-floor-3::before{content:"\F0D82"}.mdi-home-floor-a::before{content:"\F0D83"}.mdi-home-floor-b::before{content:"\F0D84"}.mdi-home-floor-g::before{content:"\F0D85"}.mdi-home-floor-l::before{content:"\F0D86"}.mdi-home-floor-negative-1::before{content:"\F0DD3"}.mdi-home-group::before{content:"\F0DD4"}.mdi-home-group-minus::before{content:"\F19C1"}.mdi-home-group-plus::before{content:"\F19C0"}.mdi-home-group-remove::before{content:"\F19C2"}.mdi-home-heart::before{content:"\F0827"}.mdi-home-import-outline::before{content:"\F0F9C"}.mdi-home-lightbulb::before{content:"\F1251"}.mdi-home-lightbulb-outline::before{content:"\F1252"}.mdi-home-lightning-bolt::before{content:"\F1903"}.mdi-home-lightning-bolt-outline::before{content:"\F1904"}.mdi-home-lock::before{content:"\F08EB"}.mdi-home-lock-open::before{content:"\F08EC"}.mdi-home-map-marker::before{content:"\F05F8"}.mdi-home-minus::before{content:"\F0974"}.mdi-home-minus-outline::before{content:"\F13D5"}.mdi-home-modern::before{content:"\F02DD"}.mdi-home-off::before{content:"\F1A46"}.mdi-home-off-outline::before{content:"\F1A47"}.mdi-home-outline::before{content:"\F06A1"}.mdi-home-percent::before{content:"\F1C7C"}.mdi-home-percent-outline::before{content:"\F1C7D"}.mdi-home-plus::before{content:"\F0975"}.mdi-home-plus-outline::before{content:"\F13D6"}.mdi-home-remove::before{content:"\F1247"}.mdi-home-remove-outline::before{content:"\F13D7"}.mdi-home-roof::before{content:"\F112B"}.mdi-home-search::before{content:"\F13B0"}.mdi-home-search-outline::before{content:"\F13B1"}.mdi-home-silo::before{content:"\F1BA0"}.mdi-home-silo-outline::before{content:"\F1BA1"}.mdi-home-sound-in::before{content:"\F1C2F"}.mdi-home-sound-in-outline::before{content:"\F1C30"}.mdi-home-sound-out::before{content:"\F1C31"}.mdi-home-sound-out-outline::before{content:"\F1C32"}.mdi-home-switch::before{content:"\F1794"}.mdi-home-switch-outline::before{content:"\F1795"}.mdi-home-thermometer::before{content:"\F0F54"}.mdi-home-thermometer-outline::before{content:"\F0F55"}.mdi-home-variant::before{content:"\F02DE"}.mdi-home-variant-outline::before{content:"\F0BA7"}.mdi-hook::before{content:"\F06E2"}.mdi-hook-off::before{content:"\F06E3"}.mdi-hoop-house::before{content:"\F0E56"}.mdi-hops::before{content:"\F02DF"}.mdi-horizontal-rotate-clockwise::before{content:"\F10F3"}.mdi-horizontal-rotate-counterclockwise::before{content:"\F10F4"}.mdi-horse::before{content:"\F15BF"}.mdi-horse-human::before{content:"\F15C0"}.mdi-horse-variant::before{content:"\F15C1"}.mdi-horse-variant-fast::before{content:"\F186E"}.mdi-horseshoe::before{content:"\F0A58"}.mdi-hospital::before{content:"\F0FF6"}.mdi-hospital-box::before{content:"\F02E0"}.mdi-hospital-box-outline::before{content:"\F0FF7"}.mdi-hospital-building::before{content:"\F02E1"}.mdi-hospital-marker::before{content:"\F02E2"}.mdi-hot-tub::before{content:"\F0828"}.mdi-hours-12::before{content:"\F1C94"}.mdi-hours-24::before{content:"\F1478"}.mdi-hub::before{content:"\F1C95"}.mdi-hub-outline::before{content:"\F1C96"}.mdi-hubspot::before{content:"\F0D17"}.mdi-hulu::before{content:"\F0829"}.mdi-human::before{content:"\F02E6"}.mdi-human-baby-changing-table::before{content:"\F138B"}.mdi-human-cane::before{content:"\F1581"}.mdi-human-capacity-decrease::before{content:"\F159B"}.mdi-human-capacity-increase::before{content:"\F159C"}.mdi-human-child::before{content:"\F02E7"}.mdi-human-dolly::before{content:"\F1980"}.mdi-human-edit::before{content:"\F14E8"}.mdi-human-female::before{content:"\F0649"}.mdi-human-female-boy::before{content:"\F0A59"}.mdi-human-female-dance::before{content:"\F15C9"}.mdi-human-female-female::before{content:"\F0A5A"}.mdi-human-female-female-child::before{content:"\F1C8E"}.mdi-human-female-girl::before{content:"\F0A5B"}.mdi-human-greeting::before{content:"\F17C4"}.mdi-human-greeting-proximity::before{content:"\F159D"}.mdi-human-greeting-variant::before{content:"\F064A"}.mdi-human-handsdown::before{content:"\F064B"}.mdi-human-handsup::before{content:"\F064C"}.mdi-human-male::before{content:"\F064D"}.mdi-human-male-board::before{content:"\F0890"}.mdi-human-male-board-poll::before{content:"\F0846"}.mdi-human-male-boy::before{content:"\F0A5C"}.mdi-human-male-child::before{content:"\F138C"}.mdi-human-male-female::before{content:"\F02E8"}.mdi-human-male-female-child::before{content:"\F1823"}.mdi-human-male-girl::before{content:"\F0A5D"}.mdi-human-male-height::before{content:"\F0EFB"}.mdi-human-male-height-variant::before{content:"\F0EFC"}.mdi-human-male-male::before{content:"\F0A5E"}.mdi-human-male-male-child::before{content:"\F1C8F"}.mdi-human-non-binary::before{content:"\F1848"}.mdi-human-pregnant::before{content:"\F05CF"}.mdi-human-queue::before{content:"\F1571"}.mdi-human-scooter::before{content:"\F11E9"}.mdi-human-walker::before{content:"\F1B71"}.mdi-human-wheelchair::before{content:"\F138D"}.mdi-human-white-cane::before{content:"\F1981"}.mdi-humble-bundle::before{content:"\F0744"}.mdi-hvac::before{content:"\F1352"}.mdi-hvac-off::before{content:"\F159E"}.mdi-hydraulic-oil-level::before{content:"\F1324"}.mdi-hydraulic-oil-temperature::before{content:"\F1325"}.mdi-hydro-power::before{content:"\F12E5"}.mdi-hydrogen-station::before{content:"\F1894"}.mdi-ice-cream::before{content:"\F082A"}.mdi-ice-cream-off::before{content:"\F0E52"}.mdi-ice-pop::before{content:"\F0EFD"}.mdi-id-card::before{content:"\F0FC0"}.mdi-identifier::before{content:"\F0EFE"}.mdi-ideogram-cjk::before{content:"\F1331"}.mdi-ideogram-cjk-variant::before{content:"\F1332"}.mdi-image::before{content:"\F02E9"}.mdi-image-album::before{content:"\F02EA"}.mdi-image-area::before{content:"\F02EB"}.mdi-image-area-close::before{content:"\F02EC"}.mdi-image-auto-adjust::before{content:"\F0FC1"}.mdi-image-broken::before{content:"\F02ED"}.mdi-image-broken-variant::before{content:"\F02EE"}.mdi-image-check::before{content:"\F1B25"}.mdi-image-check-outline::before{content:"\F1B26"}.mdi-image-edit::before{content:"\F11E3"}.mdi-image-edit-outline::before{content:"\F11E4"}.mdi-image-filter-black-white::before{content:"\F02F0"}.mdi-image-filter-center-focus::before{content:"\F02F1"}.mdi-image-filter-center-focus-strong::before{content:"\F0EFF"}.mdi-image-filter-center-focus-strong-outline::before{content:"\F0F00"}.mdi-image-filter-center-focus-weak::before{content:"\F02F2"}.mdi-image-filter-drama::before{content:"\F02F3"}.mdi-image-filter-drama-outline::before{content:"\F1BFF"}.mdi-image-filter-frames::before{content:"\F02F4"}.mdi-image-filter-hdr::before{content:"\F02F5"}.mdi-image-filter-hdr-outline::before{content:"\F1C64"}.mdi-image-filter-none::before{content:"\F02F6"}.mdi-image-filter-tilt-shift::before{content:"\F02F7"}.mdi-image-filter-vintage::before{content:"\F02F8"}.mdi-image-frame::before{content:"\F0E49"}.mdi-image-lock::before{content:"\F1AB0"}.mdi-image-lock-outline::before{content:"\F1AB1"}.mdi-image-marker::before{content:"\F177B"}.mdi-image-marker-outline::before{content:"\F177C"}.mdi-image-minus::before{content:"\F1419"}.mdi-image-minus-outline::before{content:"\F1B47"}.mdi-image-move::before{content:"\F09F8"}.mdi-image-multiple::before{content:"\F02F9"}.mdi-image-multiple-outline::before{content:"\F02EF"}.mdi-image-off::before{content:"\F082B"}.mdi-image-off-outline::before{content:"\F11D1"}.mdi-image-outline::before{content:"\F0976"}.mdi-image-plus::before{content:"\F087C"}.mdi-image-plus-outline::before{content:"\F1B46"}.mdi-image-refresh::before{content:"\F19FE"}.mdi-image-refresh-outline::before{content:"\F19FF"}.mdi-image-remove::before{content:"\F1418"}.mdi-image-remove-outline::before{content:"\F1B48"}.mdi-image-search::before{content:"\F0977"}.mdi-image-search-outline::before{content:"\F0978"}.mdi-image-size-select-actual::before{content:"\F0C8D"}.mdi-image-size-select-large::before{content:"\F0C8E"}.mdi-image-size-select-small::before{content:"\F0C8F"}.mdi-image-sync::before{content:"\F1A00"}.mdi-image-sync-outline::before{content:"\F1A01"}.mdi-image-text::before{content:"\F160D"}.mdi-import::before{content:"\F02FA"}.mdi-inbox::before{content:"\F0687"}.mdi-inbox-arrow-down::before{content:"\F02FB"}.mdi-inbox-arrow-down-outline::before{content:"\F1270"}.mdi-inbox-arrow-up::before{content:"\F03D1"}.mdi-inbox-arrow-up-outline::before{content:"\F1271"}.mdi-inbox-full::before{content:"\F1272"}.mdi-inbox-full-outline::before{content:"\F1273"}.mdi-inbox-multiple::before{content:"\F08B0"}.mdi-inbox-multiple-outline::before{content:"\F0BA8"}.mdi-inbox-outline::before{content:"\F1274"}.mdi-inbox-remove::before{content:"\F159F"}.mdi-inbox-remove-outline::before{content:"\F15A0"}.mdi-incognito::before{content:"\F05F9"}.mdi-incognito-circle::before{content:"\F1421"}.mdi-incognito-circle-off::before{content:"\F1422"}.mdi-incognito-off::before{content:"\F0075"}.mdi-induction::before{content:"\F184C"}.mdi-infinity::before{content:"\F06E4"}.mdi-information::before{content:"\F02FC"}.mdi-information-box::before{content:"\F1C65"}.mdi-information-box-outline::before{content:"\F1C66"}.mdi-information-off::before{content:"\F178C"}.mdi-information-off-outline::before{content:"\F178D"}.mdi-information-outline::before{content:"\F02FD"}.mdi-information-slab-box::before{content:"\F1C67"}.mdi-information-slab-box-outline::before{content:"\F1C68"}.mdi-information-slab-circle::before{content:"\F1C69"}.mdi-information-slab-circle-outline::before{content:"\F1C6A"}.mdi-information-slab-symbol::before{content:"\F1C6B"}.mdi-information-symbol::before{content:"\F1C6C"}.mdi-information-variant::before{content:"\F064E"}.mdi-information-variant-box::before{content:"\F1C6D"}.mdi-information-variant-box-outline::before{content:"\F1C6E"}.mdi-information-variant-circle::before{content:"\F1C6F"}.mdi-information-variant-circle-outline::before{content:"\F1C70"}.mdi-instagram::before{content:"\F02FE"}.mdi-instrument-triangle::before{content:"\F104E"}.mdi-integrated-circuit-chip::before{content:"\F1913"}.mdi-invert-colors::before{content:"\F0301"}.mdi-invert-colors-off::before{content:"\F0E4A"}.mdi-invoice::before{content:"\F1CD2"}.mdi-invoice-arrow-left::before{content:"\F1CD3"}.mdi-invoice-arrow-left-outline::before{content:"\F1CD4"}.mdi-invoice-arrow-right::before{content:"\F1CD5"}.mdi-invoice-arrow-right-outline::before{content:"\F1CD6"}.mdi-invoice-check::before{content:"\F1CD7"}.mdi-invoice-check-outline::before{content:"\F1CD8"}.mdi-invoice-clock::before{content:"\F1CD9"}.mdi-invoice-clock-outline::before{content:"\F1CDA"}.mdi-invoice-edit::before{content:"\F1CDB"}.mdi-invoice-edit-outline::before{content:"\F1CDC"}.mdi-invoice-export-outline::before{content:"\F1CDD"}.mdi-invoice-fast::before{content:"\F1CDE"}.mdi-invoice-fast-outline::before{content:"\F1CDF"}.mdi-invoice-import::before{content:"\F1CE0"}.mdi-invoice-import-outline::before{content:"\F1CE1"}.mdi-invoice-list::before{content:"\F1CE2"}.mdi-invoice-list-outline::before{content:"\F1CE3"}.mdi-invoice-minus::before{content:"\F1CE4"}.mdi-invoice-minus-outline::before{content:"\F1CE5"}.mdi-invoice-multiple::before{content:"\F1CE6"}.mdi-invoice-multiple-outline::before{content:"\F1CE7"}.mdi-invoice-outline::before{content:"\F1CE8"}.mdi-invoice-plus::before{content:"\F1CE9"}.mdi-invoice-plus-outline::before{content:"\F1CEA"}.mdi-invoice-remove::before{content:"\F1CEB"}.mdi-invoice-remove-outline::before{content:"\F1CEC"}.mdi-invoice-send::before{content:"\F1CED"}.mdi-invoice-send-outline::before{content:"\F1CEE"}.mdi-invoice-text::before{content:"\F1CEF"}.mdi-invoice-text-arrow-left::before{content:"\F1CF0"}.mdi-invoice-text-arrow-left-outline::before{content:"\F1CF1"}.mdi-invoice-text-arrow-right::before{content:"\F1CF2"}.mdi-invoice-text-arrow-right-outline::before{content:"\F1CF3"}.mdi-invoice-text-check::before{content:"\F1CF4"}.mdi-invoice-text-check-outline::before{content:"\F1CF5"}.mdi-invoice-text-clock::before{content:"\F1CF6"}.mdi-invoice-text-clock-outline::before{content:"\F1CF7"}.mdi-invoice-text-edit::before{content:"\F1CF8"}.mdi-invoice-text-edit-outline::before{content:"\F1CF9"}.mdi-invoice-text-fast::before{content:"\F1CFA"}.mdi-invoice-text-fast-outline::before{content:"\F1CFB"}.mdi-invoice-text-minus::before{content:"\F1CFC"}.mdi-invoice-text-minus-outline::before{content:"\F1CFD"}.mdi-invoice-text-multiple::before{content:"\F1CFE"}.mdi-invoice-text-multiple-outline::before{content:"\F1CFF"}.mdi-invoice-text-outline::before{content:"\F1D00"}.mdi-invoice-text-plus::before{content:"\F1D01"}.mdi-invoice-text-plus-outline::before{content:"\F1D02"}.mdi-invoice-text-remove::before{content:"\F1D03"}.mdi-invoice-text-remove-outline::before{content:"\F1D04"}.mdi-invoice-text-send::before{content:"\F1D05"}.mdi-invoice-text-send-outline::before{content:"\F1D06"}.mdi-iobroker::before{content:"\F12E8"}.mdi-ip::before{content:"\F0A5F"}.mdi-ip-network::before{content:"\F0A60"}.mdi-ip-network-outline::before{content:"\F0C90"}.mdi-ip-outline::before{content:"\F1982"}.mdi-ipod::before{content:"\F0C91"}.mdi-iron::before{content:"\F1824"}.mdi-iron-board::before{content:"\F1838"}.mdi-iron-outline::before{content:"\F1825"}.mdi-island::before{content:"\F104F"}.mdi-island-variant::before{content:"\F1CC6"}.mdi-iv-bag::before{content:"\F10B9"}.mdi-jabber::before{content:"\F0DD5"}.mdi-jeepney::before{content:"\F0302"}.mdi-jellyfish::before{content:"\F0F01"}.mdi-jellyfish-outline::before{content:"\F0F02"}.mdi-jira::before{content:"\F0303"}.mdi-jquery::before{content:"\F087D"}.mdi-jsfiddle::before{content:"\F0304"}.mdi-jump-rope::before{content:"\F12FF"}.mdi-kabaddi::before{content:"\F0D87"}.mdi-kangaroo::before{content:"\F1558"}.mdi-karate::before{content:"\F082C"}.mdi-kayaking::before{content:"\F08AF"}.mdi-keg::before{content:"\F0305"}.mdi-kettle::before{content:"\F05FA"}.mdi-kettle-alert::before{content:"\F1317"}.mdi-kettle-alert-outline::before{content:"\F1318"}.mdi-kettle-off::before{content:"\F131B"}.mdi-kettle-off-outline::before{content:"\F131C"}.mdi-kettle-outline::before{content:"\F0F56"}.mdi-kettle-pour-over::before{content:"\F173C"}.mdi-kettle-steam::before{content:"\F1319"}.mdi-kettle-steam-outline::before{content:"\F131A"}.mdi-kettlebell::before{content:"\F1300"}.mdi-key::before{content:"\F0306"}.mdi-key-alert::before{content:"\F1983"}.mdi-key-alert-outline::before{content:"\F1984"}.mdi-key-arrow-right::before{content:"\F1312"}.mdi-key-chain::before{content:"\F1574"}.mdi-key-chain-variant::before{content:"\F1575"}.mdi-key-change::before{content:"\F0307"}.mdi-key-link::before{content:"\F119F"}.mdi-key-minus::before{content:"\F0308"}.mdi-key-outline::before{content:"\F0DD6"}.mdi-key-plus::before{content:"\F0309"}.mdi-key-remove::before{content:"\F030A"}.mdi-key-star::before{content:"\F119E"}.mdi-key-variant::before{content:"\F030B"}.mdi-key-wireless::before{content:"\F0FC2"}.mdi-keyboard::before{content:"\F030C"}.mdi-keyboard-backspace::before{content:"\F030D"}.mdi-keyboard-caps::before{content:"\F030E"}.mdi-keyboard-close::before{content:"\F030F"}.mdi-keyboard-close-outline::before{content:"\F1C00"}.mdi-keyboard-esc::before{content:"\F12B7"}.mdi-keyboard-f1::before{content:"\F12AB"}.mdi-keyboard-f10::before{content:"\F12B4"}.mdi-keyboard-f11::before{content:"\F12B5"}.mdi-keyboard-f12::before{content:"\F12B6"}.mdi-keyboard-f2::before{content:"\F12AC"}.mdi-keyboard-f3::before{content:"\F12AD"}.mdi-keyboard-f4::before{content:"\F12AE"}.mdi-keyboard-f5::before{content:"\F12AF"}.mdi-keyboard-f6::before{content:"\F12B0"}.mdi-keyboard-f7::before{content:"\F12B1"}.mdi-keyboard-f8::before{content:"\F12B2"}.mdi-keyboard-f9::before{content:"\F12B3"}.mdi-keyboard-off::before{content:"\F0310"}.mdi-keyboard-off-outline::before{content:"\F0E4B"}.mdi-keyboard-outline::before{content:"\F097B"}.mdi-keyboard-return::before{content:"\F0311"}.mdi-keyboard-settings::before{content:"\F09F9"}.mdi-keyboard-settings-outline::before{content:"\F09FA"}.mdi-keyboard-space::before{content:"\F1050"}.mdi-keyboard-tab::before{content:"\F0312"}.mdi-keyboard-tab-reverse::before{content:"\F0325"}.mdi-keyboard-variant::before{content:"\F0313"}.mdi-khanda::before{content:"\F10FD"}.mdi-kickstarter::before{content:"\F0745"}.mdi-kite::before{content:"\F1985"}.mdi-kite-outline::before{content:"\F1986"}.mdi-kitesurfing::before{content:"\F1744"}.mdi-klingon::before{content:"\F135B"}.mdi-knife::before{content:"\F09FB"}.mdi-knife-military::before{content:"\F09FC"}.mdi-knob::before{content:"\F1B96"}.mdi-koala::before{content:"\F173F"}.mdi-kodi::before{content:"\F0314"}.mdi-kubernetes::before{content:"\F10FE"}.mdi-label::before{content:"\F0315"}.mdi-label-multiple::before{content:"\F1375"}.mdi-label-multiple-outline::before{content:"\F1376"}.mdi-label-off::before{content:"\F0ACB"}.mdi-label-off-outline::before{content:"\F0ACC"}.mdi-label-outline::before{content:"\F0316"}.mdi-label-percent::before{content:"\F12EA"}.mdi-label-percent-outline::before{content:"\F12EB"}.mdi-label-variant::before{content:"\F0ACD"}.mdi-label-variant-outline::before{content:"\F0ACE"}.mdi-ladder::before{content:"\F15A2"}.mdi-ladybug::before{content:"\F082D"}.mdi-lambda::before{content:"\F0627"}.mdi-lamp::before{content:"\F06B5"}.mdi-lamp-outline::before{content:"\F17D0"}.mdi-lamps::before{content:"\F1576"}.mdi-lamps-outline::before{content:"\F17D1"}.mdi-lan::before{content:"\F0317"}.mdi-lan-check::before{content:"\F12AA"}.mdi-lan-connect::before{content:"\F0318"}.mdi-lan-disconnect::before{content:"\F0319"}.mdi-lan-pending::before{content:"\F031A"}.mdi-land-fields::before{content:"\F1AB2"}.mdi-land-plots::before{content:"\F1AB3"}.mdi-land-plots-circle::before{content:"\F1AB4"}.mdi-land-plots-circle-variant::before{content:"\F1AB5"}.mdi-land-plots-marker::before{content:"\F1C5D"}.mdi-land-rows-horizontal::before{content:"\F1AB6"}.mdi-land-rows-vertical::before{content:"\F1AB7"}.mdi-landslide::before{content:"\F1A48"}.mdi-landslide-outline::before{content:"\F1A49"}.mdi-language-c::before{content:"\F0671"}.mdi-language-cpp::before{content:"\F0672"}.mdi-language-csharp::before{content:"\F031B"}.mdi-language-css3::before{content:"\F031C"}.mdi-language-fortran::before{content:"\F121A"}.mdi-language-go::before{content:"\F07D3"}.mdi-language-haskell::before{content:"\F0C92"}.mdi-language-html5::before{content:"\F031D"}.mdi-language-java::before{content:"\F0B37"}.mdi-language-javascript::before{content:"\F031E"}.mdi-language-kotlin::before{content:"\F1219"}.mdi-language-lua::before{content:"\F08B1"}.mdi-language-markdown::before{content:"\F0354"}.mdi-language-markdown-outline::before{content:"\F0F5B"}.mdi-language-php::before{content:"\F031F"}.mdi-language-python::before{content:"\F0320"}.mdi-language-r::before{content:"\F07D4"}.mdi-language-ruby::before{content:"\F0D2D"}.mdi-language-ruby-on-rails::before{content:"\F0ACF"}.mdi-language-rust::before{content:"\F1617"}.mdi-language-swift::before{content:"\F06E5"}.mdi-language-typescript::before{content:"\F06E6"}.mdi-language-xaml::before{content:"\F0673"}.mdi-laptop::before{content:"\F0322"}.mdi-laptop-account::before{content:"\F1A4A"}.mdi-laptop-off::before{content:"\F06E7"}.mdi-laravel::before{content:"\F0AD0"}.mdi-laser-pointer::before{content:"\F1484"}.mdi-lasso::before{content:"\F0F03"}.mdi-lastpass::before{content:"\F0446"}.mdi-latitude::before{content:"\F0F57"}.mdi-launch::before{content:"\F0327"}.mdi-lava-lamp::before{content:"\F07D5"}.mdi-layers::before{content:"\F0328"}.mdi-layers-edit::before{content:"\F1892"}.mdi-layers-minus::before{content:"\F0E4C"}.mdi-layers-off::before{content:"\F0329"}.mdi-layers-off-outline::before{content:"\F09FD"}.mdi-layers-outline::before{content:"\F09FE"}.mdi-layers-plus::before{content:"\F0E4D"}.mdi-layers-remove::before{content:"\F0E4E"}.mdi-layers-search::before{content:"\F1206"}.mdi-layers-search-outline::before{content:"\F1207"}.mdi-layers-triple::before{content:"\F0F58"}.mdi-layers-triple-outline::before{content:"\F0F59"}.mdi-lead-pencil::before{content:"\F064F"}.mdi-leaf::before{content:"\F032A"}.mdi-leaf-circle::before{content:"\F1905"}.mdi-leaf-circle-outline::before{content:"\F1906"}.mdi-leaf-maple::before{content:"\F0C93"}.mdi-leaf-maple-off::before{content:"\F12DA"}.mdi-leaf-off::before{content:"\F12D9"}.mdi-leak::before{content:"\F0DD7"}.mdi-leak-off::before{content:"\F0DD8"}.mdi-lectern::before{content:"\F1AF0"}.mdi-led-off::before{content:"\F032B"}.mdi-led-on::before{content:"\F032C"}.mdi-led-outline::before{content:"\F032D"}.mdi-led-strip::before{content:"\F07D6"}.mdi-led-strip-variant::before{content:"\F1051"}.mdi-led-strip-variant-off::before{content:"\F1A4B"}.mdi-led-variant-off::before{content:"\F032E"}.mdi-led-variant-on::before{content:"\F032F"}.mdi-led-variant-outline::before{content:"\F0330"}.mdi-leek::before{content:"\F117D"}.mdi-less-than::before{content:"\F097C"}.mdi-less-than-or-equal::before{content:"\F097D"}.mdi-library::before{content:"\F0331"}.mdi-library-outline::before{content:"\F1A22"}.mdi-library-shelves::before{content:"\F0BA9"}.mdi-license::before{content:"\F0FC3"}.mdi-lifebuoy::before{content:"\F087E"}.mdi-light-flood-down::before{content:"\F1987"}.mdi-light-flood-up::before{content:"\F1988"}.mdi-light-recessed::before{content:"\F179B"}.mdi-light-switch::before{content:"\F097E"}.mdi-light-switch-off::before{content:"\F1A24"}.mdi-lightbulb::before{content:"\F0335"}.mdi-lightbulb-alert::before{content:"\F19E1"}.mdi-lightbulb-alert-outline::before{content:"\F19E2"}.mdi-lightbulb-auto::before{content:"\F1800"}.mdi-lightbulb-auto-outline::before{content:"\F1801"}.mdi-lightbulb-cfl::before{content:"\F1208"}.mdi-lightbulb-cfl-off::before{content:"\F1209"}.mdi-lightbulb-cfl-spiral::before{content:"\F1275"}.mdi-lightbulb-cfl-spiral-off::before{content:"\F12C3"}.mdi-lightbulb-fluorescent-tube::before{content:"\F1804"}.mdi-lightbulb-fluorescent-tube-outline::before{content:"\F1805"}.mdi-lightbulb-group::before{content:"\F1253"}.mdi-lightbulb-group-off::before{content:"\F12CD"}.mdi-lightbulb-group-off-outline::before{content:"\F12CE"}.mdi-lightbulb-group-outline::before{content:"\F1254"}.mdi-lightbulb-multiple::before{content:"\F1255"}.mdi-lightbulb-multiple-off::before{content:"\F12CF"}.mdi-lightbulb-multiple-off-outline::before{content:"\F12D0"}.mdi-lightbulb-multiple-outline::before{content:"\F1256"}.mdi-lightbulb-night::before{content:"\F1A4C"}.mdi-lightbulb-night-outline::before{content:"\F1A4D"}.mdi-lightbulb-off::before{content:"\F0E4F"}.mdi-lightbulb-off-outline::before{content:"\F0E50"}.mdi-lightbulb-on::before{content:"\F06E8"}.mdi-lightbulb-on-10::before{content:"\F1A4E"}.mdi-lightbulb-on-20::before{content:"\F1A4F"}.mdi-lightbulb-on-30::before{content:"\F1A50"}.mdi-lightbulb-on-40::before{content:"\F1A51"}.mdi-lightbulb-on-50::before{content:"\F1A52"}.mdi-lightbulb-on-60::before{content:"\F1A53"}.mdi-lightbulb-on-70::before{content:"\F1A54"}.mdi-lightbulb-on-80::before{content:"\F1A55"}.mdi-lightbulb-on-90::before{content:"\F1A56"}.mdi-lightbulb-on-outline::before{content:"\F06E9"}.mdi-lightbulb-outline::before{content:"\F0336"}.mdi-lightbulb-question::before{content:"\F19E3"}.mdi-lightbulb-question-outline::before{content:"\F19E4"}.mdi-lightbulb-spot::before{content:"\F17F4"}.mdi-lightbulb-spot-off::before{content:"\F17F5"}.mdi-lightbulb-variant::before{content:"\F1802"}.mdi-lightbulb-variant-outline::before{content:"\F1803"}.mdi-lighthouse::before{content:"\F09FF"}.mdi-lighthouse-on::before{content:"\F0A00"}.mdi-lightning-bolt::before{content:"\F140B"}.mdi-lightning-bolt-circle::before{content:"\F0820"}.mdi-lightning-bolt-outline::before{content:"\F140C"}.mdi-line-scan::before{content:"\F0624"}.mdi-lingerie::before{content:"\F1476"}.mdi-link::before{content:"\F0337"}.mdi-link-box::before{content:"\F0D1A"}.mdi-link-box-outline::before{content:"\F0D1B"}.mdi-link-box-variant::before{content:"\F0D1C"}.mdi-link-box-variant-outline::before{content:"\F0D1D"}.mdi-link-circle::before{content:"\F1CAC"}.mdi-link-circle-outline::before{content:"\F1CAD"}.mdi-link-edit::before{content:"\F1CAE"}.mdi-link-lock::before{content:"\F10BA"}.mdi-link-off::before{content:"\F0338"}.mdi-link-plus::before{content:"\F0C94"}.mdi-link-variant::before{content:"\F0339"}.mdi-link-variant-minus::before{content:"\F10FF"}.mdi-link-variant-off::before{content:"\F033A"}.mdi-link-variant-plus::before{content:"\F1100"}.mdi-link-variant-remove::before{content:"\F1101"}.mdi-linkedin::before{content:"\F033B"}.mdi-linux::before{content:"\F033D"}.mdi-linux-mint::before{content:"\F08ED"}.mdi-lipstick::before{content:"\F13B5"}.mdi-liquid-spot::before{content:"\F1826"}.mdi-liquor::before{content:"\F191E"}.mdi-list-box::before{content:"\F1B7B"}.mdi-list-box-outline::before{content:"\F1B7C"}.mdi-list-status::before{content:"\F15AB"}.mdi-litecoin::before{content:"\F0A61"}.mdi-loading::before{content:"\F0772"}.mdi-location-enter::before{content:"\F0FC4"}.mdi-location-exit::before{content:"\F0FC5"}.mdi-lock::before{content:"\F033E"}.mdi-lock-alert::before{content:"\F08EE"}.mdi-lock-alert-outline::before{content:"\F15D1"}.mdi-lock-check::before{content:"\F139A"}.mdi-lock-check-outline::before{content:"\F16A8"}.mdi-lock-clock::before{content:"\F097F"}.mdi-lock-minus::before{content:"\F16A9"}.mdi-lock-minus-outline::before{content:"\F16AA"}.mdi-lock-off::before{content:"\F1671"}.mdi-lock-off-outline::before{content:"\F1672"}.mdi-lock-open::before{content:"\F033F"}.mdi-lock-open-alert::before{content:"\F139B"}.mdi-lock-open-alert-outline::before{content:"\F15D2"}.mdi-lock-open-check::before{content:"\F139C"}.mdi-lock-open-check-outline::before{content:"\F16AB"}.mdi-lock-open-minus::before{content:"\F16AC"}.mdi-lock-open-minus-outline::before{content:"\F16AD"}.mdi-lock-open-outline::before{content:"\F0340"}.mdi-lock-open-plus::before{content:"\F16AE"}.mdi-lock-open-plus-outline::before{content:"\F16AF"}.mdi-lock-open-remove::before{content:"\F16B0"}.mdi-lock-open-remove-outline::before{content:"\F16B1"}.mdi-lock-open-variant::before{content:"\F0FC6"}.mdi-lock-open-variant-outline::before{content:"\F0FC7"}.mdi-lock-outline::before{content:"\F0341"}.mdi-lock-pattern::before{content:"\F06EA"}.mdi-lock-percent::before{content:"\F1C12"}.mdi-lock-percent-open::before{content:"\F1C13"}.mdi-lock-percent-open-outline::before{content:"\F1C14"}.mdi-lock-percent-open-variant::before{content:"\F1C15"}.mdi-lock-percent-open-variant-outline::before{content:"\F1C16"}.mdi-lock-percent-outline::before{content:"\F1C17"}.mdi-lock-plus::before{content:"\F05FB"}.mdi-lock-plus-outline::before{content:"\F16B2"}.mdi-lock-question::before{content:"\F08EF"}.mdi-lock-remove::before{content:"\F16B3"}.mdi-lock-remove-outline::before{content:"\F16B4"}.mdi-lock-reset::before{content:"\F0773"}.mdi-lock-smart::before{content:"\F08B2"}.mdi-locker::before{content:"\F07D7"}.mdi-locker-multiple::before{content:"\F07D8"}.mdi-login::before{content:"\F0342"}.mdi-login-variant::before{content:"\F05FC"}.mdi-logout::before{content:"\F0343"}.mdi-logout-variant::before{content:"\F05FD"}.mdi-longitude::before{content:"\F0F5A"}.mdi-looks::before{content:"\F0344"}.mdi-lotion::before{content:"\F1582"}.mdi-lotion-outline::before{content:"\F1583"}.mdi-lotion-plus::before{content:"\F1584"}.mdi-lotion-plus-outline::before{content:"\F1585"}.mdi-loupe::before{content:"\F0345"}.mdi-lumx::before{content:"\F0346"}.mdi-lungs::before{content:"\F1084"}.mdi-mace::before{content:"\F1843"}.mdi-magazine-pistol::before{content:"\F0324"}.mdi-magazine-rifle::before{content:"\F0323"}.mdi-magic-staff::before{content:"\F1844"}.mdi-magnet::before{content:"\F0347"}.mdi-magnet-on::before{content:"\F0348"}.mdi-magnify::before{content:"\F0349"}.mdi-magnify-close::before{content:"\F0980"}.mdi-magnify-expand::before{content:"\F1874"}.mdi-magnify-minus::before{content:"\F034A"}.mdi-magnify-minus-cursor::before{content:"\F0A62"}.mdi-magnify-minus-outline::before{content:"\F06EC"}.mdi-magnify-plus::before{content:"\F034B"}.mdi-magnify-plus-cursor::before{content:"\F0A63"}.mdi-magnify-plus-outline::before{content:"\F06ED"}.mdi-magnify-remove-cursor::before{content:"\F120C"}.mdi-magnify-remove-outline::before{content:"\F120D"}.mdi-magnify-scan::before{content:"\F1276"}.mdi-mail::before{content:"\F0EBB"}.mdi-mailbox::before{content:"\F06EE"}.mdi-mailbox-open::before{content:"\F0D88"}.mdi-mailbox-open-outline::before{content:"\F0D89"}.mdi-mailbox-open-up::before{content:"\F0D8A"}.mdi-mailbox-open-up-outline::before{content:"\F0D8B"}.mdi-mailbox-outline::before{content:"\F0D8C"}.mdi-mailbox-up::before{content:"\F0D8D"}.mdi-mailbox-up-outline::before{content:"\F0D8E"}.mdi-manjaro::before{content:"\F160A"}.mdi-map::before{content:"\F034D"}.mdi-map-check::before{content:"\F0EBC"}.mdi-map-check-outline::before{content:"\F0EBD"}.mdi-map-clock::before{content:"\F0D1E"}.mdi-map-clock-outline::before{content:"\F0D1F"}.mdi-map-legend::before{content:"\F0A01"}.mdi-map-marker::before{content:"\F034E"}.mdi-map-marker-account::before{content:"\F18E3"}.mdi-map-marker-account-outline::before{content:"\F18E4"}.mdi-map-marker-alert::before{content:"\F0F05"}.mdi-map-marker-alert-outline::before{content:"\F0F06"}.mdi-map-marker-check::before{content:"\F0C95"}.mdi-map-marker-check-outline::before{content:"\F12FB"}.mdi-map-marker-circle::before{content:"\F034F"}.mdi-map-marker-distance::before{content:"\F08F0"}.mdi-map-marker-down::before{content:"\F1102"}.mdi-map-marker-left::before{content:"\F12DB"}.mdi-map-marker-left-outline::before{content:"\F12DD"}.mdi-map-marker-minus::before{content:"\F0650"}.mdi-map-marker-minus-outline::before{content:"\F12F9"}.mdi-map-marker-multiple::before{content:"\F0350"}.mdi-map-marker-multiple-outline::before{content:"\F1277"}.mdi-map-marker-off::before{content:"\F0351"}.mdi-map-marker-off-outline::before{content:"\F12FD"}.mdi-map-marker-outline::before{content:"\F07D9"}.mdi-map-marker-path::before{content:"\F0D20"}.mdi-map-marker-plus::before{content:"\F0651"}.mdi-map-marker-plus-outline::before{content:"\F12F8"}.mdi-map-marker-question::before{content:"\F0F07"}.mdi-map-marker-question-outline::before{content:"\F0F08"}.mdi-map-marker-radius::before{content:"\F0352"}.mdi-map-marker-radius-outline::before{content:"\F12FC"}.mdi-map-marker-remove::before{content:"\F0F09"}.mdi-map-marker-remove-outline::before{content:"\F12FA"}.mdi-map-marker-remove-variant::before{content:"\F0F0A"}.mdi-map-marker-right::before{content:"\F12DC"}.mdi-map-marker-right-outline::before{content:"\F12DE"}.mdi-map-marker-star::before{content:"\F1608"}.mdi-map-marker-star-outline::before{content:"\F1609"}.mdi-map-marker-up::before{content:"\F1103"}.mdi-map-minus::before{content:"\F0981"}.mdi-map-outline::before{content:"\F0982"}.mdi-map-plus::before{content:"\F0983"}.mdi-map-search::before{content:"\F0984"}.mdi-map-search-outline::before{content:"\F0985"}.mdi-mapbox::before{content:"\F0BAA"}.mdi-margin::before{content:"\F0353"}.mdi-marker::before{content:"\F0652"}.mdi-marker-cancel::before{content:"\F0DD9"}.mdi-marker-check::before{content:"\F0355"}.mdi-mastodon::before{content:"\F0AD1"}.mdi-material-design::before{content:"\F0986"}.mdi-material-ui::before{content:"\F0357"}.mdi-math-compass::before{content:"\F0358"}.mdi-math-cos::before{content:"\F0C96"}.mdi-math-integral::before{content:"\F0FC8"}.mdi-math-integral-box::before{content:"\F0FC9"}.mdi-math-log::before{content:"\F1085"}.mdi-math-norm::before{content:"\F0FCA"}.mdi-math-norm-box::before{content:"\F0FCB"}.mdi-math-sin::before{content:"\F0C97"}.mdi-math-tan::before{content:"\F0C98"}.mdi-matrix::before{content:"\F0628"}.mdi-medal::before{content:"\F0987"}.mdi-medal-outline::before{content:"\F1326"}.mdi-medical-bag::before{content:"\F06EF"}.mdi-medical-cotton-swab::before{content:"\F1AB8"}.mdi-medication::before{content:"\F1B14"}.mdi-medication-outline::before{content:"\F1B15"}.mdi-meditation::before{content:"\F117B"}.mdi-memory::before{content:"\F035B"}.mdi-memory-arrow-down::before{content:"\F1CA6"}.mdi-menorah::before{content:"\F17D4"}.mdi-menorah-fire::before{content:"\F17D5"}.mdi-menu::before{content:"\F035C"}.mdi-menu-close::before{content:"\F1C90"}.mdi-menu-down::before{content:"\F035D"}.mdi-menu-down-outline::before{content:"\F06B6"}.mdi-menu-left::before{content:"\F035E"}.mdi-menu-left-outline::before{content:"\F0A02"}.mdi-menu-open::before{content:"\F0BAB"}.mdi-menu-right::before{content:"\F035F"}.mdi-menu-right-outline::before{content:"\F0A03"}.mdi-menu-swap::before{content:"\F0A64"}.mdi-menu-swap-outline::before{content:"\F0A65"}.mdi-menu-up::before{content:"\F0360"}.mdi-menu-up-outline::before{content:"\F06B7"}.mdi-merge::before{content:"\F0F5C"}.mdi-message::before{content:"\F0361"}.mdi-message-alert::before{content:"\F0362"}.mdi-message-alert-outline::before{content:"\F0A04"}.mdi-message-arrow-left::before{content:"\F12F2"}.mdi-message-arrow-left-outline::before{content:"\F12F3"}.mdi-message-arrow-right::before{content:"\F12F4"}.mdi-message-arrow-right-outline::before{content:"\F12F5"}.mdi-message-badge::before{content:"\F1941"}.mdi-message-badge-outline::before{content:"\F1942"}.mdi-message-bookmark::before{content:"\F15AC"}.mdi-message-bookmark-outline::before{content:"\F15AD"}.mdi-message-bulleted::before{content:"\F06A2"}.mdi-message-bulleted-off::before{content:"\F06A3"}.mdi-message-check::before{content:"\F1B8A"}.mdi-message-check-outline::before{content:"\F1B8B"}.mdi-message-cog::before{content:"\F06F1"}.mdi-message-cog-outline::before{content:"\F1172"}.mdi-message-draw::before{content:"\F0363"}.mdi-message-fast::before{content:"\F19CC"}.mdi-message-fast-outline::before{content:"\F19CD"}.mdi-message-flash::before{content:"\F15A9"}.mdi-message-flash-outline::before{content:"\F15AA"}.mdi-message-image::before{content:"\F0364"}.mdi-message-image-outline::before{content:"\F116C"}.mdi-message-lock::before{content:"\F0FCC"}.mdi-message-lock-outline::before{content:"\F116D"}.mdi-message-minus::before{content:"\F116E"}.mdi-message-minus-outline::before{content:"\F116F"}.mdi-message-off::before{content:"\F164D"}.mdi-message-off-outline::before{content:"\F164E"}.mdi-message-outline::before{content:"\F0365"}.mdi-message-plus::before{content:"\F0653"}.mdi-message-plus-outline::before{content:"\F10BB"}.mdi-message-processing::before{content:"\F0366"}.mdi-message-processing-outline::before{content:"\F1170"}.mdi-message-question::before{content:"\F173A"}.mdi-message-question-outline::before{content:"\F173B"}.mdi-message-reply::before{content:"\F0367"}.mdi-message-reply-outline::before{content:"\F173D"}.mdi-message-reply-text::before{content:"\F0368"}.mdi-message-reply-text-outline::before{content:"\F173E"}.mdi-message-settings::before{content:"\F06F0"}.mdi-message-settings-outline::before{content:"\F1171"}.mdi-message-star::before{content:"\F069A"}.mdi-message-star-outline::before{content:"\F1250"}.mdi-message-text::before{content:"\F0369"}.mdi-message-text-clock::before{content:"\F1173"}.mdi-message-text-clock-outline::before{content:"\F1174"}.mdi-message-text-fast::before{content:"\F19CE"}.mdi-message-text-fast-outline::before{content:"\F19CF"}.mdi-message-text-lock::before{content:"\F0FCD"}.mdi-message-text-lock-outline::before{content:"\F1175"}.mdi-message-text-outline::before{content:"\F036A"}.mdi-message-video::before{content:"\F036B"}.mdi-meteor::before{content:"\F0629"}.mdi-meter-electric::before{content:"\F1A57"}.mdi-meter-electric-outline::before{content:"\F1A58"}.mdi-meter-gas::before{content:"\F1A59"}.mdi-meter-gas-outline::before{content:"\F1A5A"}.mdi-metronome::before{content:"\F07DA"}.mdi-metronome-tick::before{content:"\F07DB"}.mdi-micro-sd::before{content:"\F07DC"}.mdi-microphone::before{content:"\F036C"}.mdi-microphone-message::before{content:"\F050A"}.mdi-microphone-message-off::before{content:"\F050B"}.mdi-microphone-minus::before{content:"\F08B3"}.mdi-microphone-off::before{content:"\F036D"}.mdi-microphone-outline::before{content:"\F036E"}.mdi-microphone-plus::before{content:"\F08B4"}.mdi-microphone-question::before{content:"\F1989"}.mdi-microphone-question-outline::before{content:"\F198A"}.mdi-microphone-settings::before{content:"\F036F"}.mdi-microphone-variant::before{content:"\F0370"}.mdi-microphone-variant-off::before{content:"\F0371"}.mdi-microscope::before{content:"\F0654"}.mdi-microsoft::before{content:"\F0372"}.mdi-microsoft-access::before{content:"\F138E"}.mdi-microsoft-azure::before{content:"\F0805"}.mdi-microsoft-azure-devops::before{content:"\F0FD5"}.mdi-microsoft-bing::before{content:"\F00A4"}.mdi-microsoft-dynamics-365::before{content:"\F0988"}.mdi-microsoft-edge::before{content:"\F01E9"}.mdi-microsoft-excel::before{content:"\F138F"}.mdi-microsoft-internet-explorer::before{content:"\F0300"}.mdi-microsoft-office::before{content:"\F03C6"}.mdi-microsoft-onedrive::before{content:"\F03CA"}.mdi-microsoft-onenote::before{content:"\F0747"}.mdi-microsoft-outlook::before{content:"\F0D22"}.mdi-microsoft-powerpoint::before{content:"\F1390"}.mdi-microsoft-sharepoint::before{content:"\F1391"}.mdi-microsoft-teams::before{content:"\F02BB"}.mdi-microsoft-visual-studio::before{content:"\F0610"}.mdi-microsoft-visual-studio-code::before{content:"\F0A1E"}.mdi-microsoft-windows::before{content:"\F05B3"}.mdi-microsoft-windows-classic::before{content:"\F0A21"}.mdi-microsoft-word::before{content:"\F1392"}.mdi-microsoft-xbox::before{content:"\F05B9"}.mdi-microsoft-xbox-controller::before{content:"\F05BA"}.mdi-microsoft-xbox-controller-battery-alert::before{content:"\F074B"}.mdi-microsoft-xbox-controller-battery-charging::before{content:"\F0A22"}.mdi-microsoft-xbox-controller-battery-empty::before{content:"\F074C"}.mdi-microsoft-xbox-controller-battery-full::before{content:"\F074D"}.mdi-microsoft-xbox-controller-battery-low::before{content:"\F074E"}.mdi-microsoft-xbox-controller-battery-medium::before{content:"\F074F"}.mdi-microsoft-xbox-controller-battery-unknown::before{content:"\F0750"}.mdi-microsoft-xbox-controller-menu::before{content:"\F0E6F"}.mdi-microsoft-xbox-controller-off::before{content:"\F05BB"}.mdi-microsoft-xbox-controller-view::before{content:"\F0E70"}.mdi-microwave::before{content:"\F0C99"}.mdi-microwave-off::before{content:"\F1423"}.mdi-middleware::before{content:"\F0F5D"}.mdi-middleware-outline::before{content:"\F0F5E"}.mdi-midi::before{content:"\F08F1"}.mdi-midi-port::before{content:"\F08F2"}.mdi-mine::before{content:"\F0DDA"}.mdi-minecraft::before{content:"\F0373"}.mdi-mini-sd::before{content:"\F0A05"}.mdi-minidisc::before{content:"\F0A06"}.mdi-minus::before{content:"\F0374"}.mdi-minus-box::before{content:"\F0375"}.mdi-minus-box-multiple::before{content:"\F1141"}.mdi-minus-box-multiple-outline::before{content:"\F1142"}.mdi-minus-box-outline::before{content:"\F06F2"}.mdi-minus-circle::before{content:"\F0376"}.mdi-minus-circle-multiple::before{content:"\F035A"}.mdi-minus-circle-multiple-outline::before{content:"\F0AD3"}.mdi-minus-circle-off::before{content:"\F1459"}.mdi-minus-circle-off-outline::before{content:"\F145A"}.mdi-minus-circle-outline::before{content:"\F0377"}.mdi-minus-network::before{content:"\F0378"}.mdi-minus-network-outline::before{content:"\F0C9A"}.mdi-minus-thick::before{content:"\F1639"}.mdi-mirror::before{content:"\F11FD"}.mdi-mirror-rectangle::before{content:"\F179F"}.mdi-mirror-variant::before{content:"\F17A0"}.mdi-mixed-martial-arts::before{content:"\F0D8F"}.mdi-mixed-reality::before{content:"\F087F"}.mdi-molecule::before{content:"\F0BAC"}.mdi-molecule-co::before{content:"\F12FE"}.mdi-molecule-co2::before{content:"\F07E4"}.mdi-monitor::before{content:"\F0379"}.mdi-monitor-account::before{content:"\F1A5B"}.mdi-monitor-arrow-down::before{content:"\F19D0"}.mdi-monitor-arrow-down-variant::before{content:"\F19D1"}.mdi-monitor-cellphone::before{content:"\F0989"}.mdi-monitor-cellphone-star::before{content:"\F098A"}.mdi-monitor-dashboard::before{content:"\F0A07"}.mdi-monitor-edit::before{content:"\F12C6"}.mdi-monitor-eye::before{content:"\F13B4"}.mdi-monitor-lock::before{content:"\F0DDB"}.mdi-monitor-multiple::before{content:"\F037A"}.mdi-monitor-off::before{content:"\F0D90"}.mdi-monitor-screenshot::before{content:"\F0E51"}.mdi-monitor-share::before{content:"\F1483"}.mdi-monitor-shimmer::before{content:"\F1104"}.mdi-monitor-small::before{content:"\F1876"}.mdi-monitor-speaker::before{content:"\F0F5F"}.mdi-monitor-speaker-off::before{content:"\F0F60"}.mdi-monitor-star::before{content:"\F0DDC"}.mdi-monitor-vertical::before{content:"\F1C33"}.mdi-moon-first-quarter::before{content:"\F0F61"}.mdi-moon-full::before{content:"\F0F62"}.mdi-moon-last-quarter::before{content:"\F0F63"}.mdi-moon-new::before{content:"\F0F64"}.mdi-moon-waning-crescent::before{content:"\F0F65"}.mdi-moon-waning-gibbous::before{content:"\F0F66"}.mdi-moon-waxing-crescent::before{content:"\F0F67"}.mdi-moon-waxing-gibbous::before{content:"\F0F68"}.mdi-moped::before{content:"\F1086"}.mdi-moped-electric::before{content:"\F15B7"}.mdi-moped-electric-outline::before{content:"\F15B8"}.mdi-moped-outline::before{content:"\F15B9"}.mdi-more::before{content:"\F037B"}.mdi-mortar-pestle::before{content:"\F1748"}.mdi-mortar-pestle-plus::before{content:"\F03F1"}.mdi-mosque::before{content:"\F0D45"}.mdi-mosque-outline::before{content:"\F1827"}.mdi-mother-heart::before{content:"\F1314"}.mdi-mother-nurse::before{content:"\F0D21"}.mdi-motion::before{content:"\F15B2"}.mdi-motion-outline::before{content:"\F15B3"}.mdi-motion-pause::before{content:"\F1590"}.mdi-motion-pause-outline::before{content:"\F1592"}.mdi-motion-play::before{content:"\F158F"}.mdi-motion-play-outline::before{content:"\F1591"}.mdi-motion-sensor::before{content:"\F0D91"}.mdi-motion-sensor-off::before{content:"\F1435"}.mdi-motorbike::before{content:"\F037C"}.mdi-motorbike-electric::before{content:"\F15BA"}.mdi-motorbike-off::before{content:"\F1B16"}.mdi-mouse::before{content:"\F037D"}.mdi-mouse-bluetooth::before{content:"\F098B"}.mdi-mouse-left-click::before{content:"\F1D07"}.mdi-mouse-left-click-outline::before{content:"\F1D08"}.mdi-mouse-move-down::before{content:"\F1550"}.mdi-mouse-move-up::before{content:"\F1551"}.mdi-mouse-move-vertical::before{content:"\F1552"}.mdi-mouse-off::before{content:"\F037E"}.mdi-mouse-outline::before{content:"\F1D09"}.mdi-mouse-right-click::before{content:"\F1D0A"}.mdi-mouse-right-click-outline::before{content:"\F1D0B"}.mdi-mouse-scroll-wheel::before{content:"\F1D0C"}.mdi-mouse-variant::before{content:"\F037F"}.mdi-mouse-variant-off::before{content:"\F0380"}.mdi-move-resize::before{content:"\F0655"}.mdi-move-resize-variant::before{content:"\F0656"}.mdi-movie::before{content:"\F0381"}.mdi-movie-check::before{content:"\F16F3"}.mdi-movie-check-outline::before{content:"\F16F4"}.mdi-movie-cog::before{content:"\F16F5"}.mdi-movie-cog-outline::before{content:"\F16F6"}.mdi-movie-edit::before{content:"\F1122"}.mdi-movie-edit-outline::before{content:"\F1123"}.mdi-movie-filter::before{content:"\F1124"}.mdi-movie-filter-outline::before{content:"\F1125"}.mdi-movie-minus::before{content:"\F16F7"}.mdi-movie-minus-outline::before{content:"\F16F8"}.mdi-movie-off::before{content:"\F16F9"}.mdi-movie-off-outline::before{content:"\F16FA"}.mdi-movie-open::before{content:"\F0FCE"}.mdi-movie-open-check::before{content:"\F16FB"}.mdi-movie-open-check-outline::before{content:"\F16FC"}.mdi-movie-open-cog::before{content:"\F16FD"}.mdi-movie-open-cog-outline::before{content:"\F16FE"}.mdi-movie-open-edit::before{content:"\F16FF"}.mdi-movie-open-edit-outline::before{content:"\F1700"}.mdi-movie-open-minus::before{content:"\F1701"}.mdi-movie-open-minus-outline::before{content:"\F1702"}.mdi-movie-open-off::before{content:"\F1703"}.mdi-movie-open-off-outline::before{content:"\F1704"}.mdi-movie-open-outline::before{content:"\F0FCF"}.mdi-movie-open-play::before{content:"\F1705"}.mdi-movie-open-play-outline::before{content:"\F1706"}.mdi-movie-open-plus::before{content:"\F1707"}.mdi-movie-open-plus-outline::before{content:"\F1708"}.mdi-movie-open-remove::before{content:"\F1709"}.mdi-movie-open-remove-outline::before{content:"\F170A"}.mdi-movie-open-settings::before{content:"\F170B"}.mdi-movie-open-settings-outline::before{content:"\F170C"}.mdi-movie-open-star::before{content:"\F170D"}.mdi-movie-open-star-outline::before{content:"\F170E"}.mdi-movie-outline::before{content:"\F0DDD"}.mdi-movie-play::before{content:"\F170F"}.mdi-movie-play-outline::before{content:"\F1710"}.mdi-movie-plus::before{content:"\F1711"}.mdi-movie-plus-outline::before{content:"\F1712"}.mdi-movie-remove::before{content:"\F1713"}.mdi-movie-remove-outline::before{content:"\F1714"}.mdi-movie-roll::before{content:"\F07DE"}.mdi-movie-search::before{content:"\F11D2"}.mdi-movie-search-outline::before{content:"\F11D3"}.mdi-movie-settings::before{content:"\F1715"}.mdi-movie-settings-outline::before{content:"\F1716"}.mdi-movie-star::before{content:"\F1717"}.mdi-movie-star-outline::before{content:"\F1718"}.mdi-mower::before{content:"\F166F"}.mdi-mower-bag::before{content:"\F1670"}.mdi-mower-bag-on::before{content:"\F1B60"}.mdi-mower-on::before{content:"\F1B5F"}.mdi-muffin::before{content:"\F098C"}.mdi-multicast::before{content:"\F1893"}.mdi-multimedia::before{content:"\F1B97"}.mdi-multiplication::before{content:"\F0382"}.mdi-multiplication-box::before{content:"\F0383"}.mdi-mushroom::before{content:"\F07DF"}.mdi-mushroom-off::before{content:"\F13FA"}.mdi-mushroom-off-outline::before{content:"\F13FB"}.mdi-mushroom-outline::before{content:"\F07E0"}.mdi-music::before{content:"\F075A"}.mdi-music-accidental-double-flat::before{content:"\F0F69"}.mdi-music-accidental-double-sharp::before{content:"\F0F6A"}.mdi-music-accidental-flat::before{content:"\F0F6B"}.mdi-music-accidental-natural::before{content:"\F0F6C"}.mdi-music-accidental-sharp::before{content:"\F0F6D"}.mdi-music-box::before{content:"\F0384"}.mdi-music-box-multiple::before{content:"\F0333"}.mdi-music-box-multiple-outline::before{content:"\F0F04"}.mdi-music-box-outline::before{content:"\F0385"}.mdi-music-circle::before{content:"\F0386"}.mdi-music-circle-outline::before{content:"\F0AD4"}.mdi-music-clef-alto::before{content:"\F0F6E"}.mdi-music-clef-bass::before{content:"\F0F6F"}.mdi-music-clef-treble::before{content:"\F0F70"}.mdi-music-note::before{content:"\F0387"}.mdi-music-note-bluetooth::before{content:"\F05FE"}.mdi-music-note-bluetooth-off::before{content:"\F05FF"}.mdi-music-note-eighth::before{content:"\F0388"}.mdi-music-note-eighth-dotted::before{content:"\F0F71"}.mdi-music-note-half::before{content:"\F0389"}.mdi-music-note-half-dotted::before{content:"\F0F72"}.mdi-music-note-minus::before{content:"\F1B89"}.mdi-music-note-off::before{content:"\F038A"}.mdi-music-note-off-outline::before{content:"\F0F73"}.mdi-music-note-outline::before{content:"\F0F74"}.mdi-music-note-plus::before{content:"\F0DDE"}.mdi-music-note-quarter::before{content:"\F038B"}.mdi-music-note-quarter-dotted::before{content:"\F0F75"}.mdi-music-note-sixteenth::before{content:"\F038C"}.mdi-music-note-sixteenth-dotted::before{content:"\F0F76"}.mdi-music-note-whole::before{content:"\F038D"}.mdi-music-note-whole-dotted::before{content:"\F0F77"}.mdi-music-off::before{content:"\F075B"}.mdi-music-rest-eighth::before{content:"\F0F78"}.mdi-music-rest-half::before{content:"\F0F79"}.mdi-music-rest-quarter::before{content:"\F0F7A"}.mdi-music-rest-sixteenth::before{content:"\F0F7B"}.mdi-music-rest-whole::before{content:"\F0F7C"}.mdi-mustache::before{content:"\F15DE"}.mdi-nail::before{content:"\F0DDF"}.mdi-nas::before{content:"\F08F3"}.mdi-nativescript::before{content:"\F0880"}.mdi-nature::before{content:"\F038E"}.mdi-nature-outline::before{content:"\F1C71"}.mdi-nature-people::before{content:"\F038F"}.mdi-nature-people-outline::before{content:"\F1C72"}.mdi-navigation::before{content:"\F0390"}.mdi-navigation-outline::before{content:"\F1607"}.mdi-navigation-variant::before{content:"\F18F0"}.mdi-navigation-variant-outline::before{content:"\F18F1"}.mdi-near-me::before{content:"\F05CD"}.mdi-necklace::before{content:"\F0F0B"}.mdi-needle::before{content:"\F0391"}.mdi-needle-off::before{content:"\F19D2"}.mdi-netflix::before{content:"\F0746"}.mdi-network::before{content:"\F06F3"}.mdi-network-off::before{content:"\F0C9B"}.mdi-network-off-outline::before{content:"\F0C9C"}.mdi-network-outline::before{content:"\F0C9D"}.mdi-network-pos::before{content:"\F1ACB"}.mdi-network-strength-1::before{content:"\F08F4"}.mdi-network-strength-1-alert::before{content:"\F08F5"}.mdi-network-strength-2::before{content:"\F08F6"}.mdi-network-strength-2-alert::before{content:"\F08F7"}.mdi-network-strength-3::before{content:"\F08F8"}.mdi-network-strength-3-alert::before{content:"\F08F9"}.mdi-network-strength-4::before{content:"\F08FA"}.mdi-network-strength-4-alert::before{content:"\F08FB"}.mdi-network-strength-4-cog::before{content:"\F191A"}.mdi-network-strength-off::before{content:"\F08FC"}.mdi-network-strength-off-outline::before{content:"\F08FD"}.mdi-network-strength-outline::before{content:"\F08FE"}.mdi-new-box::before{content:"\F0394"}.mdi-newspaper::before{content:"\F0395"}.mdi-newspaper-check::before{content:"\F1943"}.mdi-newspaper-minus::before{content:"\F0F0C"}.mdi-newspaper-plus::before{content:"\F0F0D"}.mdi-newspaper-remove::before{content:"\F1944"}.mdi-newspaper-variant::before{content:"\F1001"}.mdi-newspaper-variant-multiple::before{content:"\F1002"}.mdi-newspaper-variant-multiple-outline::before{content:"\F1003"}.mdi-newspaper-variant-outline::before{content:"\F1004"}.mdi-nfc::before{content:"\F0396"}.mdi-nfc-search-variant::before{content:"\F0E53"}.mdi-nfc-tap::before{content:"\F0397"}.mdi-nfc-variant::before{content:"\F0398"}.mdi-nfc-variant-off::before{content:"\F0E54"}.mdi-ninja::before{content:"\F0774"}.mdi-nintendo-game-boy::before{content:"\F1393"}.mdi-nintendo-switch::before{content:"\F07E1"}.mdi-nintendo-wii::before{content:"\F05AB"}.mdi-nintendo-wiiu::before{content:"\F072D"}.mdi-nix::before{content:"\F1105"}.mdi-nodejs::before{content:"\F0399"}.mdi-noodles::before{content:"\F117E"}.mdi-not-equal::before{content:"\F098D"}.mdi-not-equal-variant::before{content:"\F098E"}.mdi-note::before{content:"\F039A"}.mdi-note-alert::before{content:"\F177D"}.mdi-note-alert-outline::before{content:"\F177E"}.mdi-note-check::before{content:"\F177F"}.mdi-note-check-outline::before{content:"\F1780"}.mdi-note-edit::before{content:"\F1781"}.mdi-note-edit-outline::before{content:"\F1782"}.mdi-note-minus::before{content:"\F164F"}.mdi-note-minus-outline::before{content:"\F1650"}.mdi-note-multiple::before{content:"\F06B8"}.mdi-note-multiple-outline::before{content:"\F06B9"}.mdi-note-off::before{content:"\F1783"}.mdi-note-off-outline::before{content:"\F1784"}.mdi-note-outline::before{content:"\F039B"}.mdi-note-plus::before{content:"\F039C"}.mdi-note-plus-outline::before{content:"\F039D"}.mdi-note-remove::before{content:"\F1651"}.mdi-note-remove-outline::before{content:"\F1652"}.mdi-note-search::before{content:"\F1653"}.mdi-note-search-outline::before{content:"\F1654"}.mdi-note-text::before{content:"\F039E"}.mdi-note-text-outline::before{content:"\F11D7"}.mdi-notebook::before{content:"\F082E"}.mdi-notebook-check::before{content:"\F14F5"}.mdi-notebook-check-outline::before{content:"\F14F6"}.mdi-notebook-edit::before{content:"\F14E7"}.mdi-notebook-edit-outline::before{content:"\F14E9"}.mdi-notebook-heart::before{content:"\F1A0B"}.mdi-notebook-heart-outline::before{content:"\F1A0C"}.mdi-notebook-minus::before{content:"\F1610"}.mdi-notebook-minus-outline::before{content:"\F1611"}.mdi-notebook-multiple::before{content:"\F0E55"}.mdi-notebook-outline::before{content:"\F0EBF"}.mdi-notebook-plus::before{content:"\F1612"}.mdi-notebook-plus-outline::before{content:"\F1613"}.mdi-notebook-remove::before{content:"\F1614"}.mdi-notebook-remove-outline::before{content:"\F1615"}.mdi-notification-clear-all::before{content:"\F039F"}.mdi-npm::before{content:"\F06F7"}.mdi-nuke::before{content:"\F06A4"}.mdi-null::before{content:"\F07E2"}.mdi-numeric::before{content:"\F03A0"}.mdi-numeric-0::before{content:"\F0B39"}.mdi-numeric-0-box::before{content:"\F03A1"}.mdi-numeric-0-box-multiple::before{content:"\F0F0E"}.mdi-numeric-0-box-multiple-outline::before{content:"\F03A2"}.mdi-numeric-0-box-outline::before{content:"\F03A3"}.mdi-numeric-0-circle::before{content:"\F0C9E"}.mdi-numeric-0-circle-outline::before{content:"\F0C9F"}.mdi-numeric-1::before{content:"\F0B3A"}.mdi-numeric-1-box::before{content:"\F03A4"}.mdi-numeric-1-box-multiple::before{content:"\F0F0F"}.mdi-numeric-1-box-multiple-outline::before{content:"\F03A5"}.mdi-numeric-1-box-outline::before{content:"\F03A6"}.mdi-numeric-1-circle::before{content:"\F0CA0"}.mdi-numeric-1-circle-outline::before{content:"\F0CA1"}.mdi-numeric-10::before{content:"\F0FE9"}.mdi-numeric-10-box::before{content:"\F0F7D"}.mdi-numeric-10-box-multiple::before{content:"\F0FEA"}.mdi-numeric-10-box-multiple-outline::before{content:"\F0FEB"}.mdi-numeric-10-box-outline::before{content:"\F0F7E"}.mdi-numeric-10-circle::before{content:"\F0FEC"}.mdi-numeric-10-circle-outline::before{content:"\F0FED"}.mdi-numeric-2::before{content:"\F0B3B"}.mdi-numeric-2-box::before{content:"\F03A7"}.mdi-numeric-2-box-multiple::before{content:"\F0F10"}.mdi-numeric-2-box-multiple-outline::before{content:"\F03A8"}.mdi-numeric-2-box-outline::before{content:"\F03A9"}.mdi-numeric-2-circle::before{content:"\F0CA2"}.mdi-numeric-2-circle-outline::before{content:"\F0CA3"}.mdi-numeric-3::before{content:"\F0B3C"}.mdi-numeric-3-box::before{content:"\F03AA"}.mdi-numeric-3-box-multiple::before{content:"\F0F11"}.mdi-numeric-3-box-multiple-outline::before{content:"\F03AB"}.mdi-numeric-3-box-outline::before{content:"\F03AC"}.mdi-numeric-3-circle::before{content:"\F0CA4"}.mdi-numeric-3-circle-outline::before{content:"\F0CA5"}.mdi-numeric-4::before{content:"\F0B3D"}.mdi-numeric-4-box::before{content:"\F03AD"}.mdi-numeric-4-box-multiple::before{content:"\F0F12"}.mdi-numeric-4-box-multiple-outline::before{content:"\F03B2"}.mdi-numeric-4-box-outline::before{content:"\F03AE"}.mdi-numeric-4-circle::before{content:"\F0CA6"}.mdi-numeric-4-circle-outline::before{content:"\F0CA7"}.mdi-numeric-5::before{content:"\F0B3E"}.mdi-numeric-5-box::before{content:"\F03B1"}.mdi-numeric-5-box-multiple::before{content:"\F0F13"}.mdi-numeric-5-box-multiple-outline::before{content:"\F03AF"}.mdi-numeric-5-box-outline::before{content:"\F03B0"}.mdi-numeric-5-circle::before{content:"\F0CA8"}.mdi-numeric-5-circle-outline::before{content:"\F0CA9"}.mdi-numeric-6::before{content:"\F0B3F"}.mdi-numeric-6-box::before{content:"\F03B3"}.mdi-numeric-6-box-multiple::before{content:"\F0F14"}.mdi-numeric-6-box-multiple-outline::before{content:"\F03B4"}.mdi-numeric-6-box-outline::before{content:"\F03B5"}.mdi-numeric-6-circle::before{content:"\F0CAA"}.mdi-numeric-6-circle-outline::before{content:"\F0CAB"}.mdi-numeric-7::before{content:"\F0B40"}.mdi-numeric-7-box::before{content:"\F03B6"}.mdi-numeric-7-box-multiple::before{content:"\F0F15"}.mdi-numeric-7-box-multiple-outline::before{content:"\F03B7"}.mdi-numeric-7-box-outline::before{content:"\F03B8"}.mdi-numeric-7-circle::before{content:"\F0CAC"}.mdi-numeric-7-circle-outline::before{content:"\F0CAD"}.mdi-numeric-8::before{content:"\F0B41"}.mdi-numeric-8-box::before{content:"\F03B9"}.mdi-numeric-8-box-multiple::before{content:"\F0F16"}.mdi-numeric-8-box-multiple-outline::before{content:"\F03BA"}.mdi-numeric-8-box-outline::before{content:"\F03BB"}.mdi-numeric-8-circle::before{content:"\F0CAE"}.mdi-numeric-8-circle-outline::before{content:"\F0CAF"}.mdi-numeric-9::before{content:"\F0B42"}.mdi-numeric-9-box::before{content:"\F03BC"}.mdi-numeric-9-box-multiple::before{content:"\F0F17"}.mdi-numeric-9-box-multiple-outline::before{content:"\F03BD"}.mdi-numeric-9-box-outline::before{content:"\F03BE"}.mdi-numeric-9-circle::before{content:"\F0CB0"}.mdi-numeric-9-circle-outline::before{content:"\F0CB1"}.mdi-numeric-9-plus::before{content:"\F0FEE"}.mdi-numeric-9-plus-box::before{content:"\F03BF"}.mdi-numeric-9-plus-box-multiple::before{content:"\F0F18"}.mdi-numeric-9-plus-box-multiple-outline::before{content:"\F03C0"}.mdi-numeric-9-plus-box-outline::before{content:"\F03C1"}.mdi-numeric-9-plus-circle::before{content:"\F0CB2"}.mdi-numeric-9-plus-circle-outline::before{content:"\F0CB3"}.mdi-numeric-negative-1::before{content:"\F1052"}.mdi-numeric-off::before{content:"\F19D3"}.mdi-numeric-positive-1::before{content:"\F15CB"}.mdi-nut::before{content:"\F06F8"}.mdi-nutrition::before{content:"\F03C2"}.mdi-nuxt::before{content:"\F1106"}.mdi-oar::before{content:"\F067C"}.mdi-ocarina::before{content:"\F0DE0"}.mdi-oci::before{content:"\F12E9"}.mdi-ocr::before{content:"\F113A"}.mdi-octagon::before{content:"\F03C3"}.mdi-octagon-outline::before{content:"\F03C4"}.mdi-octagram::before{content:"\F06F9"}.mdi-octagram-edit::before{content:"\F1C34"}.mdi-octagram-edit-outline::before{content:"\F1C35"}.mdi-octagram-minus::before{content:"\F1C36"}.mdi-octagram-minus-outline::before{content:"\F1C37"}.mdi-octagram-outline::before{content:"\F0775"}.mdi-octagram-plus::before{content:"\F1C38"}.mdi-octagram-plus-outline::before{content:"\F1C39"}.mdi-octahedron::before{content:"\F1950"}.mdi-octahedron-off::before{content:"\F1951"}.mdi-odnoklassniki::before{content:"\F03C5"}.mdi-offer::before{content:"\F121B"}.mdi-office-building::before{content:"\F0991"}.mdi-office-building-cog::before{content:"\F1949"}.mdi-office-building-cog-outline::before{content:"\F194A"}.mdi-office-building-marker::before{content:"\F1520"}.mdi-office-building-marker-outline::before{content:"\F1521"}.mdi-office-building-minus::before{content:"\F1BAA"}.mdi-office-building-minus-outline::before{content:"\F1BAB"}.mdi-office-building-outline::before{content:"\F151F"}.mdi-office-building-plus::before{content:"\F1BA8"}.mdi-office-building-plus-outline::before{content:"\F1BA9"}.mdi-office-building-remove::before{content:"\F1BAC"}.mdi-office-building-remove-outline::before{content:"\F1BAD"}.mdi-oil::before{content:"\F03C7"}.mdi-oil-lamp::before{content:"\F0F19"}.mdi-oil-level::before{content:"\F1053"}.mdi-oil-temperature::before{content:"\F0FF8"}.mdi-om::before{content:"\F0973"}.mdi-omega::before{content:"\F03C9"}.mdi-one-up::before{content:"\F0BAD"}.mdi-onepassword::before{content:"\F0881"}.mdi-opacity::before{content:"\F05CC"}.mdi-open-in-app::before{content:"\F03CB"}.mdi-open-in-new::before{content:"\F03CC"}.mdi-open-source-initiative::before{content:"\F0BAE"}.mdi-openid::before{content:"\F03CD"}.mdi-opera::before{content:"\F03CE"}.mdi-orbit::before{content:"\F0018"}.mdi-orbit-variant::before{content:"\F15DB"}.mdi-order-alphabetical-ascending::before{content:"\F020D"}.mdi-order-alphabetical-descending::before{content:"\F0D07"}.mdi-order-bool-ascending::before{content:"\F02BE"}.mdi-order-bool-ascending-variant::before{content:"\F098F"}.mdi-order-bool-descending::before{content:"\F1384"}.mdi-order-bool-descending-variant::before{content:"\F0990"}.mdi-order-numeric-ascending::before{content:"\F0545"}.mdi-order-numeric-descending::before{content:"\F0546"}.mdi-origin::before{content:"\F0B43"}.mdi-ornament::before{content:"\F03CF"}.mdi-ornament-variant::before{content:"\F03D0"}.mdi-outdoor-lamp::before{content:"\F1054"}.mdi-overscan::before{content:"\F1005"}.mdi-owl::before{content:"\F03D2"}.mdi-pac-man::before{content:"\F0BAF"}.mdi-package::before{content:"\F03D3"}.mdi-package-check::before{content:"\F1B51"}.mdi-package-down::before{content:"\F03D4"}.mdi-package-up::before{content:"\F03D5"}.mdi-package-variant::before{content:"\F03D6"}.mdi-package-variant-closed::before{content:"\F03D7"}.mdi-package-variant-closed-check::before{content:"\F1B52"}.mdi-package-variant-closed-minus::before{content:"\F19D4"}.mdi-package-variant-closed-plus::before{content:"\F19D5"}.mdi-package-variant-closed-remove::before{content:"\F19D6"}.mdi-package-variant-minus::before{content:"\F19D7"}.mdi-package-variant-plus::before{content:"\F19D8"}.mdi-package-variant-remove::before{content:"\F19D9"}.mdi-page-first::before{content:"\F0600"}.mdi-page-last::before{content:"\F0601"}.mdi-page-layout-body::before{content:"\F06FA"}.mdi-page-layout-footer::before{content:"\F06FB"}.mdi-page-layout-header::before{content:"\F06FC"}.mdi-page-layout-header-footer::before{content:"\F0F7F"}.mdi-page-layout-sidebar-left::before{content:"\F06FD"}.mdi-page-layout-sidebar-right::before{content:"\F06FE"}.mdi-page-next::before{content:"\F0BB0"}.mdi-page-next-outline::before{content:"\F0BB1"}.mdi-page-previous::before{content:"\F0BB2"}.mdi-page-previous-outline::before{content:"\F0BB3"}.mdi-pail::before{content:"\F1417"}.mdi-pail-minus::before{content:"\F1437"}.mdi-pail-minus-outline::before{content:"\F143C"}.mdi-pail-off::before{content:"\F1439"}.mdi-pail-off-outline::before{content:"\F143E"}.mdi-pail-outline::before{content:"\F143A"}.mdi-pail-plus::before{content:"\F1436"}.mdi-pail-plus-outline::before{content:"\F143B"}.mdi-pail-remove::before{content:"\F1438"}.mdi-pail-remove-outline::before{content:"\F143D"}.mdi-palette::before{content:"\F03D8"}.mdi-palette-advanced::before{content:"\F03D9"}.mdi-palette-outline::before{content:"\F0E0C"}.mdi-palette-swatch::before{content:"\F08B5"}.mdi-palette-swatch-outline::before{content:"\F135C"}.mdi-palette-swatch-variant::before{content:"\F195A"}.mdi-palm-tree::before{content:"\F1055"}.mdi-pan::before{content:"\F0BB4"}.mdi-pan-bottom-left::before{content:"\F0BB5"}.mdi-pan-bottom-right::before{content:"\F0BB6"}.mdi-pan-down::before{content:"\F0BB7"}.mdi-pan-horizontal::before{content:"\F0BB8"}.mdi-pan-left::before{content:"\F0BB9"}.mdi-pan-right::before{content:"\F0BBA"}.mdi-pan-top-left::before{content:"\F0BBB"}.mdi-pan-top-right::before{content:"\F0BBC"}.mdi-pan-up::before{content:"\F0BBD"}.mdi-pan-vertical::before{content:"\F0BBE"}.mdi-panda::before{content:"\F03DA"}.mdi-pandora::before{content:"\F03DB"}.mdi-panorama::before{content:"\F03DC"}.mdi-panorama-fisheye::before{content:"\F03DD"}.mdi-panorama-horizontal::before{content:"\F1928"}.mdi-panorama-horizontal-outline::before{content:"\F03DE"}.mdi-panorama-outline::before{content:"\F198C"}.mdi-panorama-sphere::before{content:"\F198D"}.mdi-panorama-sphere-outline::before{content:"\F198E"}.mdi-panorama-variant::before{content:"\F198F"}.mdi-panorama-variant-outline::before{content:"\F1990"}.mdi-panorama-vertical::before{content:"\F1929"}.mdi-panorama-vertical-outline::before{content:"\F03DF"}.mdi-panorama-wide-angle::before{content:"\F195F"}.mdi-panorama-wide-angle-outline::before{content:"\F03E0"}.mdi-paper-cut-vertical::before{content:"\F03E1"}.mdi-paper-roll::before{content:"\F1157"}.mdi-paper-roll-outline::before{content:"\F1158"}.mdi-paperclip::before{content:"\F03E2"}.mdi-paperclip-check::before{content:"\F1AC6"}.mdi-paperclip-lock::before{content:"\F19DA"}.mdi-paperclip-minus::before{content:"\F1AC7"}.mdi-paperclip-off::before{content:"\F1AC8"}.mdi-paperclip-plus::before{content:"\F1AC9"}.mdi-paperclip-remove::before{content:"\F1ACA"}.mdi-parachute::before{content:"\F0CB4"}.mdi-parachute-outline::before{content:"\F0CB5"}.mdi-paragliding::before{content:"\F1745"}.mdi-parking::before{content:"\F03E3"}.mdi-party-popper::before{content:"\F1056"}.mdi-passport::before{content:"\F07E3"}.mdi-passport-alert::before{content:"\F1CB8"}.mdi-passport-biometric::before{content:"\F0DE1"}.mdi-passport-cancel::before{content:"\F1CB9"}.mdi-passport-check::before{content:"\F1CBA"}.mdi-passport-minus::before{content:"\F1CBB"}.mdi-passport-plus::before{content:"\F1CBC"}.mdi-passport-remove::before{content:"\F1CBD"}.mdi-pasta::before{content:"\F1160"}.mdi-patio-heater::before{content:"\F0F80"}.mdi-patreon::before{content:"\F0882"}.mdi-pause::before{content:"\F03E4"}.mdi-pause-box::before{content:"\F00BC"}.mdi-pause-box-outline::before{content:"\F1B7A"}.mdi-pause-circle::before{content:"\F03E5"}.mdi-pause-circle-outline::before{content:"\F03E6"}.mdi-pause-octagon::before{content:"\F03E7"}.mdi-pause-octagon-outline::before{content:"\F03E8"}.mdi-paw::before{content:"\F03E9"}.mdi-paw-off::before{content:"\F0657"}.mdi-paw-off-outline::before{content:"\F1676"}.mdi-paw-outline::before{content:"\F1675"}.mdi-peace::before{content:"\F0884"}.mdi-peanut::before{content:"\F0FFC"}.mdi-peanut-off::before{content:"\F0FFD"}.mdi-peanut-off-outline::before{content:"\F0FFF"}.mdi-peanut-outline::before{content:"\F0FFE"}.mdi-pen::before{content:"\F03EA"}.mdi-pen-lock::before{content:"\F0DE2"}.mdi-pen-minus::before{content:"\F0DE3"}.mdi-pen-off::before{content:"\F0DE4"}.mdi-pen-plus::before{content:"\F0DE5"}.mdi-pen-remove::before{content:"\F0DE6"}.mdi-pencil::before{content:"\F03EB"}.mdi-pencil-box::before{content:"\F03EC"}.mdi-pencil-box-multiple::before{content:"\F1144"}.mdi-pencil-box-multiple-outline::before{content:"\F1145"}.mdi-pencil-box-outline::before{content:"\F03ED"}.mdi-pencil-circle::before{content:"\F06FF"}.mdi-pencil-circle-outline::before{content:"\F0776"}.mdi-pencil-lock::before{content:"\F03EE"}.mdi-pencil-lock-outline::before{content:"\F0DE7"}.mdi-pencil-minus::before{content:"\F0DE8"}.mdi-pencil-minus-outline::before{content:"\F0DE9"}.mdi-pencil-off::before{content:"\F03EF"}.mdi-pencil-off-outline::before{content:"\F0DEA"}.mdi-pencil-outline::before{content:"\F0CB6"}.mdi-pencil-plus::before{content:"\F0DEB"}.mdi-pencil-plus-outline::before{content:"\F0DEC"}.mdi-pencil-remove::before{content:"\F0DED"}.mdi-pencil-remove-outline::before{content:"\F0DEE"}.mdi-pencil-ruler::before{content:"\F1353"}.mdi-pencil-ruler-outline::before{content:"\F1C11"}.mdi-penguin::before{content:"\F0EC0"}.mdi-pentagon::before{content:"\F0701"}.mdi-pentagon-outline::before{content:"\F0700"}.mdi-pentagram::before{content:"\F1667"}.mdi-percent::before{content:"\F03F0"}.mdi-percent-box::before{content:"\F1A02"}.mdi-percent-box-outline::before{content:"\F1A03"}.mdi-percent-circle::before{content:"\F1A04"}.mdi-percent-circle-outline::before{content:"\F1A05"}.mdi-percent-outline::before{content:"\F1278"}.mdi-periodic-table::before{content:"\F08B6"}.mdi-perspective-less::before{content:"\F0D23"}.mdi-perspective-more::before{content:"\F0D24"}.mdi-ph::before{content:"\F17C5"}.mdi-phone::before{content:"\F03F2"}.mdi-phone-alert::before{content:"\F0F1A"}.mdi-phone-alert-outline::before{content:"\F118E"}.mdi-phone-bluetooth::before{content:"\F03F3"}.mdi-phone-bluetooth-outline::before{content:"\F118F"}.mdi-phone-cancel::before{content:"\F10BC"}.mdi-phone-cancel-outline::before{content:"\F1190"}.mdi-phone-check::before{content:"\F11A9"}.mdi-phone-check-outline::before{content:"\F11AA"}.mdi-phone-classic::before{content:"\F0602"}.mdi-phone-classic-off::before{content:"\F1279"}.mdi-phone-clock::before{content:"\F19DB"}.mdi-phone-dial::before{content:"\F1559"}.mdi-phone-dial-outline::before{content:"\F155A"}.mdi-phone-forward::before{content:"\F03F4"}.mdi-phone-forward-outline::before{content:"\F1191"}.mdi-phone-hangup::before{content:"\F03F5"}.mdi-phone-hangup-outline::before{content:"\F1192"}.mdi-phone-in-talk::before{content:"\F03F6"}.mdi-phone-in-talk-outline::before{content:"\F1182"}.mdi-phone-incoming::before{content:"\F03F7"}.mdi-phone-incoming-outgoing::before{content:"\F1B3F"}.mdi-phone-incoming-outgoing-outline::before{content:"\F1B40"}.mdi-phone-incoming-outline::before{content:"\F1193"}.mdi-phone-lock::before{content:"\F03F8"}.mdi-phone-lock-outline::before{content:"\F1194"}.mdi-phone-log::before{content:"\F03F9"}.mdi-phone-log-outline::before{content:"\F1195"}.mdi-phone-message::before{content:"\F1196"}.mdi-phone-message-outline::before{content:"\F1197"}.mdi-phone-minus::before{content:"\F0658"}.mdi-phone-minus-outline::before{content:"\F1198"}.mdi-phone-missed::before{content:"\F03FA"}.mdi-phone-missed-outline::before{content:"\F11A5"}.mdi-phone-off::before{content:"\F0DEF"}.mdi-phone-off-outline::before{content:"\F11A6"}.mdi-phone-outgoing::before{content:"\F03FB"}.mdi-phone-outgoing-outline::before{content:"\F1199"}.mdi-phone-outline::before{content:"\F0DF0"}.mdi-phone-paused::before{content:"\F03FC"}.mdi-phone-paused-outline::before{content:"\F119A"}.mdi-phone-plus::before{content:"\F0659"}.mdi-phone-plus-outline::before{content:"\F119B"}.mdi-phone-refresh::before{content:"\F1993"}.mdi-phone-refresh-outline::before{content:"\F1994"}.mdi-phone-remove::before{content:"\F152F"}.mdi-phone-remove-outline::before{content:"\F1530"}.mdi-phone-return::before{content:"\F082F"}.mdi-phone-return-outline::before{content:"\F119C"}.mdi-phone-ring::before{content:"\F11AB"}.mdi-phone-ring-outline::before{content:"\F11AC"}.mdi-phone-rotate-landscape::before{content:"\F0885"}.mdi-phone-rotate-portrait::before{content:"\F0886"}.mdi-phone-settings::before{content:"\F03FD"}.mdi-phone-settings-outline::before{content:"\F119D"}.mdi-phone-sync::before{content:"\F1995"}.mdi-phone-sync-outline::before{content:"\F1996"}.mdi-phone-voip::before{content:"\F03FE"}.mdi-pi::before{content:"\F03FF"}.mdi-pi-box::before{content:"\F0400"}.mdi-pi-hole::before{content:"\F0DF1"}.mdi-piano::before{content:"\F067D"}.mdi-piano-off::before{content:"\F0698"}.mdi-pickaxe::before{content:"\F08B7"}.mdi-picture-in-picture-bottom-right::before{content:"\F0E57"}.mdi-picture-in-picture-bottom-right-outline::before{content:"\F0E58"}.mdi-picture-in-picture-top-right::before{content:"\F0E59"}.mdi-picture-in-picture-top-right-outline::before{content:"\F0E5A"}.mdi-pier::before{content:"\F0887"}.mdi-pier-crane::before{content:"\F0888"}.mdi-pig::before{content:"\F0401"}.mdi-pig-variant::before{content:"\F1006"}.mdi-pig-variant-outline::before{content:"\F1678"}.mdi-piggy-bank::before{content:"\F1007"}.mdi-piggy-bank-outline::before{content:"\F1679"}.mdi-pill::before{content:"\F0402"}.mdi-pill-multiple::before{content:"\F1B4C"}.mdi-pill-off::before{content:"\F1A5C"}.mdi-pillar::before{content:"\F0702"}.mdi-pin::before{content:"\F0403"}.mdi-pin-off::before{content:"\F0404"}.mdi-pin-off-outline::before{content:"\F0930"}.mdi-pin-outline::before{content:"\F0931"}.mdi-pine-tree::before{content:"\F0405"}.mdi-pine-tree-box::before{content:"\F0406"}.mdi-pine-tree-fire::before{content:"\F141A"}.mdi-pine-tree-variant::before{content:"\F1C73"}.mdi-pine-tree-variant-outline::before{content:"\F1C74"}.mdi-pinterest::before{content:"\F0407"}.mdi-pinwheel::before{content:"\F0AD5"}.mdi-pinwheel-outline::before{content:"\F0AD6"}.mdi-pipe::before{content:"\F07E5"}.mdi-pipe-disconnected::before{content:"\F07E6"}.mdi-pipe-leak::before{content:"\F0889"}.mdi-pipe-valve::before{content:"\F184D"}.mdi-pipe-wrench::before{content:"\F1354"}.mdi-pirate::before{content:"\F0A08"}.mdi-pistol::before{content:"\F0703"}.mdi-piston::before{content:"\F088A"}.mdi-pitchfork::before{content:"\F1553"}.mdi-pizza::before{content:"\F0409"}.mdi-plane-car::before{content:"\F1AFF"}.mdi-plane-train::before{content:"\F1B00"}.mdi-play::before{content:"\F040A"}.mdi-play-box::before{content:"\F127A"}.mdi-play-box-edit-outline::before{content:"\F1C3A"}.mdi-play-box-lock::before{content:"\F1A16"}.mdi-play-box-lock-open::before{content:"\F1A17"}.mdi-play-box-lock-open-outline::before{content:"\F1A18"}.mdi-play-box-lock-outline::before{content:"\F1A19"}.mdi-play-box-multiple::before{content:"\F0D19"}.mdi-play-box-multiple-outline::before{content:"\F13E6"}.mdi-play-box-outline::before{content:"\F040B"}.mdi-play-circle::before{content:"\F040C"}.mdi-play-circle-outline::before{content:"\F040D"}.mdi-play-network::before{content:"\F088B"}.mdi-play-network-outline::before{content:"\F0CB7"}.mdi-play-outline::before{content:"\F0F1B"}.mdi-play-pause::before{content:"\F040E"}.mdi-play-protected-content::before{content:"\F040F"}.mdi-play-speed::before{content:"\F08FF"}.mdi-playlist-check::before{content:"\F05C7"}.mdi-playlist-edit::before{content:"\F0900"}.mdi-playlist-minus::before{content:"\F0410"}.mdi-playlist-music::before{content:"\F0CB8"}.mdi-playlist-music-outline::before{content:"\F0CB9"}.mdi-playlist-play::before{content:"\F0411"}.mdi-playlist-plus::before{content:"\F0412"}.mdi-playlist-remove::before{content:"\F0413"}.mdi-playlist-star::before{content:"\F0DF2"}.mdi-plex::before{content:"\F06BA"}.mdi-pliers::before{content:"\F19A4"}.mdi-plus::before{content:"\F0415"}.mdi-plus-box::before{content:"\F0416"}.mdi-plus-box-multiple::before{content:"\F0334"}.mdi-plus-box-multiple-outline::before{content:"\F1143"}.mdi-plus-box-outline::before{content:"\F0704"}.mdi-plus-circle::before{content:"\F0417"}.mdi-plus-circle-multiple::before{content:"\F034C"}.mdi-plus-circle-multiple-outline::before{content:"\F0418"}.mdi-plus-circle-outline::before{content:"\F0419"}.mdi-plus-lock::before{content:"\F1A5D"}.mdi-plus-lock-open::before{content:"\F1A5E"}.mdi-plus-minus::before{content:"\F0992"}.mdi-plus-minus-box::before{content:"\F0993"}.mdi-plus-minus-variant::before{content:"\F14C9"}.mdi-plus-network::before{content:"\F041A"}.mdi-plus-network-outline::before{content:"\F0CBA"}.mdi-plus-outline::before{content:"\F0705"}.mdi-plus-thick::before{content:"\F11EC"}.mdi-pocket::before{content:"\F1CBE"}.mdi-podcast::before{content:"\F0994"}.mdi-podium::before{content:"\F0D25"}.mdi-podium-bronze::before{content:"\F0D26"}.mdi-podium-gold::before{content:"\F0D27"}.mdi-podium-silver::before{content:"\F0D28"}.mdi-point-of-sale::before{content:"\F0D92"}.mdi-pokeball::before{content:"\F041D"}.mdi-pokemon-go::before{content:"\F0A09"}.mdi-poker-chip::before{content:"\F0830"}.mdi-polaroid::before{content:"\F041E"}.mdi-police-badge::before{content:"\F1167"}.mdi-police-badge-outline::before{content:"\F1168"}.mdi-police-station::before{content:"\F1839"}.mdi-poll::before{content:"\F041F"}.mdi-polo::before{content:"\F14C3"}.mdi-polymer::before{content:"\F0421"}.mdi-pool::before{content:"\F0606"}.mdi-pool-thermometer::before{content:"\F1A5F"}.mdi-popcorn::before{content:"\F0422"}.mdi-post::before{content:"\F1008"}.mdi-post-lamp::before{content:"\F1A60"}.mdi-post-outline::before{content:"\F1009"}.mdi-postage-stamp::before{content:"\F0CBB"}.mdi-pot::before{content:"\F02E5"}.mdi-pot-mix::before{content:"\F065B"}.mdi-pot-mix-outline::before{content:"\F0677"}.mdi-pot-outline::before{content:"\F02FF"}.mdi-pot-steam::before{content:"\F065A"}.mdi-pot-steam-outline::before{content:"\F0326"}.mdi-pound::before{content:"\F0423"}.mdi-pound-box::before{content:"\F0424"}.mdi-pound-box-outline::before{content:"\F117F"}.mdi-power::before{content:"\F0425"}.mdi-power-cycle::before{content:"\F0901"}.mdi-power-off::before{content:"\F0902"}.mdi-power-on::before{content:"\F0903"}.mdi-power-plug::before{content:"\F06A5"}.mdi-power-plug-battery::before{content:"\F1C3B"}.mdi-power-plug-battery-outline::before{content:"\F1C3C"}.mdi-power-plug-off::before{content:"\F06A6"}.mdi-power-plug-off-outline::before{content:"\F1424"}.mdi-power-plug-outline::before{content:"\F1425"}.mdi-power-settings::before{content:"\F0426"}.mdi-power-sleep::before{content:"\F0904"}.mdi-power-socket::before{content:"\F0427"}.mdi-power-socket-au::before{content:"\F0905"}.mdi-power-socket-ch::before{content:"\F0FB3"}.mdi-power-socket-de::before{content:"\F1107"}.mdi-power-socket-eu::before{content:"\F07E7"}.mdi-power-socket-fr::before{content:"\F1108"}.mdi-power-socket-it::before{content:"\F14FF"}.mdi-power-socket-jp::before{content:"\F1109"}.mdi-power-socket-uk::before{content:"\F07E8"}.mdi-power-socket-us::before{content:"\F07E9"}.mdi-power-standby::before{content:"\F0906"}.mdi-powershell::before{content:"\F0A0A"}.mdi-prescription::before{content:"\F0706"}.mdi-presentation::before{content:"\F0428"}.mdi-presentation-play::before{content:"\F0429"}.mdi-pretzel::before{content:"\F1562"}.mdi-printer::before{content:"\F042A"}.mdi-printer-3d::before{content:"\F042B"}.mdi-printer-3d-nozzle::before{content:"\F0E5B"}.mdi-printer-3d-nozzle-alert::before{content:"\F11C0"}.mdi-printer-3d-nozzle-alert-outline::before{content:"\F11C1"}.mdi-printer-3d-nozzle-heat::before{content:"\F18B8"}.mdi-printer-3d-nozzle-heat-outline::before{content:"\F18B9"}.mdi-printer-3d-nozzle-off::before{content:"\F1B19"}.mdi-printer-3d-nozzle-off-outline::before{content:"\F1B1A"}.mdi-printer-3d-nozzle-outline::before{content:"\F0E5C"}.mdi-printer-3d-off::before{content:"\F1B0E"}.mdi-printer-alert::before{content:"\F042C"}.mdi-printer-check::before{content:"\F1146"}.mdi-printer-eye::before{content:"\F1458"}.mdi-printer-off::before{content:"\F0E5D"}.mdi-printer-off-outline::before{content:"\F1785"}.mdi-printer-outline::before{content:"\F1786"}.mdi-printer-pos::before{content:"\F1057"}.mdi-printer-pos-alert::before{content:"\F1BBC"}.mdi-printer-pos-alert-outline::before{content:"\F1BBD"}.mdi-printer-pos-cancel::before{content:"\F1BBE"}.mdi-printer-pos-cancel-outline::before{content:"\F1BBF"}.mdi-printer-pos-check::before{content:"\F1BC0"}.mdi-printer-pos-check-outline::before{content:"\F1BC1"}.mdi-printer-pos-cog::before{content:"\F1BC2"}.mdi-printer-pos-cog-outline::before{content:"\F1BC3"}.mdi-printer-pos-edit::before{content:"\F1BC4"}.mdi-printer-pos-edit-outline::before{content:"\F1BC5"}.mdi-printer-pos-minus::before{content:"\F1BC6"}.mdi-printer-pos-minus-outline::before{content:"\F1BC7"}.mdi-printer-pos-network::before{content:"\F1BC8"}.mdi-printer-pos-network-outline::before{content:"\F1BC9"}.mdi-printer-pos-off::before{content:"\F1BCA"}.mdi-printer-pos-off-outline::before{content:"\F1BCB"}.mdi-printer-pos-outline::before{content:"\F1BCC"}.mdi-printer-pos-pause::before{content:"\F1BCD"}.mdi-printer-pos-pause-outline::before{content:"\F1BCE"}.mdi-printer-pos-play::before{content:"\F1BCF"}.mdi-printer-pos-play-outline::before{content:"\F1BD0"}.mdi-printer-pos-plus::before{content:"\F1BD1"}.mdi-printer-pos-plus-outline::before{content:"\F1BD2"}.mdi-printer-pos-refresh::before{content:"\F1BD3"}.mdi-printer-pos-refresh-outline::before{content:"\F1BD4"}.mdi-printer-pos-remove::before{content:"\F1BD5"}.mdi-printer-pos-remove-outline::before{content:"\F1BD6"}.mdi-printer-pos-star::before{content:"\F1BD7"}.mdi-printer-pos-star-outline::before{content:"\F1BD8"}.mdi-printer-pos-stop::before{content:"\F1BD9"}.mdi-printer-pos-stop-outline::before{content:"\F1BDA"}.mdi-printer-pos-sync::before{content:"\F1BDB"}.mdi-printer-pos-sync-outline::before{content:"\F1BDC"}.mdi-printer-pos-wrench::before{content:"\F1BDD"}.mdi-printer-pos-wrench-outline::before{content:"\F1BDE"}.mdi-printer-search::before{content:"\F1457"}.mdi-printer-settings::before{content:"\F0707"}.mdi-printer-wireless::before{content:"\F0A0B"}.mdi-priority-high::before{content:"\F0603"}.mdi-priority-low::before{content:"\F0604"}.mdi-professional-hexagon::before{content:"\F042D"}.mdi-progress-alert::before{content:"\F0CBC"}.mdi-progress-check::before{content:"\F0995"}.mdi-progress-clock::before{content:"\F0996"}.mdi-progress-close::before{content:"\F110A"}.mdi-progress-download::before{content:"\F0997"}.mdi-progress-helper::before{content:"\F1BA2"}.mdi-progress-pencil::before{content:"\F1787"}.mdi-progress-question::before{content:"\F1522"}.mdi-progress-star::before{content:"\F1788"}.mdi-progress-star-four-points::before{content:"\F1C3D"}.mdi-progress-tag::before{content:"\F1D0D"}.mdi-progress-upload::before{content:"\F0998"}.mdi-progress-wrench::before{content:"\F0CBD"}.mdi-projector::before{content:"\F042E"}.mdi-projector-off::before{content:"\F1A23"}.mdi-projector-screen::before{content:"\F042F"}.mdi-projector-screen-off::before{content:"\F180D"}.mdi-projector-screen-off-outline::before{content:"\F180E"}.mdi-projector-screen-outline::before{content:"\F1724"}.mdi-projector-screen-variant::before{content:"\F180F"}.mdi-projector-screen-variant-off::before{content:"\F1810"}.mdi-projector-screen-variant-off-outline::before{content:"\F1811"}.mdi-projector-screen-variant-outline::before{content:"\F1812"}.mdi-propane-tank::before{content:"\F1357"}.mdi-propane-tank-outline::before{content:"\F1358"}.mdi-protocol::before{content:"\F0FD8"}.mdi-publish::before{content:"\F06A7"}.mdi-publish-off::before{content:"\F1945"}.mdi-pulse::before{content:"\F0430"}.mdi-pump::before{content:"\F1402"}.mdi-pump-off::before{content:"\F1B22"}.mdi-pumpkin::before{content:"\F0BBF"}.mdi-purse::before{content:"\F0F1C"}.mdi-purse-outline::before{content:"\F0F1D"}.mdi-puzzle::before{content:"\F0431"}.mdi-puzzle-check::before{content:"\F1426"}.mdi-puzzle-check-outline::before{content:"\F1427"}.mdi-puzzle-edit::before{content:"\F14D3"}.mdi-puzzle-edit-outline::before{content:"\F14D9"}.mdi-puzzle-heart::before{content:"\F14D4"}.mdi-puzzle-heart-outline::before{content:"\F14DA"}.mdi-puzzle-minus::before{content:"\F14D1"}.mdi-puzzle-minus-outline::before{content:"\F14D7"}.mdi-puzzle-outline::before{content:"\F0A66"}.mdi-puzzle-plus::before{content:"\F14D0"}.mdi-puzzle-plus-outline::before{content:"\F14D6"}.mdi-puzzle-remove::before{content:"\F14D2"}.mdi-puzzle-remove-outline::before{content:"\F14D8"}.mdi-puzzle-star::before{content:"\F14D5"}.mdi-puzzle-star-outline::before{content:"\F14DB"}.mdi-pyramid::before{content:"\F1952"}.mdi-pyramid-off::before{content:"\F1953"}.mdi-qi::before{content:"\F0999"}.mdi-qqchat::before{content:"\F0605"}.mdi-qrcode::before{content:"\F0432"}.mdi-qrcode-edit::before{content:"\F08B8"}.mdi-qrcode-minus::before{content:"\F118C"}.mdi-qrcode-plus::before{content:"\F118B"}.mdi-qrcode-remove::before{content:"\F118D"}.mdi-qrcode-scan::before{content:"\F0433"}.mdi-quadcopter::before{content:"\F0434"}.mdi-quality-high::before{content:"\F0435"}.mdi-quality-low::before{content:"\F0A0C"}.mdi-quality-medium::before{content:"\F0A0D"}.mdi-queue-first-in-last-out::before{content:"\F1CAF"}.mdi-quora::before{content:"\F0D29"}.mdi-rabbit::before{content:"\F0907"}.mdi-rabbit-variant::before{content:"\F1A61"}.mdi-rabbit-variant-outline::before{content:"\F1A62"}.mdi-racing-helmet::before{content:"\F0D93"}.mdi-racquetball::before{content:"\F0D94"}.mdi-radar::before{content:"\F0437"}.mdi-radiator::before{content:"\F0438"}.mdi-radiator-disabled::before{content:"\F0AD7"}.mdi-radiator-off::before{content:"\F0AD8"}.mdi-radio::before{content:"\F0439"}.mdi-radio-am::before{content:"\F0CBE"}.mdi-radio-fm::before{content:"\F0CBF"}.mdi-radio-handheld::before{content:"\F043A"}.mdi-radio-off::before{content:"\F121C"}.mdi-radio-tower::before{content:"\F043B"}.mdi-radioactive::before{content:"\F043C"}.mdi-radioactive-circle::before{content:"\F185D"}.mdi-radioactive-circle-outline::before{content:"\F185E"}.mdi-radioactive-off::before{content:"\F0EC1"}.mdi-radiobox-blank::before{content:"\F043D"}.mdi-radiobox-indeterminate-variant::before{content:"\F1C5E"}.mdi-radiobox-marked::before{content:"\F043E"}.mdi-radiology-box::before{content:"\F14C5"}.mdi-radiology-box-outline::before{content:"\F14C6"}.mdi-radius::before{content:"\F0CC0"}.mdi-radius-outline::before{content:"\F0CC1"}.mdi-railroad-light::before{content:"\F0F1E"}.mdi-rake::before{content:"\F1544"}.mdi-raspberry-pi::before{content:"\F043F"}.mdi-raw::before{content:"\F1A0F"}.mdi-raw-off::before{content:"\F1A10"}.mdi-ray-end::before{content:"\F0440"}.mdi-ray-end-arrow::before{content:"\F0441"}.mdi-ray-start::before{content:"\F0442"}.mdi-ray-start-arrow::before{content:"\F0443"}.mdi-ray-start-end::before{content:"\F0444"}.mdi-ray-start-vertex-end::before{content:"\F15D8"}.mdi-ray-vertex::before{content:"\F0445"}.mdi-razor-double-edge::before{content:"\F1997"}.mdi-razor-single-edge::before{content:"\F1998"}.mdi-react::before{content:"\F0708"}.mdi-read::before{content:"\F0447"}.mdi-receipt::before{content:"\F0824"}.mdi-receipt-clock::before{content:"\F1C3E"}.mdi-receipt-clock-outline::before{content:"\F1C3F"}.mdi-receipt-outline::before{content:"\F04F7"}.mdi-receipt-send::before{content:"\F1C40"}.mdi-receipt-send-outline::before{content:"\F1C41"}.mdi-receipt-text::before{content:"\F0449"}.mdi-receipt-text-arrow-left::before{content:"\F1C42"}.mdi-receipt-text-arrow-left-outline::before{content:"\F1C43"}.mdi-receipt-text-arrow-right::before{content:"\F1C44"}.mdi-receipt-text-arrow-right-outline::before{content:"\F1C45"}.mdi-receipt-text-check::before{content:"\F1A63"}.mdi-receipt-text-check-outline::before{content:"\F1A64"}.mdi-receipt-text-clock::before{content:"\F1C46"}.mdi-receipt-text-clock-outline::before{content:"\F1C47"}.mdi-receipt-text-edit::before{content:"\F1C48"}.mdi-receipt-text-edit-outline::before{content:"\F1C49"}.mdi-receipt-text-minus::before{content:"\F1A65"}.mdi-receipt-text-minus-outline::before{content:"\F1A66"}.mdi-receipt-text-outline::before{content:"\F19DC"}.mdi-receipt-text-plus::before{content:"\F1A67"}.mdi-receipt-text-plus-outline::before{content:"\F1A68"}.mdi-receipt-text-remove::before{content:"\F1A69"}.mdi-receipt-text-remove-outline::before{content:"\F1A6A"}.mdi-receipt-text-send::before{content:"\F1C4A"}.mdi-receipt-text-send-outline::before{content:"\F1C4B"}.mdi-record::before{content:"\F044A"}.mdi-record-circle::before{content:"\F0EC2"}.mdi-record-circle-outline::before{content:"\F0EC3"}.mdi-record-player::before{content:"\F099A"}.mdi-record-rec::before{content:"\F044B"}.mdi-rectangle::before{content:"\F0E5E"}.mdi-rectangle-outline::before{content:"\F0E5F"}.mdi-recycle::before{content:"\F044C"}.mdi-recycle-variant::before{content:"\F139D"}.mdi-reddit::before{content:"\F044D"}.mdi-redhat::before{content:"\F111B"}.mdi-redo::before{content:"\F044E"}.mdi-redo-variant::before{content:"\F044F"}.mdi-reflect-horizontal::before{content:"\F0A0E"}.mdi-reflect-vertical::before{content:"\F0A0F"}.mdi-refresh::before{content:"\F0450"}.mdi-refresh-auto::before{content:"\F18F2"}.mdi-refresh-circle::before{content:"\F1377"}.mdi-regex::before{content:"\F0451"}.mdi-registered-trademark::before{content:"\F0A67"}.mdi-reiterate::before{content:"\F1588"}.mdi-relation-many-to-many::before{content:"\F1496"}.mdi-relation-many-to-one::before{content:"\F1497"}.mdi-relation-many-to-one-or-many::before{content:"\F1498"}.mdi-relation-many-to-only-one::before{content:"\F1499"}.mdi-relation-many-to-zero-or-many::before{content:"\F149A"}.mdi-relation-many-to-zero-or-one::before{content:"\F149B"}.mdi-relation-one-or-many-to-many::before{content:"\F149C"}.mdi-relation-one-or-many-to-one::before{content:"\F149D"}.mdi-relation-one-or-many-to-one-or-many::before{content:"\F149E"}.mdi-relation-one-or-many-to-only-one::before{content:"\F149F"}.mdi-relation-one-or-many-to-zero-or-many::before{content:"\F14A0"}.mdi-relation-one-or-many-to-zero-or-one::before{content:"\F14A1"}.mdi-relation-one-to-many::before{content:"\F14A2"}.mdi-relation-one-to-one::before{content:"\F14A3"}.mdi-relation-one-to-one-or-many::before{content:"\F14A4"}.mdi-relation-one-to-only-one::before{content:"\F14A5"}.mdi-relation-one-to-zero-or-many::before{content:"\F14A6"}.mdi-relation-one-to-zero-or-one::before{content:"\F14A7"}.mdi-relation-only-one-to-many::before{content:"\F14A8"}.mdi-relation-only-one-to-one::before{content:"\F14A9"}.mdi-relation-only-one-to-one-or-many::before{content:"\F14AA"}.mdi-relation-only-one-to-only-one::before{content:"\F14AB"}.mdi-relation-only-one-to-zero-or-many::before{content:"\F14AC"}.mdi-relation-only-one-to-zero-or-one::before{content:"\F14AD"}.mdi-relation-zero-or-many-to-many::before{content:"\F14AE"}.mdi-relation-zero-or-many-to-one::before{content:"\F14AF"}.mdi-relation-zero-or-many-to-one-or-many::before{content:"\F14B0"}.mdi-relation-zero-or-many-to-only-one::before{content:"\F14B1"}.mdi-relation-zero-or-many-to-zero-or-many::before{content:"\F14B2"}.mdi-relation-zero-or-many-to-zero-or-one::before{content:"\F14B3"}.mdi-relation-zero-or-one-to-many::before{content:"\F14B4"}.mdi-relation-zero-or-one-to-one::before{content:"\F14B5"}.mdi-relation-zero-or-one-to-one-or-many::before{content:"\F14B6"}.mdi-relation-zero-or-one-to-only-one::before{content:"\F14B7"}.mdi-relation-zero-or-one-to-zero-or-many::before{content:"\F14B8"}.mdi-relation-zero-or-one-to-zero-or-one::before{content:"\F14B9"}.mdi-relative-scale::before{content:"\F0452"}.mdi-reload::before{content:"\F0453"}.mdi-reload-alert::before{content:"\F110B"}.mdi-reminder::before{content:"\F088C"}.mdi-remote::before{content:"\F0454"}.mdi-remote-desktop::before{content:"\F08B9"}.mdi-remote-off::before{content:"\F0EC4"}.mdi-remote-tv::before{content:"\F0EC5"}.mdi-remote-tv-off::before{content:"\F0EC6"}.mdi-rename::before{content:"\F1C18"}.mdi-rename-box::before{content:"\F0455"}.mdi-rename-box-outline::before{content:"\F1C19"}.mdi-rename-outline::before{content:"\F1C1A"}.mdi-reorder-horizontal::before{content:"\F0688"}.mdi-reorder-vertical::before{content:"\F0689"}.mdi-repeat::before{content:"\F0456"}.mdi-repeat-off::before{content:"\F0457"}.mdi-repeat-once::before{content:"\F0458"}.mdi-repeat-variant::before{content:"\F0547"}.mdi-replay::before{content:"\F0459"}.mdi-reply::before{content:"\F045A"}.mdi-reply-all::before{content:"\F045B"}.mdi-reply-all-outline::before{content:"\F0F1F"}.mdi-reply-circle::before{content:"\F11AE"}.mdi-reply-outline::before{content:"\F0F20"}.mdi-reproduction::before{content:"\F045C"}.mdi-resistor::before{content:"\F0B44"}.mdi-resistor-nodes::before{content:"\F0B45"}.mdi-resize::before{content:"\F0A68"}.mdi-resize-bottom-right::before{content:"\F045D"}.mdi-responsive::before{content:"\F045E"}.mdi-restart::before{content:"\F0709"}.mdi-restart-alert::before{content:"\F110C"}.mdi-restart-off::before{content:"\F0D95"}.mdi-restore::before{content:"\F099B"}.mdi-restore-alert::before{content:"\F110D"}.mdi-rewind::before{content:"\F045F"}.mdi-rewind-10::before{content:"\F0D2A"}.mdi-rewind-15::before{content:"\F1946"}.mdi-rewind-30::before{content:"\F0D96"}.mdi-rewind-45::before{content:"\F1B13"}.mdi-rewind-5::before{content:"\F11F9"}.mdi-rewind-60::before{content:"\F160C"}.mdi-rewind-outline::before{content:"\F070A"}.mdi-rhombus::before{content:"\F070B"}.mdi-rhombus-medium::before{content:"\F0A10"}.mdi-rhombus-medium-outline::before{content:"\F14DC"}.mdi-rhombus-outline::before{content:"\F070C"}.mdi-rhombus-split::before{content:"\F0A11"}.mdi-rhombus-split-outline::before{content:"\F14DD"}.mdi-ribbon::before{content:"\F0460"}.mdi-rice::before{content:"\F07EA"}.mdi-rickshaw::before{content:"\F15BB"}.mdi-rickshaw-electric::before{content:"\F15BC"}.mdi-ring::before{content:"\F07EB"}.mdi-rivet::before{content:"\F0E60"}.mdi-road::before{content:"\F0461"}.mdi-road-variant::before{content:"\F0462"}.mdi-robber::before{content:"\F1058"}.mdi-robot::before{content:"\F06A9"}.mdi-robot-angry::before{content:"\F169D"}.mdi-robot-angry-outline::before{content:"\F169E"}.mdi-robot-confused::before{content:"\F169F"}.mdi-robot-confused-outline::before{content:"\F16A0"}.mdi-robot-dead::before{content:"\F16A1"}.mdi-robot-dead-outline::before{content:"\F16A2"}.mdi-robot-excited::before{content:"\F16A3"}.mdi-robot-excited-outline::before{content:"\F16A4"}.mdi-robot-happy::before{content:"\F1719"}.mdi-robot-happy-outline::before{content:"\F171A"}.mdi-robot-industrial::before{content:"\F0B46"}.mdi-robot-industrial-outline::before{content:"\F1A1A"}.mdi-robot-love::before{content:"\F16A5"}.mdi-robot-love-outline::before{content:"\F16A6"}.mdi-robot-mower::before{content:"\F11F7"}.mdi-robot-mower-outline::before{content:"\F11F3"}.mdi-robot-off::before{content:"\F16A7"}.mdi-robot-off-outline::before{content:"\F167B"}.mdi-robot-outline::before{content:"\F167A"}.mdi-robot-vacuum::before{content:"\F070D"}.mdi-robot-vacuum-alert::before{content:"\F1B5D"}.mdi-robot-vacuum-off::before{content:"\F1C01"}.mdi-robot-vacuum-variant::before{content:"\F0908"}.mdi-robot-vacuum-variant-alert::before{content:"\F1B5E"}.mdi-robot-vacuum-variant-off::before{content:"\F1C02"}.mdi-rocket::before{content:"\F0463"}.mdi-rocket-launch::before{content:"\F14DE"}.mdi-rocket-launch-outline::before{content:"\F14DF"}.mdi-rocket-outline::before{content:"\F13AF"}.mdi-rodent::before{content:"\F1327"}.mdi-roller-shade::before{content:"\F1A6B"}.mdi-roller-shade-closed::before{content:"\F1A6C"}.mdi-roller-skate::before{content:"\F0D2B"}.mdi-roller-skate-off::before{content:"\F0145"}.mdi-rollerblade::before{content:"\F0D2C"}.mdi-rollerblade-off::before{content:"\F002E"}.mdi-rollupjs::before{content:"\F0BC0"}.mdi-rolodex::before{content:"\F1AB9"}.mdi-rolodex-outline::before{content:"\F1ABA"}.mdi-roman-numeral-1::before{content:"\F1088"}.mdi-roman-numeral-10::before{content:"\F1091"}.mdi-roman-numeral-2::before{content:"\F1089"}.mdi-roman-numeral-3::before{content:"\F108A"}.mdi-roman-numeral-4::before{content:"\F108B"}.mdi-roman-numeral-5::before{content:"\F108C"}.mdi-roman-numeral-6::before{content:"\F108D"}.mdi-roman-numeral-7::before{content:"\F108E"}.mdi-roman-numeral-8::before{content:"\F108F"}.mdi-roman-numeral-9::before{content:"\F1090"}.mdi-room-service::before{content:"\F088D"}.mdi-room-service-outline::before{content:"\F0D97"}.mdi-rotate-360::before{content:"\F1999"}.mdi-rotate-3d::before{content:"\F0EC7"}.mdi-rotate-3d-variant::before{content:"\F0464"}.mdi-rotate-left::before{content:"\F0465"}.mdi-rotate-left-variant::before{content:"\F0466"}.mdi-rotate-orbit::before{content:"\F0D98"}.mdi-rotate-right::before{content:"\F0467"}.mdi-rotate-right-variant::before{content:"\F0468"}.mdi-rounded-corner::before{content:"\F0607"}.mdi-router::before{content:"\F11E2"}.mdi-router-network::before{content:"\F1087"}.mdi-router-network-wireless::before{content:"\F1C97"}.mdi-router-wireless::before{content:"\F0469"}.mdi-router-wireless-off::before{content:"\F15A3"}.mdi-router-wireless-settings::before{content:"\F0A69"}.mdi-routes::before{content:"\F046A"}.mdi-routes-clock::before{content:"\F1059"}.mdi-rowing::before{content:"\F0608"}.mdi-rss::before{content:"\F046B"}.mdi-rss-box::before{content:"\F046C"}.mdi-rss-off::before{content:"\F0F21"}.mdi-rug::before{content:"\F1475"}.mdi-rugby::before{content:"\F0D99"}.mdi-ruler::before{content:"\F046D"}.mdi-ruler-square::before{content:"\F0CC2"}.mdi-ruler-square-compass::before{content:"\F0EBE"}.mdi-run::before{content:"\F070E"}.mdi-run-fast::before{content:"\F046E"}.mdi-rv-truck::before{content:"\F11D4"}.mdi-sack::before{content:"\F0D2E"}.mdi-sack-outline::before{content:"\F1C4C"}.mdi-sack-percent::before{content:"\F0D2F"}.mdi-safe::before{content:"\F0A6A"}.mdi-safe-square::before{content:"\F127C"}.mdi-safe-square-outline::before{content:"\F127D"}.mdi-safety-goggles::before{content:"\F0D30"}.mdi-sail-boat::before{content:"\F0EC8"}.mdi-sail-boat-sink::before{content:"\F1AEF"}.mdi-sale::before{content:"\F046F"}.mdi-sale-outline::before{content:"\F1A06"}.mdi-salesforce::before{content:"\F088E"}.mdi-sass::before{content:"\F07EC"}.mdi-satellite::before{content:"\F0470"}.mdi-satellite-uplink::before{content:"\F0909"}.mdi-satellite-variant::before{content:"\F0471"}.mdi-sausage::before{content:"\F08BA"}.mdi-sausage-off::before{content:"\F1789"}.mdi-saw-blade::before{content:"\F0E61"}.mdi-sawtooth-wave::before{content:"\F147A"}.mdi-saxophone::before{content:"\F0609"}.mdi-scale::before{content:"\F0472"}.mdi-scale-balance::before{content:"\F05D1"}.mdi-scale-bathroom::before{content:"\F0473"}.mdi-scale-off::before{content:"\F105A"}.mdi-scale-unbalanced::before{content:"\F19B8"}.mdi-scan-helper::before{content:"\F13D8"}.mdi-scanner::before{content:"\F06AB"}.mdi-scanner-off::before{content:"\F090A"}.mdi-scatter-plot::before{content:"\F0EC9"}.mdi-scatter-plot-outline::before{content:"\F0ECA"}.mdi-scent::before{content:"\F1958"}.mdi-scent-off::before{content:"\F1959"}.mdi-school::before{content:"\F0474"}.mdi-school-outline::before{content:"\F1180"}.mdi-scissors-cutting::before{content:"\F0A6B"}.mdi-scooter::before{content:"\F15BD"}.mdi-scooter-electric::before{content:"\F15BE"}.mdi-scoreboard::before{content:"\F127E"}.mdi-scoreboard-outline::before{content:"\F127F"}.mdi-screen-rotation::before{content:"\F0475"}.mdi-screen-rotation-lock::before{content:"\F0478"}.mdi-screw-flat-top::before{content:"\F0DF3"}.mdi-screw-lag::before{content:"\F0DF4"}.mdi-screw-machine-flat-top::before{content:"\F0DF5"}.mdi-screw-machine-round-top::before{content:"\F0DF6"}.mdi-screw-round-top::before{content:"\F0DF7"}.mdi-screwdriver::before{content:"\F0476"}.mdi-script::before{content:"\F0BC1"}.mdi-script-outline::before{content:"\F0477"}.mdi-script-text::before{content:"\F0BC2"}.mdi-script-text-key::before{content:"\F1725"}.mdi-script-text-key-outline::before{content:"\F1726"}.mdi-script-text-outline::before{content:"\F0BC3"}.mdi-script-text-play::before{content:"\F1727"}.mdi-script-text-play-outline::before{content:"\F1728"}.mdi-sd::before{content:"\F0479"}.mdi-seal::before{content:"\F047A"}.mdi-seal-variant::before{content:"\F0FD9"}.mdi-search-web::before{content:"\F070F"}.mdi-seat::before{content:"\F0CC3"}.mdi-seat-flat::before{content:"\F047B"}.mdi-seat-flat-angled::before{content:"\F047C"}.mdi-seat-individual-suite::before{content:"\F047D"}.mdi-seat-legroom-extra::before{content:"\F047E"}.mdi-seat-legroom-normal::before{content:"\F047F"}.mdi-seat-legroom-reduced::before{content:"\F0480"}.mdi-seat-outline::before{content:"\F0CC4"}.mdi-seat-passenger::before{content:"\F1249"}.mdi-seat-recline-extra::before{content:"\F0481"}.mdi-seat-recline-normal::before{content:"\F0482"}.mdi-seatbelt::before{content:"\F0CC5"}.mdi-security::before{content:"\F0483"}.mdi-security-network::before{content:"\F0484"}.mdi-seed::before{content:"\F0E62"}.mdi-seed-off::before{content:"\F13FD"}.mdi-seed-off-outline::before{content:"\F13FE"}.mdi-seed-outline::before{content:"\F0E63"}.mdi-seed-plus::before{content:"\F1A6D"}.mdi-seed-plus-outline::before{content:"\F1A6E"}.mdi-seesaw::before{content:"\F15A4"}.mdi-segment::before{content:"\F0ECB"}.mdi-select::before{content:"\F0485"}.mdi-select-all::before{content:"\F0486"}.mdi-select-arrow-down::before{content:"\F1B59"}.mdi-select-arrow-up::before{content:"\F1B58"}.mdi-select-color::before{content:"\F0D31"}.mdi-select-compare::before{content:"\F0AD9"}.mdi-select-drag::before{content:"\F0A6C"}.mdi-select-group::before{content:"\F0F82"}.mdi-select-inverse::before{content:"\F0487"}.mdi-select-marker::before{content:"\F1280"}.mdi-select-multiple::before{content:"\F1281"}.mdi-select-multiple-marker::before{content:"\F1282"}.mdi-select-off::before{content:"\F0488"}.mdi-select-place::before{content:"\F0FDA"}.mdi-select-remove::before{content:"\F17C1"}.mdi-select-search::before{content:"\F1204"}.mdi-selection::before{content:"\F0489"}.mdi-selection-drag::before{content:"\F0A6D"}.mdi-selection-ellipse::before{content:"\F0D32"}.mdi-selection-ellipse-arrow-inside::before{content:"\F0F22"}.mdi-selection-ellipse-remove::before{content:"\F17C2"}.mdi-selection-marker::before{content:"\F1283"}.mdi-selection-multiple::before{content:"\F1285"}.mdi-selection-multiple-marker::before{content:"\F1284"}.mdi-selection-off::before{content:"\F0777"}.mdi-selection-remove::before{content:"\F17C3"}.mdi-selection-search::before{content:"\F1205"}.mdi-semantic-web::before{content:"\F1316"}.mdi-send::before{content:"\F048A"}.mdi-send-check::before{content:"\F1161"}.mdi-send-check-outline::before{content:"\F1162"}.mdi-send-circle::before{content:"\F0DF8"}.mdi-send-circle-outline::before{content:"\F0DF9"}.mdi-send-clock::before{content:"\F1163"}.mdi-send-clock-outline::before{content:"\F1164"}.mdi-send-lock::before{content:"\F07ED"}.mdi-send-lock-outline::before{content:"\F1166"}.mdi-send-outline::before{content:"\F1165"}.mdi-send-variant::before{content:"\F1C4D"}.mdi-send-variant-clock::before{content:"\F1C7E"}.mdi-send-variant-clock-outline::before{content:"\F1C7F"}.mdi-send-variant-outline::before{content:"\F1C4E"}.mdi-serial-port::before{content:"\F065C"}.mdi-server::before{content:"\F048B"}.mdi-server-minus::before{content:"\F048C"}.mdi-server-minus-outline::before{content:"\F1C98"}.mdi-server-network::before{content:"\F048D"}.mdi-server-network-off::before{content:"\F048E"}.mdi-server-network-outline::before{content:"\F1C99"}.mdi-server-off::before{content:"\F048F"}.mdi-server-outline::before{content:"\F1C9A"}.mdi-server-plus::before{content:"\F0490"}.mdi-server-plus-outline::before{content:"\F1C9B"}.mdi-server-remove::before{content:"\F0491"}.mdi-server-security::before{content:"\F0492"}.mdi-set-all::before{content:"\F0778"}.mdi-set-center::before{content:"\F0779"}.mdi-set-center-right::before{content:"\F077A"}.mdi-set-left::before{content:"\F077B"}.mdi-set-left-center::before{content:"\F077C"}.mdi-set-left-right::before{content:"\F077D"}.mdi-set-merge::before{content:"\F14E0"}.mdi-set-none::before{content:"\F077E"}.mdi-set-right::before{content:"\F077F"}.mdi-set-split::before{content:"\F14E1"}.mdi-set-square::before{content:"\F145D"}.mdi-set-top-box::before{content:"\F099F"}.mdi-settings-helper::before{content:"\F0A6E"}.mdi-shaker::before{content:"\F110E"}.mdi-shaker-outline::before{content:"\F110F"}.mdi-shape::before{content:"\F0831"}.mdi-shape-circle-plus::before{content:"\F065D"}.mdi-shape-outline::before{content:"\F0832"}.mdi-shape-oval-plus::before{content:"\F11FA"}.mdi-shape-plus::before{content:"\F0495"}.mdi-shape-plus-outline::before{content:"\F1C4F"}.mdi-shape-polygon-plus::before{content:"\F065E"}.mdi-shape-rectangle-plus::before{content:"\F065F"}.mdi-shape-square-plus::before{content:"\F0660"}.mdi-shape-square-rounded-plus::before{content:"\F14FA"}.mdi-share::before{content:"\F0496"}.mdi-share-all::before{content:"\F11F4"}.mdi-share-all-outline::before{content:"\F11F5"}.mdi-share-circle::before{content:"\F11AD"}.mdi-share-off::before{content:"\F0F23"}.mdi-share-off-outline::before{content:"\F0F24"}.mdi-share-outline::before{content:"\F0932"}.mdi-share-variant::before{content:"\F0497"}.mdi-share-variant-outline::before{content:"\F1514"}.mdi-shark::before{content:"\F18BA"}.mdi-shark-fin::before{content:"\F1673"}.mdi-shark-fin-outline::before{content:"\F1674"}.mdi-shark-off::before{content:"\F18BB"}.mdi-sheep::before{content:"\F0CC6"}.mdi-shield::before{content:"\F0498"}.mdi-shield-account::before{content:"\F088F"}.mdi-shield-account-outline::before{content:"\F0A12"}.mdi-shield-account-variant::before{content:"\F15A7"}.mdi-shield-account-variant-outline::before{content:"\F15A8"}.mdi-shield-airplane::before{content:"\F06BB"}.mdi-shield-airplane-outline::before{content:"\F0CC7"}.mdi-shield-alert::before{content:"\F0ECC"}.mdi-shield-alert-outline::before{content:"\F0ECD"}.mdi-shield-bug::before{content:"\F13DA"}.mdi-shield-bug-outline::before{content:"\F13DB"}.mdi-shield-car::before{content:"\F0F83"}.mdi-shield-check::before{content:"\F0565"}.mdi-shield-check-outline::before{content:"\F0CC8"}.mdi-shield-cross::before{content:"\F0CC9"}.mdi-shield-cross-outline::before{content:"\F0CCA"}.mdi-shield-crown::before{content:"\F18BC"}.mdi-shield-crown-outline::before{content:"\F18BD"}.mdi-shield-edit::before{content:"\F11A0"}.mdi-shield-edit-outline::before{content:"\F11A1"}.mdi-shield-half::before{content:"\F1360"}.mdi-shield-half-full::before{content:"\F0780"}.mdi-shield-home::before{content:"\F068A"}.mdi-shield-home-outline::before{content:"\F0CCB"}.mdi-shield-key::before{content:"\F0BC4"}.mdi-shield-key-outline::before{content:"\F0BC5"}.mdi-shield-link-variant::before{content:"\F0D33"}.mdi-shield-link-variant-outline::before{content:"\F0D34"}.mdi-shield-lock::before{content:"\F099D"}.mdi-shield-lock-open::before{content:"\F199A"}.mdi-shield-lock-open-outline::before{content:"\F199B"}.mdi-shield-lock-outline::before{content:"\F0CCC"}.mdi-shield-moon::before{content:"\F1828"}.mdi-shield-moon-outline::before{content:"\F1829"}.mdi-shield-off::before{content:"\F099E"}.mdi-shield-off-outline::before{content:"\F099C"}.mdi-shield-outline::before{content:"\F0499"}.mdi-shield-plus::before{content:"\F0ADA"}.mdi-shield-plus-outline::before{content:"\F0ADB"}.mdi-shield-refresh::before{content:"\F00AA"}.mdi-shield-refresh-outline::before{content:"\F01E0"}.mdi-shield-remove::before{content:"\F0ADC"}.mdi-shield-remove-outline::before{content:"\F0ADD"}.mdi-shield-search::before{content:"\F0D9A"}.mdi-shield-star::before{content:"\F113B"}.mdi-shield-star-outline::before{content:"\F113C"}.mdi-shield-sun::before{content:"\F105D"}.mdi-shield-sun-outline::before{content:"\F105E"}.mdi-shield-sword::before{content:"\F18BE"}.mdi-shield-sword-outline::before{content:"\F18BF"}.mdi-shield-sync::before{content:"\F11A2"}.mdi-shield-sync-outline::before{content:"\F11A3"}.mdi-shimmer::before{content:"\F1545"}.mdi-ship-wheel::before{content:"\F0833"}.mdi-shipping-pallet::before{content:"\F184E"}.mdi-shoe-ballet::before{content:"\F15CA"}.mdi-shoe-cleat::before{content:"\F15C7"}.mdi-shoe-formal::before{content:"\F0B47"}.mdi-shoe-heel::before{content:"\F0B48"}.mdi-shoe-print::before{content:"\F0DFA"}.mdi-shoe-sneaker::before{content:"\F15C8"}.mdi-shopping::before{content:"\F049A"}.mdi-shopping-music::before{content:"\F049B"}.mdi-shopping-outline::before{content:"\F11D5"}.mdi-shopping-search::before{content:"\F0F84"}.mdi-shopping-search-outline::before{content:"\F1A6F"}.mdi-shore::before{content:"\F14F9"}.mdi-shovel::before{content:"\F0710"}.mdi-shovel-off::before{content:"\F0711"}.mdi-shower::before{content:"\F09A0"}.mdi-shower-head::before{content:"\F09A1"}.mdi-shredder::before{content:"\F049C"}.mdi-shuffle::before{content:"\F049D"}.mdi-shuffle-disabled::before{content:"\F049E"}.mdi-shuffle-variant::before{content:"\F049F"}.mdi-shuriken::before{content:"\F137F"}.mdi-sickle::before{content:"\F18C0"}.mdi-sigma::before{content:"\F04A0"}.mdi-sigma-lower::before{content:"\F062B"}.mdi-sign-caution::before{content:"\F04A1"}.mdi-sign-direction::before{content:"\F0781"}.mdi-sign-direction-minus::before{content:"\F1000"}.mdi-sign-direction-plus::before{content:"\F0FDC"}.mdi-sign-direction-remove::before{content:"\F0FDD"}.mdi-sign-language::before{content:"\F1B4D"}.mdi-sign-language-outline::before{content:"\F1B4E"}.mdi-sign-pole::before{content:"\F14F8"}.mdi-sign-real-estate::before{content:"\F1118"}.mdi-sign-text::before{content:"\F0782"}.mdi-sign-yield::before{content:"\F1BAF"}.mdi-signal::before{content:"\F04A2"}.mdi-signal-2g::before{content:"\F0712"}.mdi-signal-3g::before{content:"\F0713"}.mdi-signal-4g::before{content:"\F0714"}.mdi-signal-5g::before{content:"\F0A6F"}.mdi-signal-cellular-1::before{content:"\F08BC"}.mdi-signal-cellular-2::before{content:"\F08BD"}.mdi-signal-cellular-3::before{content:"\F08BE"}.mdi-signal-cellular-outline::before{content:"\F08BF"}.mdi-signal-distance-variant::before{content:"\F0E64"}.mdi-signal-hspa::before{content:"\F0715"}.mdi-signal-hspa-plus::before{content:"\F0716"}.mdi-signal-off::before{content:"\F0783"}.mdi-signal-variant::before{content:"\F060A"}.mdi-signature::before{content:"\F0DFB"}.mdi-signature-freehand::before{content:"\F0DFC"}.mdi-signature-image::before{content:"\F0DFD"}.mdi-signature-text::before{content:"\F0DFE"}.mdi-silo::before{content:"\F1B9F"}.mdi-silo-outline::before{content:"\F0B49"}.mdi-silverware::before{content:"\F04A3"}.mdi-silverware-clean::before{content:"\F0FDE"}.mdi-silverware-fork::before{content:"\F04A4"}.mdi-silverware-fork-knife::before{content:"\F0A70"}.mdi-silverware-spoon::before{content:"\F04A5"}.mdi-silverware-variant::before{content:"\F04A6"}.mdi-sim::before{content:"\F04A7"}.mdi-sim-alert::before{content:"\F04A8"}.mdi-sim-alert-outline::before{content:"\F15D3"}.mdi-sim-off::before{content:"\F04A9"}.mdi-sim-off-outline::before{content:"\F15D4"}.mdi-sim-outline::before{content:"\F15D5"}.mdi-simple-icons::before{content:"\F131D"}.mdi-sina-weibo::before{content:"\F0ADF"}.mdi-sine-wave::before{content:"\F095B"}.mdi-sitemap::before{content:"\F04AA"}.mdi-sitemap-outline::before{content:"\F199C"}.mdi-size-l::before{content:"\F13A6"}.mdi-size-m::before{content:"\F13A5"}.mdi-size-s::before{content:"\F13A4"}.mdi-size-xl::before{content:"\F13A7"}.mdi-size-xs::before{content:"\F13A3"}.mdi-size-xxl::before{content:"\F13A8"}.mdi-size-xxs::before{content:"\F13A2"}.mdi-size-xxxl::before{content:"\F13A9"}.mdi-skate::before{content:"\F0D35"}.mdi-skate-off::before{content:"\F0699"}.mdi-skateboard::before{content:"\F14C2"}.mdi-skateboarding::before{content:"\F0501"}.mdi-skew-less::before{content:"\F0D36"}.mdi-skew-more::before{content:"\F0D37"}.mdi-ski::before{content:"\F1304"}.mdi-ski-cross-country::before{content:"\F1305"}.mdi-ski-water::before{content:"\F1306"}.mdi-skip-backward::before{content:"\F04AB"}.mdi-skip-backward-outline::before{content:"\F0F25"}.mdi-skip-forward::before{content:"\F04AC"}.mdi-skip-forward-outline::before{content:"\F0F26"}.mdi-skip-next::before{content:"\F04AD"}.mdi-skip-next-circle::before{content:"\F0661"}.mdi-skip-next-circle-outline::before{content:"\F0662"}.mdi-skip-next-outline::before{content:"\F0F27"}.mdi-skip-previous::before{content:"\F04AE"}.mdi-skip-previous-circle::before{content:"\F0663"}.mdi-skip-previous-circle-outline::before{content:"\F0664"}.mdi-skip-previous-outline::before{content:"\F0F28"}.mdi-skull::before{content:"\F068C"}.mdi-skull-crossbones::before{content:"\F0BC6"}.mdi-skull-crossbones-outline::before{content:"\F0BC7"}.mdi-skull-outline::before{content:"\F0BC8"}.mdi-skull-scan::before{content:"\F14C7"}.mdi-skull-scan-outline::before{content:"\F14C8"}.mdi-skype::before{content:"\F04AF"}.mdi-skype-business::before{content:"\F04B0"}.mdi-slack::before{content:"\F04B1"}.mdi-slash-forward::before{content:"\F0FDF"}.mdi-slash-forward-box::before{content:"\F0FE0"}.mdi-sledding::before{content:"\F041B"}.mdi-sleep::before{content:"\F04B2"}.mdi-sleep-off::before{content:"\F04B3"}.mdi-slide::before{content:"\F15A5"}.mdi-slope-downhill::before{content:"\F0DFF"}.mdi-slope-uphill::before{content:"\F0E00"}.mdi-slot-machine::before{content:"\F1114"}.mdi-slot-machine-outline::before{content:"\F1115"}.mdi-smart-card::before{content:"\F10BD"}.mdi-smart-card-off::before{content:"\F18F7"}.mdi-smart-card-off-outline::before{content:"\F18F8"}.mdi-smart-card-outline::before{content:"\F10BE"}.mdi-smart-card-reader::before{content:"\F10BF"}.mdi-smart-card-reader-outline::before{content:"\F10C0"}.mdi-smog::before{content:"\F0A71"}.mdi-smoke::before{content:"\F1799"}.mdi-smoke-detector::before{content:"\F0392"}.mdi-smoke-detector-alert::before{content:"\F192E"}.mdi-smoke-detector-alert-outline::before{content:"\F192F"}.mdi-smoke-detector-off::before{content:"\F1809"}.mdi-smoke-detector-off-outline::before{content:"\F180A"}.mdi-smoke-detector-outline::before{content:"\F1808"}.mdi-smoke-detector-variant::before{content:"\F180B"}.mdi-smoke-detector-variant-alert::before{content:"\F1930"}.mdi-smoke-detector-variant-off::before{content:"\F180C"}.mdi-smoking::before{content:"\F04B4"}.mdi-smoking-off::before{content:"\F04B5"}.mdi-smoking-pipe::before{content:"\F140D"}.mdi-smoking-pipe-off::before{content:"\F1428"}.mdi-snail::before{content:"\F1677"}.mdi-snake::before{content:"\F150E"}.mdi-snapchat::before{content:"\F04B6"}.mdi-snowboard::before{content:"\F1307"}.mdi-snowflake::before{content:"\F0717"}.mdi-snowflake-alert::before{content:"\F0F29"}.mdi-snowflake-check::before{content:"\F1A70"}.mdi-snowflake-melt::before{content:"\F12CB"}.mdi-snowflake-off::before{content:"\F14E3"}.mdi-snowflake-thermometer::before{content:"\F1A71"}.mdi-snowflake-variant::before{content:"\F0F2A"}.mdi-snowman::before{content:"\F04B7"}.mdi-snowmobile::before{content:"\F06DD"}.mdi-snowshoeing::before{content:"\F1A72"}.mdi-soccer::before{content:"\F04B8"}.mdi-soccer-field::before{content:"\F0834"}.mdi-social-distance-2-meters::before{content:"\F1579"}.mdi-social-distance-6-feet::before{content:"\F157A"}.mdi-sofa::before{content:"\F04B9"}.mdi-sofa-outline::before{content:"\F156D"}.mdi-sofa-single::before{content:"\F156E"}.mdi-sofa-single-outline::before{content:"\F156F"}.mdi-solar-panel::before{content:"\F0D9B"}.mdi-solar-panel-large::before{content:"\F0D9C"}.mdi-solar-power::before{content:"\F0A72"}.mdi-solar-power-variant::before{content:"\F1A73"}.mdi-solar-power-variant-outline::before{content:"\F1A74"}.mdi-soldering-iron::before{content:"\F1092"}.mdi-solid::before{content:"\F068D"}.mdi-sony-playstation::before{content:"\F0414"}.mdi-sort::before{content:"\F04BA"}.mdi-sort-alphabetical-ascending::before{content:"\F05BD"}.mdi-sort-alphabetical-ascending-variant::before{content:"\F1148"}.mdi-sort-alphabetical-descending::before{content:"\F05BF"}.mdi-sort-alphabetical-descending-variant::before{content:"\F1149"}.mdi-sort-alphabetical-variant::before{content:"\F04BB"}.mdi-sort-ascending::before{content:"\F04BC"}.mdi-sort-bool-ascending::before{content:"\F1385"}.mdi-sort-bool-ascending-variant::before{content:"\F1386"}.mdi-sort-bool-descending::before{content:"\F1387"}.mdi-sort-bool-descending-variant::before{content:"\F1388"}.mdi-sort-calendar-ascending::before{content:"\F1547"}.mdi-sort-calendar-descending::before{content:"\F1548"}.mdi-sort-clock-ascending::before{content:"\F1549"}.mdi-sort-clock-ascending-outline::before{content:"\F154A"}.mdi-sort-clock-descending::before{content:"\F154B"}.mdi-sort-clock-descending-outline::before{content:"\F154C"}.mdi-sort-descending::before{content:"\F04BD"}.mdi-sort-numeric-ascending::before{content:"\F1389"}.mdi-sort-numeric-ascending-variant::before{content:"\F090D"}.mdi-sort-numeric-descending::before{content:"\F138A"}.mdi-sort-numeric-descending-variant::before{content:"\F0AD2"}.mdi-sort-numeric-variant::before{content:"\F04BE"}.mdi-sort-reverse-variant::before{content:"\F033C"}.mdi-sort-variant::before{content:"\F04BF"}.mdi-sort-variant-lock::before{content:"\F0CCD"}.mdi-sort-variant-lock-open::before{content:"\F0CCE"}.mdi-sort-variant-off::before{content:"\F1ABB"}.mdi-sort-variant-remove::before{content:"\F1147"}.mdi-soundbar::before{content:"\F17DB"}.mdi-soundcloud::before{content:"\F04C0"}.mdi-source-branch::before{content:"\F062C"}.mdi-source-branch-check::before{content:"\F14CF"}.mdi-source-branch-minus::before{content:"\F14CB"}.mdi-source-branch-plus::before{content:"\F14CA"}.mdi-source-branch-refresh::before{content:"\F14CD"}.mdi-source-branch-remove::before{content:"\F14CC"}.mdi-source-branch-sync::before{content:"\F14CE"}.mdi-source-commit::before{content:"\F0718"}.mdi-source-commit-end::before{content:"\F0719"}.mdi-source-commit-end-local::before{content:"\F071A"}.mdi-source-commit-local::before{content:"\F071B"}.mdi-source-commit-next-local::before{content:"\F071C"}.mdi-source-commit-start::before{content:"\F071D"}.mdi-source-commit-start-next-local::before{content:"\F071E"}.mdi-source-fork::before{content:"\F04C1"}.mdi-source-merge::before{content:"\F062D"}.mdi-source-pull::before{content:"\F04C2"}.mdi-source-repository::before{content:"\F0CCF"}.mdi-source-repository-multiple::before{content:"\F0CD0"}.mdi-soy-sauce::before{content:"\F07EE"}.mdi-soy-sauce-off::before{content:"\F13FC"}.mdi-spa::before{content:"\F0CD1"}.mdi-spa-outline::before{content:"\F0CD2"}.mdi-space-invaders::before{content:"\F0BC9"}.mdi-space-station::before{content:"\F1383"}.mdi-spade::before{content:"\F0E65"}.mdi-speaker::before{content:"\F04C3"}.mdi-speaker-bluetooth::before{content:"\F09A2"}.mdi-speaker-message::before{content:"\F1B11"}.mdi-speaker-multiple::before{content:"\F0D38"}.mdi-speaker-off::before{content:"\F04C4"}.mdi-speaker-pause::before{content:"\F1B73"}.mdi-speaker-play::before{content:"\F1B72"}.mdi-speaker-stop::before{content:"\F1B74"}.mdi-speaker-wireless::before{content:"\F071F"}.mdi-spear::before{content:"\F1845"}.mdi-speedometer::before{content:"\F04C5"}.mdi-speedometer-medium::before{content:"\F0F85"}.mdi-speedometer-slow::before{content:"\F0F86"}.mdi-spellcheck::before{content:"\F04C6"}.mdi-sphere::before{content:"\F1954"}.mdi-sphere-off::before{content:"\F1955"}.mdi-spider::before{content:"\F11EA"}.mdi-spider-outline::before{content:"\F1C75"}.mdi-spider-thread::before{content:"\F11EB"}.mdi-spider-web::before{content:"\F0BCA"}.mdi-spirit-level::before{content:"\F14F1"}.mdi-spoon-sugar::before{content:"\F1429"}.mdi-spotify::before{content:"\F04C7"}.mdi-spotlight::before{content:"\F04C8"}.mdi-spotlight-beam::before{content:"\F04C9"}.mdi-spray::before{content:"\F0665"}.mdi-spray-bottle::before{content:"\F0AE0"}.mdi-sprinkler::before{content:"\F105F"}.mdi-sprinkler-fire::before{content:"\F199D"}.mdi-sprinkler-variant::before{content:"\F1060"}.mdi-sprout::before{content:"\F0E66"}.mdi-sprout-outline::before{content:"\F0E67"}.mdi-square::before{content:"\F0764"}.mdi-square-circle::before{content:"\F1500"}.mdi-square-circle-outline::before{content:"\F1C50"}.mdi-square-edit-outline::before{content:"\F090C"}.mdi-square-medium::before{content:"\F0A13"}.mdi-square-medium-outline::before{content:"\F0A14"}.mdi-square-off::before{content:"\F12EE"}.mdi-square-off-outline::before{content:"\F12EF"}.mdi-square-opacity::before{content:"\F1854"}.mdi-square-outline::before{content:"\F0763"}.mdi-square-root::before{content:"\F0784"}.mdi-square-root-box::before{content:"\F09A3"}.mdi-square-rounded::before{content:"\F14FB"}.mdi-square-rounded-badge::before{content:"\F1A07"}.mdi-square-rounded-badge-outline::before{content:"\F1A08"}.mdi-square-rounded-outline::before{content:"\F14FC"}.mdi-square-small::before{content:"\F0A15"}.mdi-square-wave::before{content:"\F147B"}.mdi-squeegee::before{content:"\F0AE1"}.mdi-ssh::before{content:"\F08C0"}.mdi-stack-exchange::before{content:"\F060B"}.mdi-stack-overflow::before{content:"\F04CC"}.mdi-stackpath::before{content:"\F0359"}.mdi-stadium::before{content:"\F0FF9"}.mdi-stadium-outline::before{content:"\F1B03"}.mdi-stadium-variant::before{content:"\F0720"}.mdi-stairs::before{content:"\F04CD"}.mdi-stairs-box::before{content:"\F139E"}.mdi-stairs-down::before{content:"\F12BE"}.mdi-stairs-up::before{content:"\F12BD"}.mdi-stamper::before{content:"\F0D39"}.mdi-standard-definition::before{content:"\F07EF"}.mdi-star::before{content:"\F04CE"}.mdi-star-box::before{content:"\F0A73"}.mdi-star-box-multiple::before{content:"\F1286"}.mdi-star-box-multiple-outline::before{content:"\F1287"}.mdi-star-box-outline::before{content:"\F0A74"}.mdi-star-check::before{content:"\F1566"}.mdi-star-check-outline::before{content:"\F156A"}.mdi-star-circle::before{content:"\F04CF"}.mdi-star-circle-outline::before{content:"\F09A4"}.mdi-star-cog::before{content:"\F1668"}.mdi-star-cog-outline::before{content:"\F1669"}.mdi-star-crescent::before{content:"\F0979"}.mdi-star-david::before{content:"\F097A"}.mdi-star-face::before{content:"\F09A5"}.mdi-star-four-points::before{content:"\F0AE2"}.mdi-star-four-points-box::before{content:"\F1C51"}.mdi-star-four-points-box-outline::before{content:"\F1C52"}.mdi-star-four-points-circle::before{content:"\F1C53"}.mdi-star-four-points-circle-outline::before{content:"\F1C54"}.mdi-star-four-points-outline::before{content:"\F0AE3"}.mdi-star-four-points-small::before{content:"\F1C55"}.mdi-star-half::before{content:"\F0246"}.mdi-star-half-full::before{content:"\F04D0"}.mdi-star-minus::before{content:"\F1564"}.mdi-star-minus-outline::before{content:"\F1568"}.mdi-star-off::before{content:"\F04D1"}.mdi-star-off-outline::before{content:"\F155B"}.mdi-star-outline::before{content:"\F04D2"}.mdi-star-plus::before{content:"\F1563"}.mdi-star-plus-outline::before{content:"\F1567"}.mdi-star-remove::before{content:"\F1565"}.mdi-star-remove-outline::before{content:"\F1569"}.mdi-star-settings::before{content:"\F166A"}.mdi-star-settings-outline::before{content:"\F166B"}.mdi-star-shooting::before{content:"\F1741"}.mdi-star-shooting-outline::before{content:"\F1742"}.mdi-star-three-points::before{content:"\F0AE4"}.mdi-star-three-points-outline::before{content:"\F0AE5"}.mdi-state-machine::before{content:"\F11EF"}.mdi-steam::before{content:"\F04D3"}.mdi-steering::before{content:"\F04D4"}.mdi-steering-off::before{content:"\F090E"}.mdi-step-backward::before{content:"\F04D5"}.mdi-step-backward-2::before{content:"\F04D6"}.mdi-step-forward::before{content:"\F04D7"}.mdi-step-forward-2::before{content:"\F04D8"}.mdi-stethoscope::before{content:"\F04D9"}.mdi-sticker::before{content:"\F1364"}.mdi-sticker-alert::before{content:"\F1365"}.mdi-sticker-alert-outline::before{content:"\F1366"}.mdi-sticker-check::before{content:"\F1367"}.mdi-sticker-check-outline::before{content:"\F1368"}.mdi-sticker-circle-outline::before{content:"\F05D0"}.mdi-sticker-emoji::before{content:"\F0785"}.mdi-sticker-minus::before{content:"\F1369"}.mdi-sticker-minus-outline::before{content:"\F136A"}.mdi-sticker-outline::before{content:"\F136B"}.mdi-sticker-plus::before{content:"\F136C"}.mdi-sticker-plus-outline::before{content:"\F136D"}.mdi-sticker-remove::before{content:"\F136E"}.mdi-sticker-remove-outline::before{content:"\F136F"}.mdi-sticker-text::before{content:"\F178E"}.mdi-sticker-text-outline::before{content:"\F178F"}.mdi-stocking::before{content:"\F04DA"}.mdi-stomach::before{content:"\F1093"}.mdi-stool::before{content:"\F195D"}.mdi-stool-outline::before{content:"\F195E"}.mdi-stop::before{content:"\F04DB"}.mdi-stop-circle::before{content:"\F0666"}.mdi-stop-circle-outline::before{content:"\F0667"}.mdi-storage-tank::before{content:"\F1A75"}.mdi-storage-tank-outline::before{content:"\F1A76"}.mdi-store::before{content:"\F04DC"}.mdi-store-24-hour::before{content:"\F04DD"}.mdi-store-alert::before{content:"\F18C1"}.mdi-store-alert-outline::before{content:"\F18C2"}.mdi-store-check::before{content:"\F18C3"}.mdi-store-check-outline::before{content:"\F18C4"}.mdi-store-clock::before{content:"\F18C5"}.mdi-store-clock-outline::before{content:"\F18C6"}.mdi-store-cog::before{content:"\F18C7"}.mdi-store-cog-outline::before{content:"\F18C8"}.mdi-store-edit::before{content:"\F18C9"}.mdi-store-edit-outline::before{content:"\F18CA"}.mdi-store-marker::before{content:"\F18CB"}.mdi-store-marker-outline::before{content:"\F18CC"}.mdi-store-minus::before{content:"\F165E"}.mdi-store-minus-outline::before{content:"\F18CD"}.mdi-store-off::before{content:"\F18CE"}.mdi-store-off-outline::before{content:"\F18CF"}.mdi-store-outline::before{content:"\F1361"}.mdi-store-plus::before{content:"\F165F"}.mdi-store-plus-outline::before{content:"\F18D0"}.mdi-store-remove::before{content:"\F1660"}.mdi-store-remove-outline::before{content:"\F18D1"}.mdi-store-search::before{content:"\F18D2"}.mdi-store-search-outline::before{content:"\F18D3"}.mdi-store-settings::before{content:"\F18D4"}.mdi-store-settings-outline::before{content:"\F18D5"}.mdi-storefront::before{content:"\F07C7"}.mdi-storefront-check::before{content:"\F1B7D"}.mdi-storefront-check-outline::before{content:"\F1B7E"}.mdi-storefront-edit::before{content:"\F1B7F"}.mdi-storefront-edit-outline::before{content:"\F1B80"}.mdi-storefront-minus::before{content:"\F1B83"}.mdi-storefront-minus-outline::before{content:"\F1B84"}.mdi-storefront-outline::before{content:"\F10C1"}.mdi-storefront-plus::before{content:"\F1B81"}.mdi-storefront-plus-outline::before{content:"\F1B82"}.mdi-storefront-remove::before{content:"\F1B85"}.mdi-storefront-remove-outline::before{content:"\F1B86"}.mdi-stove::before{content:"\F04DE"}.mdi-strategy::before{content:"\F11D6"}.mdi-stretch-to-page::before{content:"\F0F2B"}.mdi-stretch-to-page-outline::before{content:"\F0F2C"}.mdi-string-lights::before{content:"\F12BA"}.mdi-string-lights-off::before{content:"\F12BB"}.mdi-subdirectory-arrow-left::before{content:"\F060C"}.mdi-subdirectory-arrow-right::before{content:"\F060D"}.mdi-submarine::before{content:"\F156C"}.mdi-subtitles::before{content:"\F0A16"}.mdi-subtitles-outline::before{content:"\F0A17"}.mdi-subway::before{content:"\F06AC"}.mdi-subway-alert-variant::before{content:"\F0D9D"}.mdi-subway-variant::before{content:"\F04DF"}.mdi-summit::before{content:"\F0786"}.mdi-sun-angle::before{content:"\F1B27"}.mdi-sun-angle-outline::before{content:"\F1B28"}.mdi-sun-clock::before{content:"\F1A77"}.mdi-sun-clock-outline::before{content:"\F1A78"}.mdi-sun-compass::before{content:"\F19A5"}.mdi-sun-snowflake::before{content:"\F1796"}.mdi-sun-snowflake-variant::before{content:"\F1A79"}.mdi-sun-thermometer::before{content:"\F18D6"}.mdi-sun-thermometer-outline::before{content:"\F18D7"}.mdi-sun-wireless::before{content:"\F17FE"}.mdi-sun-wireless-outline::before{content:"\F17FF"}.mdi-sunglasses::before{content:"\F04E0"}.mdi-surfing::before{content:"\F1746"}.mdi-surround-sound::before{content:"\F05C5"}.mdi-surround-sound-2-0::before{content:"\F07F0"}.mdi-surround-sound-2-1::before{content:"\F1729"}.mdi-surround-sound-3-1::before{content:"\F07F1"}.mdi-surround-sound-5-1::before{content:"\F07F2"}.mdi-surround-sound-5-1-2::before{content:"\F172A"}.mdi-surround-sound-7-1::before{content:"\F07F3"}.mdi-svg::before{content:"\F0721"}.mdi-swap-horizontal::before{content:"\F04E1"}.mdi-swap-horizontal-bold::before{content:"\F0BCD"}.mdi-swap-horizontal-circle::before{content:"\F0FE1"}.mdi-swap-horizontal-circle-outline::before{content:"\F0FE2"}.mdi-swap-horizontal-hidden::before{content:"\F1D0E"}.mdi-swap-horizontal-variant::before{content:"\F08C1"}.mdi-swap-vertical::before{content:"\F04E2"}.mdi-swap-vertical-bold::before{content:"\F0BCE"}.mdi-swap-vertical-circle::before{content:"\F0FE3"}.mdi-swap-vertical-circle-outline::before{content:"\F0FE4"}.mdi-swap-vertical-variant::before{content:"\F08C2"}.mdi-swim::before{content:"\F04E3"}.mdi-switch::before{content:"\F04E4"}.mdi-sword::before{content:"\F04E5"}.mdi-sword-cross::before{content:"\F0787"}.mdi-syllabary-hangul::before{content:"\F1333"}.mdi-syllabary-hiragana::before{content:"\F1334"}.mdi-syllabary-katakana::before{content:"\F1335"}.mdi-syllabary-katakana-halfwidth::before{content:"\F1336"}.mdi-symbol::before{content:"\F1501"}.mdi-symfony::before{content:"\F0AE6"}.mdi-synagogue::before{content:"\F1B04"}.mdi-synagogue-outline::before{content:"\F1B05"}.mdi-sync::before{content:"\F04E6"}.mdi-sync-alert::before{content:"\F04E7"}.mdi-sync-circle::before{content:"\F1378"}.mdi-sync-off::before{content:"\F04E8"}.mdi-tab::before{content:"\F04E9"}.mdi-tab-minus::before{content:"\F0B4B"}.mdi-tab-plus::before{content:"\F075C"}.mdi-tab-remove::before{content:"\F0B4C"}.mdi-tab-search::before{content:"\F199E"}.mdi-tab-unselected::before{content:"\F04EA"}.mdi-table::before{content:"\F04EB"}.mdi-table-account::before{content:"\F13B9"}.mdi-table-alert::before{content:"\F13BA"}.mdi-table-arrow-down::before{content:"\F13BB"}.mdi-table-arrow-left::before{content:"\F13BC"}.mdi-table-arrow-right::before{content:"\F13BD"}.mdi-table-arrow-up::before{content:"\F13BE"}.mdi-table-border::before{content:"\F0A18"}.mdi-table-cancel::before{content:"\F13BF"}.mdi-table-chair::before{content:"\F1061"}.mdi-table-check::before{content:"\F13C0"}.mdi-table-clock::before{content:"\F13C1"}.mdi-table-cog::before{content:"\F13C2"}.mdi-table-column::before{content:"\F0835"}.mdi-table-column-plus-after::before{content:"\F04EC"}.mdi-table-column-plus-before::before{content:"\F04ED"}.mdi-table-column-remove::before{content:"\F04EE"}.mdi-table-column-width::before{content:"\F04EF"}.mdi-table-edit::before{content:"\F04F0"}.mdi-table-eye::before{content:"\F1094"}.mdi-table-eye-off::before{content:"\F13C3"}.mdi-table-filter::before{content:"\F1B8C"}.mdi-table-furniture::before{content:"\F05BC"}.mdi-table-headers-eye::before{content:"\F121D"}.mdi-table-headers-eye-off::before{content:"\F121E"}.mdi-table-heart::before{content:"\F13C4"}.mdi-table-key::before{content:"\F13C5"}.mdi-table-large::before{content:"\F04F1"}.mdi-table-large-plus::before{content:"\F0F87"}.mdi-table-large-remove::before{content:"\F0F88"}.mdi-table-lock::before{content:"\F13C6"}.mdi-table-merge-cells::before{content:"\F09A6"}.mdi-table-minus::before{content:"\F13C7"}.mdi-table-multiple::before{content:"\F13C8"}.mdi-table-network::before{content:"\F13C9"}.mdi-table-of-contents::before{content:"\F0836"}.mdi-table-off::before{content:"\F13CA"}.mdi-table-picnic::before{content:"\F1743"}.mdi-table-pivot::before{content:"\F183C"}.mdi-table-plus::before{content:"\F0A75"}.mdi-table-question::before{content:"\F1B21"}.mdi-table-refresh::before{content:"\F13A0"}.mdi-table-remove::before{content:"\F0A76"}.mdi-table-row::before{content:"\F0837"}.mdi-table-row-height::before{content:"\F04F2"}.mdi-table-row-plus-after::before{content:"\F04F3"}.mdi-table-row-plus-before::before{content:"\F04F4"}.mdi-table-row-remove::before{content:"\F04F5"}.mdi-table-search::before{content:"\F090F"}.mdi-table-settings::before{content:"\F0838"}.mdi-table-split-cell::before{content:"\F142A"}.mdi-table-star::before{content:"\F13CB"}.mdi-table-sync::before{content:"\F13A1"}.mdi-table-tennis::before{content:"\F0E68"}.mdi-tablet::before{content:"\F04F6"}.mdi-tablet-cellphone::before{content:"\F09A7"}.mdi-tablet-dashboard::before{content:"\F0ECE"}.mdi-taco::before{content:"\F0762"}.mdi-tag::before{content:"\F04F9"}.mdi-tag-arrow-down::before{content:"\F172B"}.mdi-tag-arrow-down-outline::before{content:"\F172C"}.mdi-tag-arrow-left::before{content:"\F172D"}.mdi-tag-arrow-left-outline::before{content:"\F172E"}.mdi-tag-arrow-right::before{content:"\F172F"}.mdi-tag-arrow-right-outline::before{content:"\F1730"}.mdi-tag-arrow-up::before{content:"\F1731"}.mdi-tag-arrow-up-outline::before{content:"\F1732"}.mdi-tag-check::before{content:"\F1A7A"}.mdi-tag-check-outline::before{content:"\F1A7B"}.mdi-tag-edit::before{content:"\F1C9C"}.mdi-tag-edit-outline::before{content:"\F1C9D"}.mdi-tag-faces::before{content:"\F04FA"}.mdi-tag-heart::before{content:"\F068B"}.mdi-tag-heart-outline::before{content:"\F0BCF"}.mdi-tag-hidden::before{content:"\F1C76"}.mdi-tag-minus::before{content:"\F0910"}.mdi-tag-minus-outline::before{content:"\F121F"}.mdi-tag-multiple::before{content:"\F04FB"}.mdi-tag-multiple-outline::before{content:"\F12F7"}.mdi-tag-off::before{content:"\F1220"}.mdi-tag-off-outline::before{content:"\F1221"}.mdi-tag-outline::before{content:"\F04FC"}.mdi-tag-plus::before{content:"\F0722"}.mdi-tag-plus-outline::before{content:"\F1222"}.mdi-tag-remove::before{content:"\F0723"}.mdi-tag-remove-outline::before{content:"\F1223"}.mdi-tag-search::before{content:"\F1907"}.mdi-tag-search-outline::before{content:"\F1908"}.mdi-tag-text::before{content:"\F1224"}.mdi-tag-text-outline::before{content:"\F04FD"}.mdi-tailwind::before{content:"\F13FF"}.mdi-tally-mark-1::before{content:"\F1ABC"}.mdi-tally-mark-2::before{content:"\F1ABD"}.mdi-tally-mark-3::before{content:"\F1ABE"}.mdi-tally-mark-4::before{content:"\F1ABF"}.mdi-tally-mark-5::before{content:"\F1AC0"}.mdi-tangram::before{content:"\F04F8"}.mdi-tank::before{content:"\F0D3A"}.mdi-tanker-truck::before{content:"\F0FE5"}.mdi-tape-drive::before{content:"\F16DF"}.mdi-tape-measure::before{content:"\F0B4D"}.mdi-target::before{content:"\F04FE"}.mdi-target-account::before{content:"\F0BD0"}.mdi-target-variant::before{content:"\F0A77"}.mdi-taxi::before{content:"\F04FF"}.mdi-tea::before{content:"\F0D9E"}.mdi-tea-outline::before{content:"\F0D9F"}.mdi-teamviewer::before{content:"\F0500"}.mdi-teddy-bear::before{content:"\F18FB"}.mdi-telescope::before{content:"\F0B4E"}.mdi-television::before{content:"\F0502"}.mdi-television-ambient-light::before{content:"\F1356"}.mdi-television-box::before{content:"\F0839"}.mdi-television-classic::before{content:"\F07F4"}.mdi-television-classic-off::before{content:"\F083A"}.mdi-television-guide::before{content:"\F0503"}.mdi-television-off::before{content:"\F083B"}.mdi-television-pause::before{content:"\F0F89"}.mdi-television-play::before{content:"\F0ECF"}.mdi-television-shimmer::before{content:"\F1110"}.mdi-television-speaker::before{content:"\F1B1B"}.mdi-television-speaker-off::before{content:"\F1B1C"}.mdi-television-stop::before{content:"\F0F8A"}.mdi-temperature-celsius::before{content:"\F0504"}.mdi-temperature-fahrenheit::before{content:"\F0505"}.mdi-temperature-kelvin::before{content:"\F0506"}.mdi-temple-buddhist::before{content:"\F1B06"}.mdi-temple-buddhist-outline::before{content:"\F1B07"}.mdi-temple-hindu::before{content:"\F1B08"}.mdi-temple-hindu-outline::before{content:"\F1B09"}.mdi-tennis::before{content:"\F0DA0"}.mdi-tennis-ball::before{content:"\F0507"}.mdi-tennis-ball-outline::before{content:"\F1C5F"}.mdi-tent::before{content:"\F0508"}.mdi-terraform::before{content:"\F1062"}.mdi-terrain::before{content:"\F0509"}.mdi-test-tube::before{content:"\F0668"}.mdi-test-tube-empty::before{content:"\F0911"}.mdi-test-tube-off::before{content:"\F0912"}.mdi-text::before{content:"\F09A8"}.mdi-text-account::before{content:"\F1570"}.mdi-text-box::before{content:"\F021A"}.mdi-text-box-check::before{content:"\F0EA6"}.mdi-text-box-check-outline::before{content:"\F0EA7"}.mdi-text-box-edit::before{content:"\F1A7C"}.mdi-text-box-edit-outline::before{content:"\F1A7D"}.mdi-text-box-minus::before{content:"\F0EA8"}.mdi-text-box-minus-outline::before{content:"\F0EA9"}.mdi-text-box-multiple::before{content:"\F0AB7"}.mdi-text-box-multiple-outline::before{content:"\F0AB8"}.mdi-text-box-outline::before{content:"\F09ED"}.mdi-text-box-plus::before{content:"\F0EAA"}.mdi-text-box-plus-outline::before{content:"\F0EAB"}.mdi-text-box-remove::before{content:"\F0EAC"}.mdi-text-box-remove-outline::before{content:"\F0EAD"}.mdi-text-box-search::before{content:"\F0EAE"}.mdi-text-box-search-outline::before{content:"\F0EAF"}.mdi-text-long::before{content:"\F09AA"}.mdi-text-recognition::before{content:"\F113D"}.mdi-text-search::before{content:"\F13B8"}.mdi-text-search-variant::before{content:"\F1A7E"}.mdi-text-shadow::before{content:"\F0669"}.mdi-text-short::before{content:"\F09A9"}.mdi-texture::before{content:"\F050C"}.mdi-texture-box::before{content:"\F0FE6"}.mdi-theater::before{content:"\F050D"}.mdi-theme-light-dark::before{content:"\F050E"}.mdi-thermometer::before{content:"\F050F"}.mdi-thermometer-alert::before{content:"\F0E01"}.mdi-thermometer-auto::before{content:"\F1B0F"}.mdi-thermometer-bluetooth::before{content:"\F1895"}.mdi-thermometer-check::before{content:"\F1A7F"}.mdi-thermometer-chevron-down::before{content:"\F0E02"}.mdi-thermometer-chevron-up::before{content:"\F0E03"}.mdi-thermometer-high::before{content:"\F10C2"}.mdi-thermometer-lines::before{content:"\F0510"}.mdi-thermometer-low::before{content:"\F10C3"}.mdi-thermometer-minus::before{content:"\F0E04"}.mdi-thermometer-off::before{content:"\F1531"}.mdi-thermometer-plus::before{content:"\F0E05"}.mdi-thermometer-probe::before{content:"\F1B2B"}.mdi-thermometer-probe-off::before{content:"\F1B2C"}.mdi-thermometer-water::before{content:"\F1A80"}.mdi-thermostat::before{content:"\F0393"}.mdi-thermostat-auto::before{content:"\F1B17"}.mdi-thermostat-box::before{content:"\F0891"}.mdi-thermostat-box-auto::before{content:"\F1B18"}.mdi-thermostat-cog::before{content:"\F1C80"}.mdi-thought-bubble::before{content:"\F07F6"}.mdi-thought-bubble-outline::before{content:"\F07F7"}.mdi-thumb-down::before{content:"\F0511"}.mdi-thumb-down-outline::before{content:"\F0512"}.mdi-thumb-up::before{content:"\F0513"}.mdi-thumb-up-outline::before{content:"\F0514"}.mdi-thumbs-up-down::before{content:"\F0515"}.mdi-thumbs-up-down-outline::before{content:"\F1914"}.mdi-ticket::before{content:"\F0516"}.mdi-ticket-account::before{content:"\F0517"}.mdi-ticket-confirmation::before{content:"\F0518"}.mdi-ticket-confirmation-outline::before{content:"\F13AA"}.mdi-ticket-outline::before{content:"\F0913"}.mdi-ticket-percent::before{content:"\F0724"}.mdi-ticket-percent-outline::before{content:"\F142B"}.mdi-tie::before{content:"\F0519"}.mdi-tilde::before{content:"\F0725"}.mdi-tilde-off::before{content:"\F18F3"}.mdi-timelapse::before{content:"\F051A"}.mdi-timeline::before{content:"\F0BD1"}.mdi-timeline-alert::before{content:"\F0F95"}.mdi-timeline-alert-outline::before{content:"\F0F98"}.mdi-timeline-check::before{content:"\F1532"}.mdi-timeline-check-outline::before{content:"\F1533"}.mdi-timeline-clock::before{content:"\F11FB"}.mdi-timeline-clock-outline::before{content:"\F11FC"}.mdi-timeline-minus::before{content:"\F1534"}.mdi-timeline-minus-outline::before{content:"\F1535"}.mdi-timeline-outline::before{content:"\F0BD2"}.mdi-timeline-plus::before{content:"\F0F96"}.mdi-timeline-plus-outline::before{content:"\F0F97"}.mdi-timeline-question::before{content:"\F0F99"}.mdi-timeline-question-outline::before{content:"\F0F9A"}.mdi-timeline-remove::before{content:"\F1536"}.mdi-timeline-remove-outline::before{content:"\F1537"}.mdi-timeline-text::before{content:"\F0BD3"}.mdi-timeline-text-outline::before{content:"\F0BD4"}.mdi-timer::before{content:"\F13AB"}.mdi-timer-10::before{content:"\F051C"}.mdi-timer-3::before{content:"\F051D"}.mdi-timer-alert::before{content:"\F1ACC"}.mdi-timer-alert-outline::before{content:"\F1ACD"}.mdi-timer-cancel::before{content:"\F1ACE"}.mdi-timer-cancel-outline::before{content:"\F1ACF"}.mdi-timer-check::before{content:"\F1AD0"}.mdi-timer-check-outline::before{content:"\F1AD1"}.mdi-timer-cog::before{content:"\F1925"}.mdi-timer-cog-outline::before{content:"\F1926"}.mdi-timer-edit::before{content:"\F1AD2"}.mdi-timer-edit-outline::before{content:"\F1AD3"}.mdi-timer-lock::before{content:"\F1AD4"}.mdi-timer-lock-open::before{content:"\F1AD5"}.mdi-timer-lock-open-outline::before{content:"\F1AD6"}.mdi-timer-lock-outline::before{content:"\F1AD7"}.mdi-timer-marker::before{content:"\F1AD8"}.mdi-timer-marker-outline::before{content:"\F1AD9"}.mdi-timer-minus::before{content:"\F1ADA"}.mdi-timer-minus-outline::before{content:"\F1ADB"}.mdi-timer-music::before{content:"\F1ADC"}.mdi-timer-music-outline::before{content:"\F1ADD"}.mdi-timer-off::before{content:"\F13AC"}.mdi-timer-off-outline::before{content:"\F051E"}.mdi-timer-outline::before{content:"\F051B"}.mdi-timer-pause::before{content:"\F1ADE"}.mdi-timer-pause-outline::before{content:"\F1ADF"}.mdi-timer-play::before{content:"\F1AE0"}.mdi-timer-play-outline::before{content:"\F1AE1"}.mdi-timer-plus::before{content:"\F1AE2"}.mdi-timer-plus-outline::before{content:"\F1AE3"}.mdi-timer-refresh::before{content:"\F1AE4"}.mdi-timer-refresh-outline::before{content:"\F1AE5"}.mdi-timer-remove::before{content:"\F1AE6"}.mdi-timer-remove-outline::before{content:"\F1AE7"}.mdi-timer-sand::before{content:"\F051F"}.mdi-timer-sand-complete::before{content:"\F199F"}.mdi-timer-sand-empty::before{content:"\F06AD"}.mdi-timer-sand-full::before{content:"\F078C"}.mdi-timer-sand-paused::before{content:"\F19A0"}.mdi-timer-settings::before{content:"\F1923"}.mdi-timer-settings-outline::before{content:"\F1924"}.mdi-timer-star::before{content:"\F1AE8"}.mdi-timer-star-outline::before{content:"\F1AE9"}.mdi-timer-stop::before{content:"\F1AEA"}.mdi-timer-stop-outline::before{content:"\F1AEB"}.mdi-timer-sync::before{content:"\F1AEC"}.mdi-timer-sync-outline::before{content:"\F1AED"}.mdi-timetable::before{content:"\F0520"}.mdi-tire::before{content:"\F1896"}.mdi-toaster::before{content:"\F1063"}.mdi-toaster-off::before{content:"\F11B7"}.mdi-toaster-oven::before{content:"\F0CD3"}.mdi-toggle-switch::before{content:"\F0521"}.mdi-toggle-switch-off::before{content:"\F0522"}.mdi-toggle-switch-off-outline::before{content:"\F0A19"}.mdi-toggle-switch-outline::before{content:"\F0A1A"}.mdi-toggle-switch-variant::before{content:"\F1A25"}.mdi-toggle-switch-variant-off::before{content:"\F1A26"}.mdi-toilet::before{content:"\F09AB"}.mdi-toolbox::before{content:"\F09AC"}.mdi-toolbox-outline::before{content:"\F09AD"}.mdi-tools::before{content:"\F1064"}.mdi-tooltip::before{content:"\F0523"}.mdi-tooltip-account::before{content:"\F000C"}.mdi-tooltip-cellphone::before{content:"\F183B"}.mdi-tooltip-check::before{content:"\F155C"}.mdi-tooltip-check-outline::before{content:"\F155D"}.mdi-tooltip-edit::before{content:"\F0524"}.mdi-tooltip-edit-outline::before{content:"\F12C5"}.mdi-tooltip-image::before{content:"\F0525"}.mdi-tooltip-image-outline::before{content:"\F0BD5"}.mdi-tooltip-minus::before{content:"\F155E"}.mdi-tooltip-minus-outline::before{content:"\F155F"}.mdi-tooltip-outline::before{content:"\F0526"}.mdi-tooltip-plus::before{content:"\F0BD6"}.mdi-tooltip-plus-outline::before{content:"\F0527"}.mdi-tooltip-question::before{content:"\F1BBA"}.mdi-tooltip-question-outline::before{content:"\F1BBB"}.mdi-tooltip-remove::before{content:"\F1560"}.mdi-tooltip-remove-outline::before{content:"\F1561"}.mdi-tooltip-text::before{content:"\F0528"}.mdi-tooltip-text-outline::before{content:"\F0BD7"}.mdi-tooth::before{content:"\F08C3"}.mdi-tooth-outline::before{content:"\F0529"}.mdi-toothbrush::before{content:"\F1129"}.mdi-toothbrush-electric::before{content:"\F112C"}.mdi-toothbrush-paste::before{content:"\F112A"}.mdi-torch::before{content:"\F1606"}.mdi-tortoise::before{content:"\F0D3B"}.mdi-toslink::before{content:"\F12B8"}.mdi-touch-text-outline::before{content:"\F1C60"}.mdi-tournament::before{content:"\F09AE"}.mdi-tow-truck::before{content:"\F083C"}.mdi-tower-beach::before{content:"\F0681"}.mdi-tower-fire::before{content:"\F0682"}.mdi-town-hall::before{content:"\F1875"}.mdi-toy-brick::before{content:"\F1288"}.mdi-toy-brick-marker::before{content:"\F1289"}.mdi-toy-brick-marker-outline::before{content:"\F128A"}.mdi-toy-brick-minus::before{content:"\F128B"}.mdi-toy-brick-minus-outline::before{content:"\F128C"}.mdi-toy-brick-outline::before{content:"\F128D"}.mdi-toy-brick-plus::before{content:"\F128E"}.mdi-toy-brick-plus-outline::before{content:"\F128F"}.mdi-toy-brick-remove::before{content:"\F1290"}.mdi-toy-brick-remove-outline::before{content:"\F1291"}.mdi-toy-brick-search::before{content:"\F1292"}.mdi-toy-brick-search-outline::before{content:"\F1293"}.mdi-track-light::before{content:"\F0914"}.mdi-track-light-off::before{content:"\F1B01"}.mdi-trackpad::before{content:"\F07F8"}.mdi-trackpad-lock::before{content:"\F0933"}.mdi-tractor::before{content:"\F0892"}.mdi-tractor-variant::before{content:"\F14C4"}.mdi-trademark::before{content:"\F0A78"}.mdi-traffic-cone::before{content:"\F137C"}.mdi-traffic-light::before{content:"\F052B"}.mdi-traffic-light-outline::before{content:"\F182A"}.mdi-train::before{content:"\F052C"}.mdi-train-bus::before{content:"\F1CC7"}.mdi-train-car::before{content:"\F0BD8"}.mdi-train-car-autorack::before{content:"\F1B2D"}.mdi-train-car-box::before{content:"\F1B2E"}.mdi-train-car-box-full::before{content:"\F1B2F"}.mdi-train-car-box-open::before{content:"\F1B30"}.mdi-train-car-caboose::before{content:"\F1B31"}.mdi-train-car-centerbeam::before{content:"\F1B32"}.mdi-train-car-centerbeam-full::before{content:"\F1B33"}.mdi-train-car-container::before{content:"\F1B34"}.mdi-train-car-flatbed::before{content:"\F1B35"}.mdi-train-car-flatbed-car::before{content:"\F1B36"}.mdi-train-car-flatbed-tank::before{content:"\F1B37"}.mdi-train-car-gondola::before{content:"\F1B38"}.mdi-train-car-gondola-full::before{content:"\F1B39"}.mdi-train-car-hopper::before{content:"\F1B3A"}.mdi-train-car-hopper-covered::before{content:"\F1B3B"}.mdi-train-car-hopper-full::before{content:"\F1B3C"}.mdi-train-car-intermodal::before{content:"\F1B3D"}.mdi-train-car-passenger::before{content:"\F1733"}.mdi-train-car-passenger-door::before{content:"\F1734"}.mdi-train-car-passenger-door-open::before{content:"\F1735"}.mdi-train-car-passenger-variant::before{content:"\F1736"}.mdi-train-car-tank::before{content:"\F1B3E"}.mdi-train-variant::before{content:"\F08C4"}.mdi-tram::before{content:"\F052D"}.mdi-tram-side::before{content:"\F0FE7"}.mdi-transcribe::before{content:"\F052E"}.mdi-transcribe-close::before{content:"\F052F"}.mdi-transfer::before{content:"\F1065"}.mdi-transfer-down::before{content:"\F0DA1"}.mdi-transfer-left::before{content:"\F0DA2"}.mdi-transfer-right::before{content:"\F0530"}.mdi-transfer-up::before{content:"\F0DA3"}.mdi-transit-connection::before{content:"\F0D3C"}.mdi-transit-connection-horizontal::before{content:"\F1546"}.mdi-transit-connection-variant::before{content:"\F0D3D"}.mdi-transit-detour::before{content:"\F0F8B"}.mdi-transit-skip::before{content:"\F1515"}.mdi-transit-transfer::before{content:"\F06AE"}.mdi-transition::before{content:"\F0915"}.mdi-transition-masked::before{content:"\F0916"}.mdi-translate::before{content:"\F05CA"}.mdi-translate-off::before{content:"\F0E06"}.mdi-translate-variant::before{content:"\F1B99"}.mdi-transmission-tower::before{content:"\F0D3E"}.mdi-transmission-tower-export::before{content:"\F192C"}.mdi-transmission-tower-import::before{content:"\F192D"}.mdi-transmission-tower-off::before{content:"\F19DD"}.mdi-trash-can::before{content:"\F0A79"}.mdi-trash-can-outline::before{content:"\F0A7A"}.mdi-tray::before{content:"\F1294"}.mdi-tray-alert::before{content:"\F1295"}.mdi-tray-arrow-down::before{content:"\F0120"}.mdi-tray-arrow-up::before{content:"\F011D"}.mdi-tray-full::before{content:"\F1296"}.mdi-tray-minus::before{content:"\F1297"}.mdi-tray-plus::before{content:"\F1298"}.mdi-tray-remove::before{content:"\F1299"}.mdi-treasure-chest::before{content:"\F0726"}.mdi-treasure-chest-outline::before{content:"\F1C77"}.mdi-tree::before{content:"\F0531"}.mdi-tree-outline::before{content:"\F0E69"}.mdi-trello::before{content:"\F0532"}.mdi-trending-down::before{content:"\F0533"}.mdi-trending-neutral::before{content:"\F0534"}.mdi-trending-up::before{content:"\F0535"}.mdi-triangle::before{content:"\F0536"}.mdi-triangle-down::before{content:"\F1C56"}.mdi-triangle-down-outline::before{content:"\F1C57"}.mdi-triangle-outline::before{content:"\F0537"}.mdi-triangle-small-down::before{content:"\F1A09"}.mdi-triangle-small-up::before{content:"\F1A0A"}.mdi-triangle-wave::before{content:"\F147C"}.mdi-triforce::before{content:"\F0BD9"}.mdi-trophy::before{content:"\F0538"}.mdi-trophy-award::before{content:"\F0539"}.mdi-trophy-broken::before{content:"\F0DA4"}.mdi-trophy-outline::before{content:"\F053A"}.mdi-trophy-variant::before{content:"\F053B"}.mdi-trophy-variant-outline::before{content:"\F053C"}.mdi-truck::before{content:"\F053D"}.mdi-truck-alert::before{content:"\F19DE"}.mdi-truck-alert-outline::before{content:"\F19DF"}.mdi-truck-cargo-container::before{content:"\F18D8"}.mdi-truck-check::before{content:"\F0CD4"}.mdi-truck-check-outline::before{content:"\F129A"}.mdi-truck-delivery::before{content:"\F053E"}.mdi-truck-delivery-outline::before{content:"\F129B"}.mdi-truck-fast::before{content:"\F0788"}.mdi-truck-fast-outline::before{content:"\F129C"}.mdi-truck-flatbed::before{content:"\F1891"}.mdi-truck-minus::before{content:"\F19AE"}.mdi-truck-minus-outline::before{content:"\F19BD"}.mdi-truck-off-road::before{content:"\F1C9E"}.mdi-truck-off-road-off::before{content:"\F1C9F"}.mdi-truck-outline::before{content:"\F129D"}.mdi-truck-plus::before{content:"\F19AD"}.mdi-truck-plus-outline::before{content:"\F19BC"}.mdi-truck-remove::before{content:"\F19AF"}.mdi-truck-remove-outline::before{content:"\F19BE"}.mdi-truck-snowflake::before{content:"\F19A6"}.mdi-truck-trailer::before{content:"\F0727"}.mdi-trumpet::before{content:"\F1096"}.mdi-tshirt-crew::before{content:"\F0A7B"}.mdi-tshirt-crew-outline::before{content:"\F053F"}.mdi-tshirt-v::before{content:"\F0A7C"}.mdi-tshirt-v-outline::before{content:"\F0540"}.mdi-tsunami::before{content:"\F1A81"}.mdi-tumble-dryer::before{content:"\F0917"}.mdi-tumble-dryer-alert::before{content:"\F11BA"}.mdi-tumble-dryer-off::before{content:"\F11BB"}.mdi-tune::before{content:"\F062E"}.mdi-tune-variant::before{content:"\F1542"}.mdi-tune-vertical::before{content:"\F066A"}.mdi-tune-vertical-variant::before{content:"\F1543"}.mdi-tunnel::before{content:"\F183D"}.mdi-tunnel-outline::before{content:"\F183E"}.mdi-turbine::before{content:"\F1A82"}.mdi-turkey::before{content:"\F171B"}.mdi-turnstile::before{content:"\F0CD5"}.mdi-turnstile-outline::before{content:"\F0CD6"}.mdi-turtle::before{content:"\F0CD7"}.mdi-twitch::before{content:"\F0543"}.mdi-twitter::before{content:"\F0544"}.mdi-two-factor-authentication::before{content:"\F09AF"}.mdi-typewriter::before{content:"\F0F2D"}.mdi-ubisoft::before{content:"\F0BDA"}.mdi-ubuntu::before{content:"\F0548"}.mdi-ufo::before{content:"\F10C4"}.mdi-ufo-outline::before{content:"\F10C5"}.mdi-ultra-high-definition::before{content:"\F07F9"}.mdi-umbraco::before{content:"\F0549"}.mdi-umbrella::before{content:"\F054A"}.mdi-umbrella-beach::before{content:"\F188A"}.mdi-umbrella-beach-outline::before{content:"\F188B"}.mdi-umbrella-closed::before{content:"\F09B0"}.mdi-umbrella-closed-outline::before{content:"\F13E2"}.mdi-umbrella-closed-variant::before{content:"\F13E1"}.mdi-umbrella-outline::before{content:"\F054B"}.mdi-underwear-outline::before{content:"\F1D0F"}.mdi-undo::before{content:"\F054C"}.mdi-undo-variant::before{content:"\F054D"}.mdi-unfold-less-horizontal::before{content:"\F054E"}.mdi-unfold-less-vertical::before{content:"\F0760"}.mdi-unfold-more-horizontal::before{content:"\F054F"}.mdi-unfold-more-vertical::before{content:"\F0761"}.mdi-ungroup::before{content:"\F0550"}.mdi-unicode::before{content:"\F0ED0"}.mdi-unicorn::before{content:"\F15C2"}.mdi-unicorn-variant::before{content:"\F15C3"}.mdi-unicycle::before{content:"\F15E5"}.mdi-unity::before{content:"\F06AF"}.mdi-unreal::before{content:"\F09B1"}.mdi-update::before{content:"\F06B0"}.mdi-upload::before{content:"\F0552"}.mdi-upload-box::before{content:"\F1D10"}.mdi-upload-box-outline::before{content:"\F1D11"}.mdi-upload-circle::before{content:"\F1D12"}.mdi-upload-circle-outline::before{content:"\F1D13"}.mdi-upload-lock::before{content:"\F1373"}.mdi-upload-lock-outline::before{content:"\F1374"}.mdi-upload-multiple::before{content:"\F083D"}.mdi-upload-multiple-outline::before{content:"\F1D14"}.mdi-upload-network::before{content:"\F06F6"}.mdi-upload-network-outline::before{content:"\F0CD8"}.mdi-upload-off::before{content:"\F10C6"}.mdi-upload-off-outline::before{content:"\F10C7"}.mdi-upload-outline::before{content:"\F0E07"}.mdi-usb::before{content:"\F0553"}.mdi-usb-c-port::before{content:"\F1CBF"}.mdi-usb-flash-drive::before{content:"\F129E"}.mdi-usb-flash-drive-outline::before{content:"\F129F"}.mdi-usb-port::before{content:"\F11F0"}.mdi-vacuum::before{content:"\F19A1"}.mdi-vacuum-outline::before{content:"\F19A2"}.mdi-valve::before{content:"\F1066"}.mdi-valve-closed::before{content:"\F1067"}.mdi-valve-open::before{content:"\F1068"}.mdi-van-passenger::before{content:"\F07FA"}.mdi-van-utility::before{content:"\F07FB"}.mdi-vanish::before{content:"\F07FC"}.mdi-vanish-quarter::before{content:"\F1554"}.mdi-vanity-light::before{content:"\F11E1"}.mdi-variable::before{content:"\F0AE7"}.mdi-variable-box::before{content:"\F1111"}.mdi-vector-arrange-above::before{content:"\F0554"}.mdi-vector-arrange-below::before{content:"\F0555"}.mdi-vector-bezier::before{content:"\F0AE8"}.mdi-vector-circle::before{content:"\F0556"}.mdi-vector-circle-variant::before{content:"\F0557"}.mdi-vector-combine::before{content:"\F0558"}.mdi-vector-curve::before{content:"\F0559"}.mdi-vector-difference::before{content:"\F055A"}.mdi-vector-difference-ab::before{content:"\F055B"}.mdi-vector-difference-ba::before{content:"\F055C"}.mdi-vector-ellipse::before{content:"\F0893"}.mdi-vector-intersection::before{content:"\F055D"}.mdi-vector-line::before{content:"\F055E"}.mdi-vector-link::before{content:"\F0FE8"}.mdi-vector-point::before{content:"\F01C4"}.mdi-vector-point-edit::before{content:"\F09E8"}.mdi-vector-point-minus::before{content:"\F1B78"}.mdi-vector-point-plus::before{content:"\F1B79"}.mdi-vector-point-select::before{content:"\F055F"}.mdi-vector-polygon::before{content:"\F0560"}.mdi-vector-polygon-variant::before{content:"\F1856"}.mdi-vector-polyline::before{content:"\F0561"}.mdi-vector-polyline-edit::before{content:"\F1225"}.mdi-vector-polyline-minus::before{content:"\F1226"}.mdi-vector-polyline-plus::before{content:"\F1227"}.mdi-vector-polyline-remove::before{content:"\F1228"}.mdi-vector-radius::before{content:"\F074A"}.mdi-vector-rectangle::before{content:"\F05C6"}.mdi-vector-selection::before{content:"\F0562"}.mdi-vector-square::before{content:"\F0001"}.mdi-vector-square-close::before{content:"\F1857"}.mdi-vector-square-edit::before{content:"\F18D9"}.mdi-vector-square-minus::before{content:"\F18DA"}.mdi-vector-square-open::before{content:"\F1858"}.mdi-vector-square-plus::before{content:"\F18DB"}.mdi-vector-square-remove::before{content:"\F18DC"}.mdi-vector-triangle::before{content:"\F0563"}.mdi-vector-union::before{content:"\F0564"}.mdi-vhs::before{content:"\F0A1B"}.mdi-vibrate::before{content:"\F0566"}.mdi-vibrate-off::before{content:"\F0CD9"}.mdi-video::before{content:"\F0567"}.mdi-video-2d::before{content:"\F1A1C"}.mdi-video-3d::before{content:"\F07FD"}.mdi-video-3d-off::before{content:"\F13D9"}.mdi-video-3d-variant::before{content:"\F0ED1"}.mdi-video-4k-box::before{content:"\F083E"}.mdi-video-account::before{content:"\F0919"}.mdi-video-box::before{content:"\F00FD"}.mdi-video-box-off::before{content:"\F00FE"}.mdi-video-check::before{content:"\F1069"}.mdi-video-check-outline::before{content:"\F106A"}.mdi-video-high-definition::before{content:"\F152E"}.mdi-video-image::before{content:"\F091A"}.mdi-video-input-antenna::before{content:"\F083F"}.mdi-video-input-component::before{content:"\F0840"}.mdi-video-input-hdmi::before{content:"\F0841"}.mdi-video-input-scart::before{content:"\F0F8C"}.mdi-video-input-svideo::before{content:"\F0842"}.mdi-video-marker::before{content:"\F19A9"}.mdi-video-marker-outline::before{content:"\F19AA"}.mdi-video-minus::before{content:"\F09B2"}.mdi-video-minus-outline::before{content:"\F02BA"}.mdi-video-off::before{content:"\F0568"}.mdi-video-off-outline::before{content:"\F0BDB"}.mdi-video-outline::before{content:"\F0BDC"}.mdi-video-plus::before{content:"\F09B3"}.mdi-video-plus-outline::before{content:"\F01D3"}.mdi-video-stabilization::before{content:"\F091B"}.mdi-video-standard-definition::before{content:"\F1CA0"}.mdi-video-switch::before{content:"\F0569"}.mdi-video-switch-outline::before{content:"\F0790"}.mdi-video-vintage::before{content:"\F0A1C"}.mdi-video-wireless::before{content:"\F0ED2"}.mdi-video-wireless-outline::before{content:"\F0ED3"}.mdi-view-agenda::before{content:"\F056A"}.mdi-view-agenda-outline::before{content:"\F11D8"}.mdi-view-array::before{content:"\F056B"}.mdi-view-array-outline::before{content:"\F1485"}.mdi-view-carousel::before{content:"\F056C"}.mdi-view-carousel-outline::before{content:"\F1486"}.mdi-view-column::before{content:"\F056D"}.mdi-view-column-outline::before{content:"\F1487"}.mdi-view-comfy::before{content:"\F0E6A"}.mdi-view-comfy-outline::before{content:"\F1488"}.mdi-view-compact::before{content:"\F0E6B"}.mdi-view-compact-outline::before{content:"\F0E6C"}.mdi-view-dashboard::before{content:"\F056E"}.mdi-view-dashboard-edit::before{content:"\F1947"}.mdi-view-dashboard-edit-outline::before{content:"\F1948"}.mdi-view-dashboard-outline::before{content:"\F0A1D"}.mdi-view-dashboard-variant::before{content:"\F0843"}.mdi-view-dashboard-variant-outline::before{content:"\F1489"}.mdi-view-day::before{content:"\F056F"}.mdi-view-day-outline::before{content:"\F148A"}.mdi-view-gallery::before{content:"\F1888"}.mdi-view-gallery-outline::before{content:"\F1889"}.mdi-view-grid::before{content:"\F0570"}.mdi-view-grid-compact::before{content:"\F1C61"}.mdi-view-grid-outline::before{content:"\F11D9"}.mdi-view-grid-plus::before{content:"\F0F8D"}.mdi-view-grid-plus-outline::before{content:"\F11DA"}.mdi-view-headline::before{content:"\F0571"}.mdi-view-list::before{content:"\F0572"}.mdi-view-list-outline::before{content:"\F148B"}.mdi-view-module::before{content:"\F0573"}.mdi-view-module-outline::before{content:"\F148C"}.mdi-view-parallel::before{content:"\F0728"}.mdi-view-parallel-outline::before{content:"\F148D"}.mdi-view-quilt::before{content:"\F0574"}.mdi-view-quilt-outline::before{content:"\F148E"}.mdi-view-sequential::before{content:"\F0729"}.mdi-view-sequential-outline::before{content:"\F148F"}.mdi-view-split-horizontal::before{content:"\F0BCB"}.mdi-view-split-vertical::before{content:"\F0BCC"}.mdi-view-stream::before{content:"\F0575"}.mdi-view-stream-outline::before{content:"\F1490"}.mdi-view-week::before{content:"\F0576"}.mdi-view-week-outline::before{content:"\F1491"}.mdi-vimeo::before{content:"\F0577"}.mdi-violin::before{content:"\F060F"}.mdi-virtual-reality::before{content:"\F0894"}.mdi-virus::before{content:"\F13B6"}.mdi-virus-off::before{content:"\F18E1"}.mdi-virus-off-outline::before{content:"\F18E2"}.mdi-virus-outline::before{content:"\F13B7"}.mdi-vlc::before{content:"\F057C"}.mdi-voicemail::before{content:"\F057D"}.mdi-volcano::before{content:"\F1A83"}.mdi-volcano-outline::before{content:"\F1A84"}.mdi-volleyball::before{content:"\F09B4"}.mdi-volume-equal::before{content:"\F1B10"}.mdi-volume-high::before{content:"\F057E"}.mdi-volume-low::before{content:"\F057F"}.mdi-volume-medium::before{content:"\F0580"}.mdi-volume-minus::before{content:"\F075E"}.mdi-volume-mute::before{content:"\F075F"}.mdi-volume-off::before{content:"\F0581"}.mdi-volume-plus::before{content:"\F075D"}.mdi-volume-source::before{content:"\F1120"}.mdi-volume-variant-off::before{content:"\F0E08"}.mdi-volume-vibrate::before{content:"\F1121"}.mdi-vote::before{content:"\F0A1F"}.mdi-vote-outline::before{content:"\F0A20"}.mdi-vpn::before{content:"\F0582"}.mdi-vuejs::before{content:"\F0844"}.mdi-vuetify::before{content:"\F0E6D"}.mdi-walk::before{content:"\F0583"}.mdi-wall::before{content:"\F07FE"}.mdi-wall-fire::before{content:"\F1A11"}.mdi-wall-sconce::before{content:"\F091C"}.mdi-wall-sconce-flat::before{content:"\F091D"}.mdi-wall-sconce-flat-outline::before{content:"\F17C9"}.mdi-wall-sconce-flat-variant::before{content:"\F041C"}.mdi-wall-sconce-flat-variant-outline::before{content:"\F17CA"}.mdi-wall-sconce-outline::before{content:"\F17CB"}.mdi-wall-sconce-round::before{content:"\F0748"}.mdi-wall-sconce-round-outline::before{content:"\F17CC"}.mdi-wall-sconce-round-variant::before{content:"\F091E"}.mdi-wall-sconce-round-variant-outline::before{content:"\F17CD"}.mdi-wallet::before{content:"\F0584"}.mdi-wallet-bifold::before{content:"\F1C58"}.mdi-wallet-bifold-outline::before{content:"\F1C59"}.mdi-wallet-giftcard::before{content:"\F0585"}.mdi-wallet-membership::before{content:"\F0586"}.mdi-wallet-outline::before{content:"\F0BDD"}.mdi-wallet-plus::before{content:"\F0F8E"}.mdi-wallet-plus-outline::before{content:"\F0F8F"}.mdi-wallet-travel::before{content:"\F0587"}.mdi-wallpaper::before{content:"\F0E09"}.mdi-wan::before{content:"\F0588"}.mdi-wardrobe::before{content:"\F0F90"}.mdi-wardrobe-outline::before{content:"\F0F91"}.mdi-warehouse::before{content:"\F0F81"}.mdi-washing-machine::before{content:"\F072A"}.mdi-washing-machine-alert::before{content:"\F11BC"}.mdi-washing-machine-off::before{content:"\F11BD"}.mdi-watch::before{content:"\F0589"}.mdi-watch-export::before{content:"\F058A"}.mdi-watch-export-variant::before{content:"\F0895"}.mdi-watch-import::before{content:"\F058B"}.mdi-watch-import-variant::before{content:"\F0896"}.mdi-watch-variant::before{content:"\F0897"}.mdi-watch-vibrate::before{content:"\F06B1"}.mdi-watch-vibrate-off::before{content:"\F0CDA"}.mdi-water::before{content:"\F058C"}.mdi-water-alert::before{content:"\F1502"}.mdi-water-alert-outline::before{content:"\F1503"}.mdi-water-boiler::before{content:"\F0F92"}.mdi-water-boiler-alert::before{content:"\F11B3"}.mdi-water-boiler-auto::before{content:"\F1B98"}.mdi-water-boiler-off::before{content:"\F11B4"}.mdi-water-check::before{content:"\F1504"}.mdi-water-check-outline::before{content:"\F1505"}.mdi-water-circle::before{content:"\F1806"}.mdi-water-minus::before{content:"\F1506"}.mdi-water-minus-outline::before{content:"\F1507"}.mdi-water-off::before{content:"\F058D"}.mdi-water-off-outline::before{content:"\F1508"}.mdi-water-opacity::before{content:"\F1855"}.mdi-water-outline::before{content:"\F0E0A"}.mdi-water-percent::before{content:"\F058E"}.mdi-water-percent-alert::before{content:"\F1509"}.mdi-water-plus::before{content:"\F150A"}.mdi-water-plus-outline::before{content:"\F150B"}.mdi-water-polo::before{content:"\F12A0"}.mdi-water-pump::before{content:"\F058F"}.mdi-water-pump-off::before{content:"\F0F93"}.mdi-water-remove::before{content:"\F150C"}.mdi-water-remove-outline::before{content:"\F150D"}.mdi-water-sync::before{content:"\F17C6"}.mdi-water-thermometer::before{content:"\F1A85"}.mdi-water-thermometer-outline::before{content:"\F1A86"}.mdi-water-well::before{content:"\F106B"}.mdi-water-well-outline::before{content:"\F106C"}.mdi-waterfall::before{content:"\F1849"}.mdi-watering-can::before{content:"\F1481"}.mdi-watering-can-outline::before{content:"\F1482"}.mdi-watermark::before{content:"\F0612"}.mdi-wave::before{content:"\F0F2E"}.mdi-wave-arrow-down::before{content:"\F1CB0"}.mdi-wave-arrow-up::before{content:"\F1CB1"}.mdi-wave-undercurrent::before{content:"\F1CC0"}.mdi-waveform::before{content:"\F147D"}.mdi-waves::before{content:"\F078D"}.mdi-waves-arrow-left::before{content:"\F1859"}.mdi-waves-arrow-right::before{content:"\F185A"}.mdi-waves-arrow-up::before{content:"\F185B"}.mdi-waze::before{content:"\F0BDE"}.mdi-weather-cloudy::before{content:"\F0590"}.mdi-weather-cloudy-alert::before{content:"\F0F2F"}.mdi-weather-cloudy-arrow-right::before{content:"\F0E6E"}.mdi-weather-cloudy-clock::before{content:"\F18F6"}.mdi-weather-dust::before{content:"\F1B5A"}.mdi-weather-fog::before{content:"\F0591"}.mdi-weather-hail::before{content:"\F0592"}.mdi-weather-hazy::before{content:"\F0F30"}.mdi-weather-hurricane::before{content:"\F0898"}.mdi-weather-hurricane-outline::before{content:"\F1C78"}.mdi-weather-lightning::before{content:"\F0593"}.mdi-weather-lightning-rainy::before{content:"\F067E"}.mdi-weather-moonset::before{content:"\F1D15"}.mdi-weather-moonset-down::before{content:"\F1D16"}.mdi-weather-moonset-up::before{content:"\F1D17"}.mdi-weather-night::before{content:"\F0594"}.mdi-weather-night-partly-cloudy::before{content:"\F0F31"}.mdi-weather-partly-cloudy::before{content:"\F0595"}.mdi-weather-partly-lightning::before{content:"\F0F32"}.mdi-weather-partly-rainy::before{content:"\F0F33"}.mdi-weather-partly-snowy::before{content:"\F0F34"}.mdi-weather-partly-snowy-rainy::before{content:"\F0F35"}.mdi-weather-pouring::before{content:"\F0596"}.mdi-weather-rainy::before{content:"\F0597"}.mdi-weather-snowy::before{content:"\F0598"}.mdi-weather-snowy-heavy::before{content:"\F0F36"}.mdi-weather-snowy-rainy::before{content:"\F067F"}.mdi-weather-sunny::before{content:"\F0599"}.mdi-weather-sunny-alert::before{content:"\F0F37"}.mdi-weather-sunny-off::before{content:"\F14E4"}.mdi-weather-sunset::before{content:"\F059A"}.mdi-weather-sunset-down::before{content:"\F059B"}.mdi-weather-sunset-up::before{content:"\F059C"}.mdi-weather-tornado::before{content:"\F0F38"}.mdi-weather-windy::before{content:"\F059D"}.mdi-weather-windy-variant::before{content:"\F059E"}.mdi-web::before{content:"\F059F"}.mdi-web-box::before{content:"\F0F94"}.mdi-web-cancel::before{content:"\F1790"}.mdi-web-check::before{content:"\F0789"}.mdi-web-clock::before{content:"\F124A"}.mdi-web-minus::before{content:"\F10A0"}.mdi-web-off::before{content:"\F0A8E"}.mdi-web-plus::before{content:"\F0033"}.mdi-web-refresh::before{content:"\F1791"}.mdi-web-remove::before{content:"\F0551"}.mdi-web-sync::before{content:"\F1792"}.mdi-webcam::before{content:"\F05A0"}.mdi-webcam-off::before{content:"\F1737"}.mdi-webhook::before{content:"\F062F"}.mdi-webpack::before{content:"\F072B"}.mdi-webrtc::before{content:"\F1248"}.mdi-wechat::before{content:"\F0611"}.mdi-weight::before{content:"\F05A1"}.mdi-weight-gram::before{content:"\F0D3F"}.mdi-weight-kilogram::before{content:"\F05A2"}.mdi-weight-lifter::before{content:"\F115D"}.mdi-weight-pound::before{content:"\F09B5"}.mdi-whatsapp::before{content:"\F05A3"}.mdi-wheel-barrow::before{content:"\F14F2"}.mdi-wheelchair::before{content:"\F1A87"}.mdi-wheelchair-accessibility::before{content:"\F05A4"}.mdi-whistle::before{content:"\F09B6"}.mdi-whistle-outline::before{content:"\F12BC"}.mdi-white-balance-auto::before{content:"\F05A5"}.mdi-white-balance-incandescent::before{content:"\F05A6"}.mdi-white-balance-iridescent::before{content:"\F05A7"}.mdi-white-balance-sunny::before{content:"\F05A8"}.mdi-widgets::before{content:"\F072C"}.mdi-widgets-outline::before{content:"\F1355"}.mdi-wifi::before{content:"\F05A9"}.mdi-wifi-alert::before{content:"\F16B5"}.mdi-wifi-arrow-down::before{content:"\F16B6"}.mdi-wifi-arrow-left::before{content:"\F16B7"}.mdi-wifi-arrow-left-right::before{content:"\F16B8"}.mdi-wifi-arrow-right::before{content:"\F16B9"}.mdi-wifi-arrow-up::before{content:"\F16BA"}.mdi-wifi-arrow-up-down::before{content:"\F16BB"}.mdi-wifi-cancel::before{content:"\F16BC"}.mdi-wifi-check::before{content:"\F16BD"}.mdi-wifi-cog::before{content:"\F16BE"}.mdi-wifi-lock::before{content:"\F16BF"}.mdi-wifi-lock-open::before{content:"\F16C0"}.mdi-wifi-marker::before{content:"\F16C1"}.mdi-wifi-minus::before{content:"\F16C2"}.mdi-wifi-off::before{content:"\F05AA"}.mdi-wifi-plus::before{content:"\F16C3"}.mdi-wifi-refresh::before{content:"\F16C4"}.mdi-wifi-remove::before{content:"\F16C5"}.mdi-wifi-settings::before{content:"\F16C6"}.mdi-wifi-star::before{content:"\F0E0B"}.mdi-wifi-strength-1::before{content:"\F091F"}.mdi-wifi-strength-1-alert::before{content:"\F0920"}.mdi-wifi-strength-1-lock::before{content:"\F0921"}.mdi-wifi-strength-1-lock-open::before{content:"\F16CB"}.mdi-wifi-strength-2::before{content:"\F0922"}.mdi-wifi-strength-2-alert::before{content:"\F0923"}.mdi-wifi-strength-2-lock::before{content:"\F0924"}.mdi-wifi-strength-2-lock-open::before{content:"\F16CC"}.mdi-wifi-strength-3::before{content:"\F0925"}.mdi-wifi-strength-3-alert::before{content:"\F0926"}.mdi-wifi-strength-3-lock::before{content:"\F0927"}.mdi-wifi-strength-3-lock-open::before{content:"\F16CD"}.mdi-wifi-strength-4::before{content:"\F0928"}.mdi-wifi-strength-4-alert::before{content:"\F0929"}.mdi-wifi-strength-4-lock::before{content:"\F092A"}.mdi-wifi-strength-4-lock-open::before{content:"\F16CE"}.mdi-wifi-strength-alert-outline::before{content:"\F092B"}.mdi-wifi-strength-lock-open-outline::before{content:"\F16CF"}.mdi-wifi-strength-lock-outline::before{content:"\F092C"}.mdi-wifi-strength-off::before{content:"\F092D"}.mdi-wifi-strength-off-outline::before{content:"\F092E"}.mdi-wifi-strength-outline::before{content:"\F092F"}.mdi-wifi-sync::before{content:"\F16C7"}.mdi-wikipedia::before{content:"\F05AC"}.mdi-wind-power::before{content:"\F1A88"}.mdi-wind-power-outline::before{content:"\F1A89"}.mdi-wind-turbine::before{content:"\F0DA5"}.mdi-wind-turbine-alert::before{content:"\F19AB"}.mdi-wind-turbine-check::before{content:"\F19AC"}.mdi-window-close::before{content:"\F05AD"}.mdi-window-closed::before{content:"\F05AE"}.mdi-window-closed-variant::before{content:"\F11DB"}.mdi-window-maximize::before{content:"\F05AF"}.mdi-window-minimize::before{content:"\F05B0"}.mdi-window-open::before{content:"\F05B1"}.mdi-window-open-variant::before{content:"\F11DC"}.mdi-window-restore::before{content:"\F05B2"}.mdi-window-shutter::before{content:"\F111C"}.mdi-window-shutter-alert::before{content:"\F111D"}.mdi-window-shutter-auto::before{content:"\F1BA3"}.mdi-window-shutter-cog::before{content:"\F1A8A"}.mdi-window-shutter-open::before{content:"\F111E"}.mdi-window-shutter-settings::before{content:"\F1A8B"}.mdi-windsock::before{content:"\F15FA"}.mdi-wiper::before{content:"\F0AE9"}.mdi-wiper-wash::before{content:"\F0DA6"}.mdi-wiper-wash-alert::before{content:"\F18DF"}.mdi-wizard-hat::before{content:"\F1477"}.mdi-wordpress::before{content:"\F05B4"}.mdi-wrap::before{content:"\F05B6"}.mdi-wrap-disabled::before{content:"\F0BDF"}.mdi-wrench::before{content:"\F05B7"}.mdi-wrench-check::before{content:"\F1B8F"}.mdi-wrench-check-outline::before{content:"\F1B90"}.mdi-wrench-clock::before{content:"\F19A3"}.mdi-wrench-clock-outline::before{content:"\F1B93"}.mdi-wrench-cog::before{content:"\F1B91"}.mdi-wrench-cog-outline::before{content:"\F1B92"}.mdi-wrench-outline::before{content:"\F0BE0"}.mdi-xamarin::before{content:"\F0845"}.mdi-xml::before{content:"\F05C0"}.mdi-xmpp::before{content:"\F07FF"}.mdi-yahoo::before{content:"\F0B4F"}.mdi-yeast::before{content:"\F05C1"}.mdi-yin-yang::before{content:"\F0680"}.mdi-yoga::before{content:"\F117C"}.mdi-youtube::before{content:"\F05C3"}.mdi-youtube-gaming::before{content:"\F0848"}.mdi-youtube-studio::before{content:"\F0847"}.mdi-youtube-subscription::before{content:"\F0D40"}.mdi-youtube-tv::before{content:"\F0448"}.mdi-yurt::before{content:"\F1516"}.mdi-z-wave::before{content:"\F0AEA"}.mdi-zend::before{content:"\F0AEB"}.mdi-zigbee::before{content:"\F0D41"}.mdi-zip-box::before{content:"\F05C4"}.mdi-zip-box-outline::before{content:"\F0FFA"}.mdi-zip-disk::before{content:"\F0A23"}.mdi-zodiac-aquarius::before{content:"\F0A7D"}.mdi-zodiac-aries::before{content:"\F0A7E"}.mdi-zodiac-cancer::before{content:"\F0A7F"}.mdi-zodiac-capricorn::before{content:"\F0A80"}.mdi-zodiac-gemini::before{content:"\F0A81"}.mdi-zodiac-leo::before{content:"\F0A82"}.mdi-zodiac-libra::before{content:"\F0A83"}.mdi-zodiac-pisces::before{content:"\F0A84"}.mdi-zodiac-sagittarius::before{content:"\F0A85"}.mdi-zodiac-scorpio::before{content:"\F0A86"}.mdi-zodiac-taurus::before{content:"\F0A87"}.mdi-zodiac-virgo::before{content:"\F0A88"}.mdi-blank::before{content:"\F68C";visibility:hidden}.mdi-18px.mdi-set,.mdi-18px.mdi:before{font-size:18px}.mdi-24px.mdi-set,.mdi-24px.mdi:before{font-size:24px}.mdi-36px.mdi-set,.mdi-36px.mdi:before{font-size:36px}.mdi-48px.mdi-set,.mdi-48px.mdi:before{font-size:48px}.mdi-dark:before{color:rgba(0,0,0,0.54)}.mdi-dark.mdi-inactive:before{color:rgba(0,0,0,0.26)}.mdi-light:before{color:#fff}.mdi-light.mdi-inactive:before{color:rgba(255,255,255,0.3)}.mdi-rotate-45:before{-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.mdi-rotate-90:before{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.mdi-rotate-135:before{-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);transform:rotate(135deg)}.mdi-rotate-180:before{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.mdi-rotate-225:before{-webkit-transform:rotate(225deg);-ms-transform:rotate(225deg);transform:rotate(225deg)}.mdi-rotate-270:before{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.mdi-rotate-315:before{-webkit-transform:rotate(315deg);-ms-transform:rotate(315deg);transform:rotate(315deg)}.mdi-flip-h:before{-webkit-transform:scaleX(-1);transform:scaleX(-1);filter:FlipH;-ms-filter:"FlipH"}.mdi-flip-v:before{-webkit-transform:scaleY(-1);transform:scaleY(-1);filter:FlipV;-ms-filter:"FlipV"}.mdi-spin:before{-webkit-animation:mdi-spin 2s infinite linear;animation:mdi-spin 2s infinite linear}@-webkit-keyframes mdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes mdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}} - -/*# sourceMappingURL=materialdesignicons.css.map */ \ No newline at end of file diff --git a/web/scripts/api.js b/web/scripts/api.js deleted file mode 100644 index eee1c9a15..000000000 --- a/web/scripts/api.js +++ /dev/null @@ -1,482 +0,0 @@ -class ComfyApi extends EventTarget { - #registered = new Set(); - - constructor() { - super(); - this.api_host = location.host; - this.api_base = location.pathname.split('/').slice(0, -1).join('/'); - this.initialClientId = sessionStorage.getItem("clientId"); - } - - apiURL(route) { - return this.api_base + route; - } - - fetchApi(route, options) { - if (!options) { - options = {}; - } - if (!options.headers) { - options.headers = {}; - } - options.headers["Comfy-User"] = this.user; - return fetch(this.apiURL(route), options); - } - - addEventListener(type, callback, options) { - super.addEventListener(type, callback, options); - this.#registered.add(type); - } - - /** - * Poll status for colab and other things that don't support websockets. - */ - #pollQueue() { - setInterval(async () => { - try { - const resp = await this.fetchApi("/prompt"); - const status = await resp.json(); - this.dispatchEvent(new CustomEvent("status", { detail: status })); - } catch (error) { - this.dispatchEvent(new CustomEvent("status", { detail: null })); - } - }, 1000); - } - - /** - * Creates and connects a WebSocket for realtime updates - * @param {boolean} isReconnect If the socket is connection is a reconnect attempt - */ - #createSocket(isReconnect) { - if (this.socket) { - return; - } - - let opened = false; - let existingSession = window.name; - if (existingSession) { - existingSession = "?clientId=" + existingSession; - } - this.socket = new WebSocket( - `ws${window.location.protocol === "https:" ? "s" : ""}://${this.api_host}${this.api_base}/ws${existingSession}` - ); - this.socket.binaryType = "arraybuffer"; - - this.socket.addEventListener("open", () => { - opened = true; - if (isReconnect) { - this.dispatchEvent(new CustomEvent("reconnected")); - } - }); - - this.socket.addEventListener("error", () => { - if (this.socket) this.socket.close(); - if (!isReconnect && !opened) { - this.#pollQueue(); - } - }); - - this.socket.addEventListener("close", () => { - setTimeout(() => { - this.socket = null; - this.#createSocket(true); - }, 300); - if (opened) { - this.dispatchEvent(new CustomEvent("status", { detail: null })); - this.dispatchEvent(new CustomEvent("reconnecting")); - } - }); - - this.socket.addEventListener("message", (event) => { - try { - if (event.data instanceof ArrayBuffer) { - const view = new DataView(event.data); - const eventType = view.getUint32(0); - const buffer = event.data.slice(4); - switch (eventType) { - case 1: - const view2 = new DataView(event.data); - const imageType = view2.getUint32(0) - let imageMime - switch (imageType) { - case 1: - default: - imageMime = "image/jpeg"; - break; - case 2: - imageMime = "image/png" - } - const imageBlob = new Blob([buffer.slice(4)], { type: imageMime }); - this.dispatchEvent(new CustomEvent("b_preview", { detail: imageBlob })); - break; - default: - throw new Error(`Unknown binary websocket message of type ${eventType}`); - } - } - else { - const msg = JSON.parse(event.data); - switch (msg.type) { - case "status": - if (msg.data.sid) { - this.clientId = msg.data.sid; - window.name = this.clientId; // use window name so it isnt reused when duplicating tabs - sessionStorage.setItem("clientId", this.clientId); // store in session storage so duplicate tab can load correct workflow - } - this.dispatchEvent(new CustomEvent("status", { detail: msg.data.status })); - break; - case "progress": - this.dispatchEvent(new CustomEvent("progress", { detail: msg.data })); - break; - case "executing": - this.dispatchEvent(new CustomEvent("executing", { detail: msg.data.display_node })); - break; - case "executed": - this.dispatchEvent(new CustomEvent("executed", { detail: msg.data })); - break; - case "execution_start": - this.dispatchEvent(new CustomEvent("execution_start", { detail: msg.data })); - break; - case "execution_success": - this.dispatchEvent(new CustomEvent("execution_success", { detail: msg.data })); - break; - case "execution_error": - this.dispatchEvent(new CustomEvent("execution_error", { detail: msg.data })); - break; - case "execution_cached": - this.dispatchEvent(new CustomEvent("execution_cached", { detail: msg.data })); - break; - default: - if (this.#registered.has(msg.type)) { - this.dispatchEvent(new CustomEvent(msg.type, { detail: msg.data })); - } else { - throw new Error(`Unknown message type ${msg.type}`); - } - } - } - } catch (error) { - console.warn("Unhandled message:", event.data, error); - } - }); - } - - /** - * Initialises sockets and realtime updates - */ - init() { - this.#createSocket(); - } - - /** - * Gets a list of extension urls - * @returns An array of script urls to import - */ - async getExtensions() { - const resp = await this.fetchApi("/extensions", { cache: "no-store" }); - return await resp.json(); - } - - /** - * Gets a list of embedding names - * @returns An array of script urls to import - */ - async getEmbeddings() { - const resp = await this.fetchApi("/embeddings", { cache: "no-store" }); - return await resp.json(); - } - - /** - * Loads node object definitions for the graph - * @returns The node definitions - */ - async getNodeDefs() { - const resp = await this.fetchApi("/object_info", { cache: "no-store" }); - return await resp.json(); - } - - /** - * - * @param {number} number The index at which to queue the prompt, passing -1 will insert the prompt at the front of the queue - * @param {object} prompt The prompt data to queue - */ - async queuePrompt(number, { output, workflow }) { - const body = { - client_id: this.clientId, - prompt: output, - extra_data: { extra_pnginfo: { workflow } }, - }; - - if (number === -1) { - body.front = true; - } else if (number != 0) { - body.number = number; - } - - const res = await this.fetchApi("/prompt", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - }); - - if (res.status !== 200) { - throw { - response: await res.json(), - }; - } - - return await res.json(); - } - - /** - * Loads a list of items (queue or history) - * @param {string} type The type of items to load, queue or history - * @returns The items of the specified type grouped by their status - */ - async getItems(type) { - if (type === "queue") { - return this.getQueue(); - } - return this.getHistory(); - } - - /** - * Gets the current state of the queue - * @returns The currently running and queued items - */ - async getQueue() { - try { - const res = await this.fetchApi("/queue"); - const data = await res.json(); - return { - // Running action uses a different endpoint for cancelling - Running: data.queue_running.map((prompt) => ({ - prompt, - remove: { name: "Cancel", cb: () => api.interrupt() }, - })), - Pending: data.queue_pending.map((prompt) => ({ prompt })), - }; - } catch (error) { - console.error(error); - return { Running: [], Pending: [] }; - } - } - - /** - * Gets the prompt execution history - * @returns Prompt history including node outputs - */ - async getHistory(max_items=200) { - try { - const res = await this.fetchApi(`/history?max_items=${max_items}`); - return { History: Object.values(await res.json()) }; - } catch (error) { - console.error(error); - return { History: [] }; - } - } - - /** - * Gets system & device stats - * @returns System stats such as python version, OS, per device info - */ - async getSystemStats() { - const res = await this.fetchApi("/system_stats"); - return await res.json(); - } - - /** - * Sends a POST request to the API - * @param {*} type The endpoint to post to - * @param {*} body Optional POST data - */ - async #postItem(type, body) { - try { - await this.fetchApi("/" + type, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: body ? JSON.stringify(body) : undefined, - }); - } catch (error) { - console.error(error); - } - } - - /** - * Deletes an item from the specified list - * @param {string} type The type of item to delete, queue or history - * @param {number} id The id of the item to delete - */ - async deleteItem(type, id) { - await this.#postItem(type, { delete: [id] }); - } - - /** - * Clears the specified list - * @param {string} type The type of list to clear, queue or history - */ - async clearItems(type) { - await this.#postItem(type, { clear: true }); - } - - /** - * Interrupts the execution of the running prompt - */ - async interrupt() { - await this.#postItem("interrupt", null); - } - - /** - * Gets user configuration data and where data should be stored - * @returns { Promise<{ storage: "server" | "browser", users?: Promise, migrated?: boolean }> } - */ - async getUserConfig() { - return (await this.fetchApi("/users")).json(); - } - - /** - * Creates a new user - * @param { string } username - * @returns The fetch response - */ - createUser(username) { - return this.fetchApi("/users", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ username }), - }); - } - - /** - * Gets all setting values for the current user - * @returns { Promise } A dictionary of id -> value - */ - async getSettings() { - return (await this.fetchApi("/settings")).json(); - } - - /** - * Gets a setting for the current user - * @param { string } id The id of the setting to fetch - * @returns { Promise } The setting value - */ - async getSetting(id) { - return (await this.fetchApi(`/settings/${encodeURIComponent(id)}`)).json(); - } - - /** - * Stores a dictionary of settings for the current user - * @param { Record } settings Dictionary of setting id -> value to save - * @returns { Promise } - */ - async storeSettings(settings) { - return this.fetchApi(`/settings`, { - method: "POST", - body: JSON.stringify(settings) - }); - } - - /** - * Stores a setting for the current user - * @param { string } id The id of the setting to update - * @param { unknown } value The value of the setting - * @returns { Promise } - */ - async storeSetting(id, value) { - return this.fetchApi(`/settings/${encodeURIComponent(id)}`, { - method: "POST", - body: JSON.stringify(value) - }); - } - - /** - * Gets a user data file for the current user - * @param { string } file The name of the userdata file to load - * @param { RequestInit } [options] - * @returns { Promise } The fetch response object - */ - async getUserData(file, options) { - return this.fetchApi(`/userdata/${encodeURIComponent(file)}`, options); - } - - /** - * Stores a user data file for the current user - * @param { string } file The name of the userdata file to save - * @param { unknown } data The data to save to the file - * @param { RequestInit & { overwrite?: boolean, stringify?: boolean, throwOnError?: boolean } } [options] - * @returns { Promise } - */ - async storeUserData(file, data, options = { overwrite: true, stringify: true, throwOnError: true }) { - const resp = await this.fetchApi(`/userdata/${encodeURIComponent(file)}?overwrite=${options?.overwrite}`, { - method: "POST", - body: options?.stringify ? JSON.stringify(data) : data, - ...options, - }); - if (resp.status !== 200 && options?.throwOnError !== false) { - throw new Error(`Error storing user data file '${file}': ${resp.status} ${(await resp).statusText}`); - } - return resp; - } - - /** - * Deletes a user data file for the current user - * @param { string } file The name of the userdata file to delete - */ - async deleteUserData(file) { - const resp = await this.fetchApi(`/userdata/${encodeURIComponent(file)}`, { - method: "DELETE", - }); - if (resp.status !== 204) { - throw new Error(`Error removing user data file '${file}': ${resp.status} ${(resp).statusText}`); - } - } - - /** - * Move a user data file for the current user - * @param { string } source The userdata file to move - * @param { string } dest The destination for the file - */ - async moveUserData(source, dest, options = { overwrite: false }) { - const resp = await this.fetchApi(`/userdata/${encodeURIComponent(source)}/move/${encodeURIComponent(dest)}?overwrite=${options?.overwrite}`, { - method: "POST", - }); - return resp; - } - - /** - * @overload - * Lists user data files for the current user - * @param { string } dir The directory in which to list files - * @param { boolean } [recurse] If the listing should be recursive - * @param { true } [split] If the paths should be split based on the os path separator - * @returns { Promise> } The list of split file paths in the format [fullPath, ...splitPath] - */ - /** - * @overload - * Lists user data files for the current user - * @param { string } dir The directory in which to list files - * @param { boolean } [recurse] If the listing should be recursive - * @param { false | undefined } [split] If the paths should be split based on the os path separator - * @returns { Promise> } The list of files - */ - async listUserData(dir, recurse, split) { - const resp = await this.fetchApi( - `/userdata?${new URLSearchParams({ - recurse, - dir, - split, - })}` - ); - if (resp.status === 404) return []; - if (resp.status !== 200) { - throw new Error(`Error getting user data list '${dir}': ${resp.status} ${resp.statusText}`); - } - return resp.json(); - } -} - -export const api = new ComfyApi(); diff --git a/web/scripts/app.js b/web/scripts/app.js deleted file mode 100644 index 6e2b39538..000000000 --- a/web/scripts/app.js +++ /dev/null @@ -1,2460 +0,0 @@ -import { ComfyLogging } from "./logging.js"; -import { ComfyWidgets, initWidgets } from "./widgets.js"; -import { ComfyUI, $el } from "./ui.js"; -import { api } from "./api.js"; -import { defaultGraph } from "./defaultGraph.js"; -import { getPngMetadata, getWebpMetadata, getFlacMetadata, importA1111, getLatentMetadata } from "./pnginfo.js"; -import { addDomClippingSetting } from "./domWidget.js"; -import { createImageHost, calculateImageGrid } from "./ui/imagePreview.js"; -import { ComfyAppMenu } from "./ui/menu/index.js"; -import { getStorageValue, setStorageValue } from "./utils.js"; -import { ComfyWorkflowManager } from "./workflows.js"; -export const ANIM_PREVIEW_WIDGET = "$$comfy_animation_preview"; - -function sanitizeNodeName(string) { - let entityMap = { - '&': '', - '<': '', - '>': '', - '"': '', - "'": '', - '`': '', - '=': '' - }; - return String(string).replace(/[&<>"'`=]/g, function fromEntityMap (s) { - return entityMap[s]; - }); -} - -/** - * @typedef {import("types/comfy").ComfyExtension} ComfyExtension - */ - -export class ComfyApp { - /** - * List of entries to queue - * @type {{number: number, batchCount: number}[]} - */ - #queueItems = []; - /** - * If the queue is currently being processed - * @type {boolean} - */ - #processingQueue = false; - - /** - * Content Clipboard - * @type {serialized node object} - */ - static clipspace = null; - static clipspace_invalidate_handler = null; - static open_maskeditor = null; - static clipspace_return_node = null; - - constructor() { - this.ui = new ComfyUI(this); - this.logging = new ComfyLogging(this); - this.workflowManager = new ComfyWorkflowManager(this); - this.bodyTop = $el("div.comfyui-body-top", { parent: document.body }); - this.bodyLeft = $el("div.comfyui-body-left", { parent: document.body }); - this.bodyRight = $el("div.comfyui-body-right", { parent: document.body }); - this.bodyBottom = $el("div.comfyui-body-bottom", { parent: document.body }); - this.menu = new ComfyAppMenu(this); - - /** - * List of extensions that are registered with the app - * @type {ComfyExtension[]} - */ - this.extensions = []; - - /** - * Stores the execution output data for each node - * @type {Record} - */ - this._nodeOutputs = {}; - - /** - * Stores the preview image data for each node - * @type {Record} - */ - this.nodePreviewImages = {}; - - /** - * If the shift key on the keyboard is pressed - * @type {boolean} - */ - this.shiftDown = false; - } - - get nodeOutputs() { - return this._nodeOutputs; - } - - set nodeOutputs(value) { - this._nodeOutputs = value; - this.#invokeExtensions("onNodeOutputsUpdated", value); - } - - getPreviewFormatParam() { - let preview_format = this.ui.settings.getSettingValue("Comfy.PreviewFormat"); - if(preview_format) - return `&preview=${preview_format}`; - else - return ""; - } - - getRandParam() { - return "&rand=" + Math.random(); - } - - static isImageNode(node) { - return node.imgs || (node && node.widgets && node.widgets.findIndex(obj => obj.name === 'image') >= 0); - } - - static onClipspaceEditorSave() { - if(ComfyApp.clipspace_return_node) { - ComfyApp.pasteFromClipspace(ComfyApp.clipspace_return_node); - } - } - - static onClipspaceEditorClosed() { - ComfyApp.clipspace_return_node = null; - } - - static copyToClipspace(node) { - var widgets = null; - if(node.widgets) { - widgets = node.widgets.map(({ type, name, value }) => ({ type, name, value })); - } - - var imgs = undefined; - var orig_imgs = undefined; - if(node.imgs != undefined) { - imgs = []; - orig_imgs = []; - - for (let i = 0; i < node.imgs.length; i++) { - imgs[i] = new Image(); - imgs[i].src = node.imgs[i].src; - orig_imgs[i] = imgs[i]; - } - } - - var selectedIndex = 0; - if(node.imageIndex) { - selectedIndex = node.imageIndex; - } - - ComfyApp.clipspace = { - 'widgets': widgets, - 'imgs': imgs, - 'original_imgs': orig_imgs, - 'images': node.images, - 'selectedIndex': selectedIndex, - 'img_paste_mode': 'selected' // reset to default im_paste_mode state on copy action - }; - - ComfyApp.clipspace_return_node = null; - - if(ComfyApp.clipspace_invalidate_handler) { - ComfyApp.clipspace_invalidate_handler(); - } - } - - static pasteFromClipspace(node) { - if(ComfyApp.clipspace) { - // image paste - if(ComfyApp.clipspace.imgs && node.imgs) { - if(node.images && ComfyApp.clipspace.images) { - if(ComfyApp.clipspace['img_paste_mode'] == 'selected') { - node.images = [ComfyApp.clipspace.images[ComfyApp.clipspace['selectedIndex']]]; - } - else { - node.images = ComfyApp.clipspace.images; - } - - if(app.nodeOutputs[node.id + ""]) - app.nodeOutputs[node.id + ""].images = node.images; - } - - if(ComfyApp.clipspace.imgs) { - // deep-copy to cut link with clipspace - if(ComfyApp.clipspace['img_paste_mode'] == 'selected') { - const img = new Image(); - img.src = ComfyApp.clipspace.imgs[ComfyApp.clipspace['selectedIndex']].src; - node.imgs = [img]; - node.imageIndex = 0; - } - else { - const imgs = []; - for(let i=0; i obj.name === 'image'); - if(index >= 0) { - if(node.widgets[index].type != 'image' && typeof node.widgets[index].value == "string" && clip_image.filename) { - node.widgets[index].value = (clip_image.subfolder?clip_image.subfolder+'/':'') + clip_image.filename + (clip_image.type?` [${clip_image.type}]`:''); - } - else { - node.widgets[index].value = clip_image; - } - } - } - if(ComfyApp.clipspace.widgets) { - ComfyApp.clipspace.widgets.forEach(({ type, name, value }) => { - const prop = Object.values(node.widgets).find(obj => obj.type === type && obj.name === name); - if (prop && prop.type != 'button') { - if(prop.type != 'image' && typeof prop.value == "string" && value.filename) { - prop.value = (value.subfolder?value.subfolder+'/':'') + value.filename + (value.type?` [${value.type}]`:''); - } - else { - prop.value = value; - prop.callback(value); - } - } - }); - } - } - - app.graph.setDirtyCanvas(true); - } - } - - /** - * Invoke an extension callback - * @param {keyof ComfyExtension} method The extension callback to execute - * @param {any[]} args Any arguments to pass to the callback - * @returns - */ - #invokeExtensions(method, ...args) { - let results = []; - for (const ext of this.extensions) { - if (method in ext) { - try { - results.push(ext[method](...args, this)); - } catch (error) { - console.error( - `Error calling extension '${ext.name}' method '${method}'`, - { error }, - { extension: ext }, - { args } - ); - } - } - } - return results; - } - - /** - * Invoke an async extension callback - * Each callback will be invoked concurrently - * @param {string} method The extension callback to execute - * @param {...any} args Any arguments to pass to the callback - * @returns - */ - async #invokeExtensionsAsync(method, ...args) { - return await Promise.all( - this.extensions.map(async (ext) => { - if (method in ext) { - try { - return await ext[method](...args, this); - } catch (error) { - console.error( - `Error calling extension '${ext.name}' method '${method}'`, - { error }, - { extension: ext }, - { args } - ); - } - } - }) - ); - } - - #addRestoreWorkflowView() { - const serialize = LGraph.prototype.serialize; - const self = this; - LGraph.prototype.serialize = function() { - const workflow = serialize.apply(this, arguments); - - // Store the drag & scale info in the serialized workflow if the setting is enabled - if (self.enableWorkflowViewRestore.value) { - if (!workflow.extra) { - workflow.extra = {}; - } - workflow.extra.ds = { - scale: self.canvas.ds.scale, - offset: self.canvas.ds.offset, - }; - } else if (workflow.extra?.ds) { - // Clear any old view data - delete workflow.extra.ds; - } - - return workflow; - } - this.enableWorkflowViewRestore = this.ui.settings.addSetting({ - id: "Comfy.EnableWorkflowViewRestore", - name: "Save and restore canvas position and zoom level in workflows", - type: "boolean", - defaultValue: true - }); - } - - /** - * Adds special context menu handling for nodes - * e.g. this adds Open Image functionality for nodes that show images - * @param {*} node The node to add the menu handler - */ - #addNodeContextMenuHandler(node) { - function getCopyImageOption(img) { - if (typeof window.ClipboardItem === "undefined") return []; - return [ - { - content: "Copy Image", - callback: async () => { - const url = new URL(img.src); - url.searchParams.delete("preview"); - - const writeImage = async (blob) => { - await navigator.clipboard.write([ - new ClipboardItem({ - [blob.type]: blob, - }), - ]); - }; - - try { - const data = await fetch(url); - const blob = await data.blob(); - try { - await writeImage(blob); - } catch (error) { - // Chrome seems to only support PNG on write, convert and try again - if (blob.type !== "image/png") { - const canvas = $el("canvas", { - width: img.naturalWidth, - height: img.naturalHeight, - }); - const ctx = canvas.getContext("2d"); - let image; - if (typeof window.createImageBitmap === "undefined") { - image = new Image(); - const p = new Promise((resolve, reject) => { - image.onload = resolve; - image.onerror = reject; - }).finally(() => { - URL.revokeObjectURL(image.src); - }); - image.src = URL.createObjectURL(blob); - await p; - } else { - image = await createImageBitmap(blob); - } - try { - ctx.drawImage(image, 0, 0); - canvas.toBlob(writeImage, "image/png"); - } finally { - if (typeof image.close === "function") { - image.close(); - } - } - - return; - } - throw error; - } - } catch (error) { - alert("Error copying image: " + (error.message ?? error)); - } - }, - }, - ]; - } - - node.prototype.getExtraMenuOptions = function (_, options) { - if (this.imgs) { - // If this node has images then we add an open in new tab item - let img; - if (this.imageIndex != null) { - // An image is selected so select that - img = this.imgs[this.imageIndex]; - } else if (this.overIndex != null) { - // No image is selected but one is hovered - img = this.imgs[this.overIndex]; - } - if (img) { - options.unshift( - { - content: "Open Image", - callback: () => { - let url = new URL(img.src); - url.searchParams.delete("preview"); - window.open(url, "_blank"); - }, - }, - ...getCopyImageOption(img), - { - content: "Save Image", - callback: () => { - const a = document.createElement("a"); - let url = new URL(img.src); - url.searchParams.delete("preview"); - a.href = url; - a.setAttribute("download", new URLSearchParams(url.search).get("filename")); - document.body.append(a); - a.click(); - requestAnimationFrame(() => a.remove()); - }, - } - ); - } - } - - options.push({ - content: "Bypass", - callback: (obj) => { - if (this.mode === 4) this.mode = 0; - else this.mode = 4; - this.graph.change(); - }, - }); - - // prevent conflict of clipspace content - if (!ComfyApp.clipspace_return_node) { - options.push({ - content: "Copy (Clipspace)", - callback: (obj) => { - ComfyApp.copyToClipspace(this); - }, - }); - - if (ComfyApp.clipspace != null) { - options.push({ - content: "Paste (Clipspace)", - callback: () => { - ComfyApp.pasteFromClipspace(this); - }, - }); - } - - if (ComfyApp.isImageNode(this)) { - options.push({ - content: "Open in MaskEditor", - callback: (obj) => { - ComfyApp.copyToClipspace(this); - ComfyApp.clipspace_return_node = this; - ComfyApp.open_maskeditor(); - }, - }); - } - } - }; - } - - #addNodeKeyHandler(node) { - const app = this; - const origNodeOnKeyDown = node.prototype.onKeyDown; - - node.prototype.onKeyDown = function(e) { - if (origNodeOnKeyDown && origNodeOnKeyDown.apply(this, e) === false) { - return false; - } - - if (this.flags.collapsed || !this.imgs || this.imageIndex === null) { - return; - } - - let handled = false; - - if (e.key === "ArrowLeft" || e.key === "ArrowRight") { - if (e.key === "ArrowLeft") { - this.imageIndex -= 1; - } else if (e.key === "ArrowRight") { - this.imageIndex += 1; - } - this.imageIndex %= this.imgs.length; - - if (this.imageIndex < 0) { - this.imageIndex = this.imgs.length + this.imageIndex; - } - handled = true; - } else if (e.key === "Escape") { - this.imageIndex = null; - handled = true; - } - - if (handled === true) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; - } - } - } - - /** - * Adds Custom drawing logic for nodes - * e.g. Draws images and handles thumbnail navigation on nodes that output images - * @param {*} node The node to add the draw handler - */ - #addDrawBackgroundHandler(node) { - const app = this; - - function getImageTop(node) { - let shiftY; - if (node.imageOffset != null) { - shiftY = node.imageOffset; - } else { - if (node.widgets?.length) { - const w = node.widgets[node.widgets.length - 1]; - shiftY = w.last_y; - if (w.computeSize) { - shiftY += w.computeSize()[1] + 4; - } - else if(w.computedHeight) { - shiftY += w.computedHeight; - } - else { - shiftY += LiteGraph.NODE_WIDGET_HEIGHT + 4; - } - } else { - shiftY = node.computeSize()[1]; - } - } - return shiftY; - } - - node.prototype.setSizeForImage = function (force) { - if(!force && this.animatedImages) return; - - if (this.inputHeight || this.freeWidgetSpace > 210) { - this.setSize(this.size); - return; - } - const minHeight = getImageTop(this) + 220; - if (this.size[1] < minHeight) { - this.setSize([this.size[0], minHeight]); - } - }; - - node.prototype.onDrawBackground = function (ctx) { - if (!this.flags.collapsed) { - let imgURLs = [] - let imagesChanged = false - - const output = app.nodeOutputs[this.id + ""]; - if (output?.images) { - this.animatedImages = output?.animated?.find(Boolean); - if (this.images !== output.images) { - this.images = output.images; - imagesChanged = true; - imgURLs = imgURLs.concat( - output.images.map((params) => { - return api.apiURL( - "/view?" + - new URLSearchParams(params).toString() + - (this.animatedImages ? "" : app.getPreviewFormatParam()) + app.getRandParam() - ); - }) - ); - } - } - - const preview = app.nodePreviewImages[this.id + ""] - if (this.preview !== preview) { - this.preview = preview - imagesChanged = true; - if (preview != null) { - imgURLs.push(preview); - } - } - - if (imagesChanged) { - this.imageIndex = null; - if (imgURLs.length > 0) { - Promise.all( - imgURLs.map((src) => { - return new Promise((r) => { - const img = new Image(); - img.onload = () => r(img); - img.onerror = () => r(null); - img.src = src - }); - }) - ).then((imgs) => { - if ((!output || this.images === output.images) && (!preview || this.preview === preview)) { - this.imgs = imgs.filter(Boolean); - this.setSizeForImage?.(); - app.graph.setDirtyCanvas(true); - } - }); - } - else { - this.imgs = null; - } - } - - function calculateGrid(w, h, n) { - let columns, rows, cellsize; - - if (w > h) { - cellsize = h; - columns = Math.ceil(w / cellsize); - rows = Math.ceil(n / columns); - } else { - cellsize = w; - rows = Math.ceil(h / cellsize); - columns = Math.ceil(n / rows); - } - - while (columns * rows < n) { - cellsize++; - if (w >= h) { - columns = Math.ceil(w / cellsize); - rows = Math.ceil(n / columns); - } else { - rows = Math.ceil(h / cellsize); - columns = Math.ceil(n / rows); - } - } - - const cell_size = Math.min(w/columns, h/rows); - return {cell_size, columns, rows}; - } - - function is_all_same_aspect_ratio(imgs) { - // assume: imgs.length >= 2 - let ratio = imgs[0].naturalWidth/imgs[0].naturalHeight; - - for(let i=1; i w.name === ANIM_PREVIEW_WIDGET); - - if(this.animatedImages) { - // Instead of using the canvas we'll use a IMG - if(widgetIdx > -1) { - // Replace content - const widget = this.widgets[widgetIdx]; - widget.options.host.updateImages(this.imgs); - } else { - const host = createImageHost(this); - this.setSizeForImage(true); - const widget = this.addDOMWidget(ANIM_PREVIEW_WIDGET, "img", host.el, { - host, - getHeight: host.getHeight, - onDraw: host.onDraw, - hideOnZoom: false - }); - widget.serializeValue = () => undefined; - widget.options.host.updateImages(this.imgs); - } - return; - } - - if (widgetIdx > -1) { - this.widgets[widgetIdx].onRemove?.(); - this.widgets.splice(widgetIdx, 1); - } - - const canvas = app.graph.list_of_graphcanvas[0]; - const mouse = canvas.graph_mouse; - if (!canvas.pointer_is_down && this.pointerDown) { - if (mouse[0] === this.pointerDown.pos[0] && mouse[1] === this.pointerDown.pos[1]) { - this.imageIndex = this.pointerDown.index; - } - this.pointerDown = null; - } - - let imageIndex = this.imageIndex; - const numImages = this.imgs.length; - if (numImages === 1 && !imageIndex) { - this.imageIndex = imageIndex = 0; - } - - const top = getImageTop(this); - var shiftY = top; - - let dw = this.size[0]; - let dh = this.size[1]; - dh -= shiftY; - - if (imageIndex == null) { - var cellWidth, cellHeight, shiftX, cell_padding, cols; - - const compact_mode = is_all_same_aspect_ratio(this.imgs); - if(!compact_mode) { - // use rectangle cell style and border line - cell_padding = 2; - const { cell_size, columns, rows } = calculateGrid(dw, dh, numImages); - cols = columns; - - cellWidth = cell_size; - cellHeight = cell_size; - shiftX = (dw-cell_size*cols)/2; - shiftY = (dh-cell_size*rows)/2 + top; - } - else { - cell_padding = 0; - ({ cellWidth, cellHeight, cols, shiftX } = calculateImageGrid(this.imgs, dw, dh)); - } - - let anyHovered = false; - this.imageRects = []; - for (let i = 0; i < numImages; i++) { - const img = this.imgs[i]; - const row = Math.floor(i / cols); - const col = i % cols; - const x = col * cellWidth + shiftX; - const y = row * cellHeight + shiftY; - if (!anyHovered) { - anyHovered = LiteGraph.isInsideRectangle( - mouse[0], - mouse[1], - x + this.pos[0], - y + this.pos[1], - cellWidth, - cellHeight - ); - if (anyHovered) { - this.overIndex = i; - let value = 110; - if (canvas.pointer_is_down) { - if (!this.pointerDown || this.pointerDown.index !== i) { - this.pointerDown = { index: i, pos: [...mouse] }; - } - value = 125; - } - ctx.filter = `contrast(${value}%) brightness(${value}%)`; - canvas.canvas.style.cursor = "pointer"; - } - } - this.imageRects.push([x, y, cellWidth, cellHeight]); - - let wratio = cellWidth/img.width; - let hratio = cellHeight/img.height; - var ratio = Math.min(wratio, hratio); - - let imgHeight = ratio * img.height; - let imgY = row * cellHeight + shiftY + (cellHeight - imgHeight)/2; - let imgWidth = ratio * img.width; - let imgX = col * cellWidth + shiftX + (cellWidth - imgWidth)/2; - - ctx.drawImage(img, imgX+cell_padding, imgY+cell_padding, imgWidth-cell_padding*2, imgHeight-cell_padding*2); - if(!compact_mode) { - // rectangle cell and border line style - ctx.strokeStyle = "#8F8F8F"; - ctx.lineWidth = 1; - ctx.strokeRect(x+cell_padding, y+cell_padding, cellWidth-cell_padding*2, cellHeight-cell_padding*2); - } - - ctx.filter = "none"; - } - - if (!anyHovered) { - this.pointerDown = null; - this.overIndex = null; - } - } else { - // Draw individual - let w = this.imgs[imageIndex].naturalWidth; - let h = this.imgs[imageIndex].naturalHeight; - - const scaleX = dw / w; - const scaleY = dh / h; - const scale = Math.min(scaleX, scaleY, 1); - - w *= scale; - h *= scale; - - let x = (dw - w) / 2; - let y = (dh - h) / 2 + shiftY; - ctx.drawImage(this.imgs[imageIndex], x, y, w, h); - - const drawButton = (x, y, sz, text) => { - const hovered = LiteGraph.isInsideRectangle(mouse[0], mouse[1], x + this.pos[0], y + this.pos[1], sz, sz); - let fill = "#333"; - let textFill = "#fff"; - let isClicking = false; - if (hovered) { - canvas.canvas.style.cursor = "pointer"; - if (canvas.pointer_is_down) { - fill = "#1e90ff"; - isClicking = true; - } else { - fill = "#eee"; - textFill = "#000"; - } - } else { - this.pointerWasDown = null; - } - - ctx.fillStyle = fill; - ctx.beginPath(); - ctx.roundRect(x, y, sz, sz, [4]); - ctx.fill(); - ctx.fillStyle = textFill; - ctx.font = "12px Arial"; - ctx.textAlign = "center"; - ctx.fillText(text, x + 15, y + 20); - - return isClicking; - }; - - if (numImages > 1) { - if (drawButton(dw - 40, dh + top - 40, 30, `${this.imageIndex + 1}/${numImages}`)) { - let i = this.imageIndex + 1 >= numImages ? 0 : this.imageIndex + 1; - if (!this.pointerDown || !this.pointerDown.index === i) { - this.pointerDown = { index: i, pos: [...mouse] }; - } - } - - if (drawButton(dw - 40, top + 10, 30, `x`)) { - if (!this.pointerDown || !this.pointerDown.index === null) { - this.pointerDown = { index: null, pos: [...mouse] }; - } - } - } - } - } - } - }; - } - - /** - * Adds a handler allowing drag+drop of files onto the window to load workflows - */ - #addDropHandler() { - // Get prompt from dropped PNG or json - document.addEventListener("drop", async (event) => { - event.preventDefault(); - event.stopPropagation(); - - const n = this.dragOverNode; - this.dragOverNode = null; - // Node handles file drop, we dont use the built in onDropFile handler as its buggy - // If you drag multiple files it will call it multiple times with the same file - if (n && n.onDragDrop && (await n.onDragDrop(event))) { - return; - } - // Dragging from Chrome->Firefox there is a file but its a bmp, so ignore that - if (event.dataTransfer.files.length && event.dataTransfer.files[0].type !== "image/bmp") { - await this.handleFile(event.dataTransfer.files[0]); - } else { - // Try loading the first URI in the transfer list - 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()); - } - } - } - }); - - // Always clear over node on drag leave - this.canvasEl.addEventListener("dragleave", async () => { - if (this.dragOverNode) { - this.dragOverNode = null; - this.graph.setDirtyCanvas(false, true); - } - }); - - // Add handler for dropping onto a specific node - this.canvasEl.addEventListener( - "dragover", - (e) => { - this.canvas.adjustMouseEvent(e); - const node = this.graph.getNodeOnPos(e.canvasX, e.canvasY); - if (node) { - if (node.onDragOver && node.onDragOver(e)) { - this.dragOverNode = node; - - // dragover event is fired very frequently, run this on an animation frame - requestAnimationFrame(() => { - this.graph.setDirtyCanvas(false, true); - }); - return; - } - } - this.dragOverNode = null; - }, - false - ); - } - - /** - * Adds a handler on paste that extracts and loads images or workflows from pasted JSON data - */ - #addPasteHandler() { - document.addEventListener("paste", async (e) => { - // ctrl+shift+v is used to paste nodes with connections - // this is handled by litegraph - if(this.shiftDown) return; - - let data = (e.clipboardData || window.clipboardData); - const items = data.items; - - // Look for image paste data - for (const item of items) { - if (item.type.startsWith('image/')) { - var imageNode = null; - - // If an image node is selected, paste into it - if (this.canvas.current_node && - this.canvas.current_node.is_selected && - ComfyApp.isImageNode(this.canvas.current_node)) { - imageNode = this.canvas.current_node; - } - - // No image node selected: add a new one - if (!imageNode) { - const newNode = LiteGraph.createNode("LoadImage"); - newNode.pos = [...this.canvas.graph_mouse]; - imageNode = this.graph.add(newNode); - this.graph.change(); - } - const blob = item.getAsFile(); - imageNode.pasteFile(blob); - return; - } - } - - // No image found. Look for node data - data = data.getData("text/plain"); - let workflow; - try { - data = data.slice(data.indexOf("{")); - workflow = JSON.parse(data); - } catch (err) { - try { - data = data.slice(data.indexOf("workflow\n")); - data = data.slice(data.indexOf("{")); - workflow = JSON.parse(data); - } catch (error) {} - } - - if (workflow && workflow.version && workflow.nodes && workflow.extra) { - await this.loadGraphData(workflow); - } - else { - if (e.target.type === "text" || e.target.type === "textarea") { - return; - } - - // Litegraph default paste - this.canvas.pasteFromClipboard(); - } - - - }); - } - - - /** - * Adds a handler on copy that serializes selected nodes to JSON - */ - #addCopyHandler() { - document.addEventListener("copy", (e) => { - if (e.target.type === "text" || e.target.type === "textarea") { - // Default system copy - return; - } - - // copy nodes and clear clipboard - if (e.target.className === "litegraph" && this.canvas.selected_nodes) { - this.canvas.copyToClipboard(); - e.clipboardData.setData('text', ' '); //clearData doesn't remove images from clipboard - e.preventDefault(); - e.stopImmediatePropagation(); - return false; - } - }); - } - - - /** - * Handle mouse - * - * Move group by header - */ - #addProcessMouseHandler() { - const self = this; - - const origProcessMouseDown = LGraphCanvas.prototype.processMouseDown; - LGraphCanvas.prototype.processMouseDown = function(e) { - // prepare for ctrl+shift drag: zoom start - if(e.ctrlKey && e.shiftKey && e.buttons) { - self.zoom_drag_start = [e.x, e.y, this.ds.scale]; - return; - } - - const res = origProcessMouseDown.apply(this, arguments); - - this.selected_group_moving = false; - - if (this.selected_group && !this.selected_group_resizing) { - var font_size = - this.selected_group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; - var height = font_size * 1.4; - - // Move group by header - if (LiteGraph.isInsideRectangle(e.canvasX, e.canvasY, this.selected_group.pos[0], this.selected_group.pos[1], this.selected_group.size[0], height)) { - this.selected_group_moving = true; - } - } - - return res; - } - - const origProcessMouseMove = LGraphCanvas.prototype.processMouseMove; - LGraphCanvas.prototype.processMouseMove = function(e) { - // handle ctrl+shift drag - if(e.ctrlKey && e.shiftKey && self.zoom_drag_start) { - // stop canvas zoom action - if(!e.buttons) { - self.zoom_drag_start = null; - return; - } - - // calculate delta - let deltaY = e.y - self.zoom_drag_start[1]; - let startScale = self.zoom_drag_start[2]; - - let scale = startScale - deltaY/100; - - this.ds.changeScale(scale, [this.ds.element.width/2, this.ds.element.height/2]); - this.graph.change(); - - return; - } - - const orig_selected_group = this.selected_group; - - if (this.selected_group && !this.selected_group_resizing && !this.selected_group_moving) { - this.selected_group = null; - } - - const res = origProcessMouseMove.apply(this, arguments); - - if (orig_selected_group && !this.selected_group_resizing && !this.selected_group_moving) { - this.selected_group = orig_selected_group; - } - - return res; - }; - } - - /** - * Handle keypress - * - * Ctrl + M mute/unmute selected nodes - */ - #addProcessKeyHandler() { - const self = this; - const origProcessKey = LGraphCanvas.prototype.processKey; - LGraphCanvas.prototype.processKey = function(e) { - if (!this.graph) { - return; - } - - var block_default = false; - - if (e.target.localName == "input") { - return; - } - - if (e.type == "keydown" && !e.repeat) { - - // Ctrl + M mute/unmute - if (e.key === 'm' && (e.metaKey || e.ctrlKey)) { - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].mode === 2) { // never - this.selected_nodes[i].mode = 0; // always - } else { - this.selected_nodes[i].mode = 2; // never - } - } - } - block_default = true; - } - - // Ctrl + B bypass - if (e.key === 'b' && (e.metaKey || e.ctrlKey)) { - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].mode === 4) { // never - this.selected_nodes[i].mode = 0; // always - } else { - this.selected_nodes[i].mode = 4; // never - } - } - } - block_default = true; - } - - // Alt + C collapse/uncollapse - if (e.key === 'c' && e.altKey) { - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - this.selected_nodes[i].collapse() - } - } - block_default = true; - } - - // Ctrl+C Copy - if ((e.key === 'c') && (e.metaKey || e.ctrlKey)) { - // Trigger onCopy - return true; - } - - // Ctrl+V Paste - if ((e.key === 'v' || e.key == 'V') && (e.metaKey || e.ctrlKey) && !e.shiftKey) { - // Trigger onPaste - return true; - } - - if((e.key === '+') && e.altKey) { - block_default = true; - let scale = this.ds.scale * 1.1; - this.ds.changeScale(scale, [this.ds.element.width/2, this.ds.element.height/2]); - this.graph.change(); - } - - if((e.key === '-') && e.altKey) { - block_default = true; - let scale = this.ds.scale * 1 / 1.1; - this.ds.changeScale(scale, [this.ds.element.width/2, this.ds.element.height/2]); - this.graph.change(); - } - } - - this.graph.change(); - - if (block_default) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; - } - - // Fall through to Litegraph defaults - return origProcessKey.apply(this, arguments); - }; - } - - /** - * Draws group header bar - */ - #addDrawGroupsHandler() { - const self = this; - - const origDrawGroups = LGraphCanvas.prototype.drawGroups; - LGraphCanvas.prototype.drawGroups = function(canvas, ctx) { - if (!this.graph) { - return; - } - - var groups = this.graph._groups; - - ctx.save(); - ctx.globalAlpha = 0.7 * this.editor_alpha; - - for (var i = 0; i < groups.length; ++i) { - var group = groups[i]; - - if (!LiteGraph.overlapBounding(this.visible_area, group._bounding)) { - continue; - } //out of the visible area - - ctx.fillStyle = group.color || "#335"; - ctx.strokeStyle = group.color || "#335"; - var pos = group._pos; - var size = group._size; - ctx.globalAlpha = 0.25 * this.editor_alpha; - ctx.beginPath(); - var font_size = - group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; - ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], font_size * 1.4); - ctx.fill(); - ctx.globalAlpha = this.editor_alpha; - } - - ctx.restore(); - - const res = origDrawGroups.apply(this, arguments); - return res; - } - } - - /** - * Draws node highlights (executing, drag drop) and progress bar - */ - #addDrawNodeHandler() { - const origDrawNodeShape = LGraphCanvas.prototype.drawNodeShape; - const self = this; - - LGraphCanvas.prototype.drawNodeShape = function (node, ctx, size, fgcolor, bgcolor, selected, mouse_over) { - const res = origDrawNodeShape.apply(this, arguments); - - const nodeErrors = self.lastNodeErrors?.[node.id]; - - let color = null; - let lineWidth = 1; - if (node.id === +self.runningNodeId) { - color = "#0f0"; - } else if (self.dragOverNode && node.id === self.dragOverNode.id) { - color = "dodgerblue"; - } - else if (nodeErrors?.errors) { - color = "red"; - lineWidth = 2; - } - else if (self.lastExecutionError && +self.lastExecutionError.node_id === node.id) { - color = "#f0f"; - lineWidth = 2; - } - - if (color) { - const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; - ctx.lineWidth = lineWidth; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) - ctx.rect(-6, -6 - LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT); - else if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed)) - ctx.roundRect( - -6, - -6 - LiteGraph.NODE_TITLE_HEIGHT, - 12 + size[0] + 1, - 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, - this.round_radius * 2 - ); - else if (shape == LiteGraph.CARD_SHAPE) - ctx.roundRect( - -6, - -6 - LiteGraph.NODE_TITLE_HEIGHT, - 12 + size[0] + 1, - 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT, - [this.round_radius * 2, this.round_radius * 2, 2, 2] - ); - else if (shape == LiteGraph.CIRCLE_SHAPE) - ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2); - ctx.strokeStyle = color; - ctx.stroke(); - ctx.strokeStyle = fgcolor; - ctx.globalAlpha = 1; - } - - if (self.progress && node.id === +self.runningNodeId) { - ctx.fillStyle = "green"; - ctx.fillRect(0, 0, size[0] * (self.progress.value / self.progress.max), 6); - ctx.fillStyle = bgcolor; - } - - // Highlight inputs that failed validation - if (nodeErrors) { - ctx.lineWidth = 2; - ctx.strokeStyle = "red"; - for (const error of nodeErrors.errors) { - if (error.extra_info && error.extra_info.input_name) { - const inputIndex = node.findInputSlot(error.extra_info.input_name) - if (inputIndex !== -1) { - let pos = node.getConnectionPos(true, inputIndex); - ctx.beginPath(); - ctx.arc(pos[0] - node.pos[0], pos[1] - node.pos[1], 12, 0, 2 * Math.PI, false) - ctx.stroke(); - } - } - } - } - - return res; - }; - - const origDrawNode = LGraphCanvas.prototype.drawNode; - LGraphCanvas.prototype.drawNode = function (node, ctx) { - var editor_alpha = this.editor_alpha; - var old_color = node.bgcolor; - - if (node.mode === 2) { // never - this.editor_alpha = 0.4; - } - - if (node.mode === 4) { // never - node.bgcolor = "#FF00FF"; - this.editor_alpha = 0.2; - } - - const res = origDrawNode.apply(this, arguments); - - this.editor_alpha = editor_alpha; - node.bgcolor = old_color; - - return res; - }; - } - - /** - * Handles updates from the API socket - */ - #addApiUpdateHandlers() { - api.addEventListener("status", ({ detail }) => { - this.ui.setStatus(detail); - }); - - api.addEventListener("reconnecting", () => { - this.ui.dialog.show("Reconnecting..."); - }); - - api.addEventListener("reconnected", () => { - this.ui.dialog.close(); - }); - - api.addEventListener("progress", ({ detail }) => { - if (this.workflowManager.activePrompt?.workflow - && this.workflowManager.activePrompt.workflow !== this.workflowManager.activeWorkflow) return; - this.progress = detail; - this.graph.setDirtyCanvas(true, false); - }); - - api.addEventListener("executing", ({ detail }) => { - if (this.workflowManager.activePrompt ?.workflow - && this.workflowManager.activePrompt.workflow !== this.workflowManager.activeWorkflow) return; - this.progress = null; - this.runningNodeId = detail; - this.graph.setDirtyCanvas(true, false); - delete this.nodePreviewImages[this.runningNodeId] - }); - - api.addEventListener("executed", ({ detail }) => { - if (this.workflowManager.activePrompt ?.workflow - && this.workflowManager.activePrompt.workflow !== this.workflowManager.activeWorkflow) return; - const output = this.nodeOutputs[detail.display_node]; - if (detail.merge && output) { - for (const k in detail.output ?? {}) { - const v = output[k]; - if (v instanceof Array) { - output[k] = v.concat(detail.output[k]); - } else { - output[k] = detail.output[k]; - } - } - } else { - this.nodeOutputs[detail.display_node] = detail.output; - } - const node = this.graph.getNodeById(detail.display_node); - if (node) { - if (node.onExecuted) - node.onExecuted(detail.output); - } - }); - - api.addEventListener("execution_start", ({ detail }) => { - this.runningNodeId = null; - this.lastExecutionError = null - this.graph._nodes.forEach((node) => { - if (node.onExecutionStart) - node.onExecutionStart() - }) - }); - - api.addEventListener("execution_error", ({ detail }) => { - this.lastExecutionError = detail; - const formattedError = this.#formatExecutionError(detail); - this.ui.dialog.show(formattedError); - this.canvas.draw(true, true); - }); - - api.addEventListener("b_preview", ({ detail }) => { - const id = this.runningNodeId - if (id == null) - return; - - const blob = detail - const blobUrl = URL.createObjectURL(blob) - this.nodePreviewImages[id] = [blobUrl] - }); - - api.init(); - } - - #addKeyboardHandler() { - window.addEventListener("keydown", (e) => { - this.shiftDown = e.shiftKey; - }); - window.addEventListener("keyup", (e) => { - this.shiftDown = e.shiftKey; - }); - } - - #addConfigureHandler() { - const app = this; - const configure = LGraph.prototype.configure; - // Flag that the graph is configuring to prevent nodes from running checks while its still loading - LGraph.prototype.configure = function () { - app.configuringGraph = true; - try { - return configure.apply(this, arguments); - } finally { - app.configuringGraph = false; - } - }; - } - - #addAfterConfigureHandler() { - const app = this; - const onConfigure = app.graph.onConfigure; - app.graph.onConfigure = function () { - // Fire callbacks before the onConfigure, this is used by widget inputs to setup the config - for (const node of app.graph._nodes) { - node.onGraphConfigured?.(); - } - - const r = onConfigure?.apply(this, arguments); - - // Fire after onConfigure, used by primitves to generate widget using input nodes config - for (const node of app.graph._nodes) { - node.onAfterGraphConfigured?.(); - } - - return r; - }; - } - - /** - * Loads all extensions from the API into the window in parallel - */ - async #loadExtensions() { - const extensions = await api.getExtensions(); - this.logging.addEntry("Comfy.App", "debug", { Extensions: extensions }); - - const extensionPromises = extensions.map(async ext => { - try { - await import(api.apiURL(ext)); - } catch (error) { - console.error("Error loading extension", ext, error); - } - }); - - await Promise.all(extensionPromises); - try { - this.menu.workflows.registerExtension(this); - } catch (error) { - console.error(error); - } - } - - async #migrateSettings() { - this.isNewUserSession = true; - // Store all current settings - const settings = Object.keys(this.ui.settings).reduce((p, n) => { - const v = localStorage[`Comfy.Settings.${n}`]; - if (v) { - try { - p[n] = JSON.parse(v); - } catch (error) {} - } - return p; - }, {}); - - await api.storeSettings(settings); - } - - async #setUser() { - const userConfig = await api.getUserConfig(); - this.storageLocation = userConfig.storage; - if (typeof userConfig.migrated == "boolean") { - // Single user mode migrated true/false for if the default user is created - if (!userConfig.migrated && this.storageLocation === "server") { - // Default user not created yet - await this.#migrateSettings(); - } - return; - } - - this.multiUserServer = true; - let user = localStorage["Comfy.userId"]; - const users = userConfig.users ?? {}; - if (!user || !users[user]) { - // This will rarely be hit so move the loading to on demand - const { UserSelectionScreen } = await import("./ui/userSelection.js"); - - this.ui.menuContainer.style.display = "none"; - const { userId, username, created } = await new UserSelectionScreen().show(users, user); - this.ui.menuContainer.style.display = ""; - - user = userId; - localStorage["Comfy.userName"] = username; - localStorage["Comfy.userId"] = user; - - if (created) { - api.user = user; - await this.#migrateSettings(); - } - } - - api.user = user; - - this.ui.settings.addSetting({ - id: "Comfy.SwitchUser", - name: "Switch User", - type: (name) => { - let currentUser = localStorage["Comfy.userName"]; - if (currentUser) { - currentUser = ` (${currentUser})`; - } - return $el("tr", [ - $el("td", [ - $el("label", { - textContent: name, - }), - ]), - $el("td", [ - $el("button", { - textContent: name + (currentUser ?? ""), - onclick: () => { - delete localStorage["Comfy.userId"]; - delete localStorage["Comfy.userName"]; - window.location.reload(); - }, - }), - ]), - ]); - }, - }); - } - - /** - * Set up the app on the page - */ - async setup() { - await this.#setUser(); - - // Create and mount the LiteGraph in the DOM - const mainCanvas = document.createElement("canvas") - mainCanvas.style.touchAction = "none" - const canvasEl = (this.canvasEl = Object.assign(mainCanvas, { id: "graph-canvas" })); - canvasEl.tabIndex = "1"; - document.body.append(canvasEl); - this.resizeCanvas(); - - await Promise.all([this.workflowManager.loadWorkflows(), this.ui.settings.load()]); - await this.#loadExtensions(); - - addDomClippingSetting(); - this.#addProcessMouseHandler(); - this.#addProcessKeyHandler(); - this.#addConfigureHandler(); - this.#addApiUpdateHandlers(); - this.#addRestoreWorkflowView(); - - this.graph = new LGraph(); - - this.#addAfterConfigureHandler(); - - this.canvas = new LGraphCanvas(canvasEl, this.graph); - this.ctx = canvasEl.getContext("2d"); - - LiteGraph.release_link_on_empty_shows_menu = true; - LiteGraph.alt_drag_do_clone_nodes = true; - - this.graph.start(); - - // Ensure the canvas fills the window - this.resizeCanvas(); - window.addEventListener("resize", () => this.resizeCanvas()); - const ro = new ResizeObserver(() => this.resizeCanvas()); - ro.observe(this.bodyTop); - ro.observe(this.bodyLeft); - ro.observe(this.bodyRight); - ro.observe(this.bodyBottom); - - await this.#invokeExtensionsAsync("init"); - await this.registerNodes(); - initWidgets(this); - - // Load previous workflow - let restored = false; - try { - const loadWorkflow = async (json) => { - if (json) { - const workflow = JSON.parse(json); - const workflowName = getStorageValue("Comfy.PreviousWorkflow"); - await this.loadGraphData(workflow, true, true, workflowName); - return true; - } - }; - const clientId = api.initialClientId ?? api.clientId; - restored = - (clientId && (await loadWorkflow(sessionStorage.getItem(`workflow:${clientId}`)))) || - (await loadWorkflow(localStorage.getItem("workflow"))); - } catch (err) { - console.error("Error loading previous workflow", err); - } - - // We failed to restore a workflow so load the default - if (!restored) { - await this.loadGraphData(); - } - - // Save current workflow automatically - setInterval(() => { - const workflow = JSON.stringify(this.graph.serialize()); - localStorage.setItem("workflow", workflow); - if (api.clientId) { - sessionStorage.setItem(`workflow:${api.clientId}`, workflow); - } - }, 1000); - - this.#addDrawNodeHandler(); - this.#addDrawGroupsHandler(); - this.#addDropHandler(); - this.#addCopyHandler(); - this.#addPasteHandler(); - this.#addKeyboardHandler(); - - await this.#invokeExtensionsAsync("setup"); - } - - resizeCanvas() { - // Limit minimal scale to 1, see https://github.com/comfyanonymous/ComfyUI/pull/845 - const scale = Math.max(window.devicePixelRatio, 1); - - // Clear fixed width and height while calculating rect so it uses 100% instead - this.canvasEl.height = this.canvasEl.width = ""; - const { width, height } = this.canvasEl.getBoundingClientRect(); - this.canvasEl.width = Math.round(width * scale); - this.canvasEl.height = Math.round(height * scale); - this.canvasEl.getContext("2d").scale(scale, scale); - this.canvas?.draw(true, true); - } - - /** - * Registers nodes with the graph - */ - async registerNodes() { - const app = this; - // Load node definitions from the backend - const defs = await api.getNodeDefs(); - await this.registerNodesFromDefs(defs); - await this.#invokeExtensionsAsync("registerCustomNodes"); - } - - getWidgetType(inputData, inputName) { - const type = inputData[0]; - - if (Array.isArray(type)) { - return "COMBO"; - } else if (`${type}:${inputName}` in this.widgets) { - return `${type}:${inputName}`; - } else if (type in this.widgets) { - return type; - } else { - return null; - } - } - - async registerNodeDef(nodeId, nodeData) { - const self = this; - const node = Object.assign( - function ComfyNode() { - var inputs = nodeData["input"]["required"]; - if (nodeData["input"]["optional"] != undefined) { - inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"]); - } - const config = { minWidth: 1, minHeight: 1 }; - for (const inputName in inputs) { - const inputData = inputs[inputName]; - const type = inputData[0]; - - let widgetCreated = true; - const widgetType = self.getWidgetType(inputData, inputName); - if(widgetType) { - if(widgetType === "COMBO") { - Object.assign(config, self.widgets.COMBO(this, inputName, inputData, app) || {}); - } else { - Object.assign(config, self.widgets[widgetType](this, inputName, inputData, app) || {}); - } - } else { - // Node connection inputs - this.addInput(inputName, type); - widgetCreated = false; - } - - if(widgetCreated && inputData[1]?.forceInput && config?.widget) { - if (!config.widget.options) config.widget.options = {}; - config.widget.options.forceInput = inputData[1].forceInput; - } - if(widgetCreated && inputData[1]?.defaultInput && config?.widget) { - if (!config.widget.options) config.widget.options = {}; - config.widget.options.defaultInput = inputData[1].defaultInput; - } - } - - for (const o in nodeData["output"]) { - let output = nodeData["output"][o]; - if(output instanceof Array) output = "COMBO"; - const outputTooltip = nodeData["output_tooltips"]?.[o]; - const outputName = nodeData["output_name"][o] || output; - const outputShape = nodeData["output_is_list"][o] ? LiteGraph.GRID_SHAPE : LiteGraph.CIRCLE_SHAPE ; - this.addOutput(outputName, output, { shape: outputShape, tooltip: outputTooltip }); - } - - const s = this.computeSize(); - s[0] = Math.max(config.minWidth, s[0] * 1.5); - s[1] = Math.max(config.minHeight, s[1]); - this.size = s; - this.serialize_widgets = true; - - app.#invokeExtensionsAsync("nodeCreated", this); - }, - { - title: nodeData.display_name || nodeData.name, - comfyClass: nodeData.name, - nodeData - } - ); - node.prototype.comfyClass = nodeData.name; - - this.#addNodeContextMenuHandler(node); - this.#addDrawBackgroundHandler(node, app); - this.#addNodeKeyHandler(node); - - await this.#invokeExtensionsAsync("beforeRegisterNodeDef", node, nodeData); - LiteGraph.registerNodeType(nodeId, node); - node.category = nodeData.category; - } - - async registerNodesFromDefs(defs) { - await this.#invokeExtensionsAsync("addCustomNodeDefs", defs); - - // Generate list of known widgets - this.widgets = Object.assign( - {}, - ComfyWidgets, - ...(await this.#invokeExtensionsAsync("getCustomWidgets")).filter(Boolean) - ); - - // Register a node for each definition - for (const nodeId in defs) { - this.registerNodeDef(nodeId, defs[nodeId]); - } - } - - loadTemplateData(templateData) { - if (!templateData?.templates) { - return; - } - - const old = localStorage.getItem("litegrapheditor_clipboard"); - - var maxY, nodeBottom, node; - - for (const template of templateData.templates) { - if (!template?.data) { - continue; - } - - localStorage.setItem("litegrapheditor_clipboard", template.data); - app.canvas.pasteFromClipboard(); - - // Move mouse position down to paste the next template below - - maxY = false; - - for (const i in app.canvas.selected_nodes) { - node = app.canvas.selected_nodes[i]; - - nodeBottom = node.pos[1] + node.size[1]; - - if (maxY === false || nodeBottom > maxY) { - maxY = nodeBottom; - } - } - - app.canvas.graph_mouse[1] = maxY + 50; - } - - localStorage.setItem("litegrapheditor_clipboard", old); - } - - showMissingNodesError(missingNodeTypes, hasAddedNodes = true) { - let seenTypes = new Set(); - - this.ui.dialog.show( - $el("div.comfy-missing-nodes", [ - $el("span", { textContent: "When loading the graph, the following node types were not found: " }), - $el( - "ul", - Array.from(new Set(missingNodeTypes)).map((t) => { - let children = []; - if (typeof t === "object") { - if(seenTypes.has(t.type)) return null; - seenTypes.add(t.type); - children.push($el("span", { textContent: t.type })); - if (t.hint) { - children.push($el("span", { textContent: t.hint })); - } - if (t.action) { - children.push($el("button", { onclick: t.action.callback, textContent: t.action.text })); - } - } else { - if(seenTypes.has(t)) return null; - seenTypes.add(t); - children.push($el("span", { textContent: t })); - } - return $el("li", children); - }).filter(Boolean) - ), - ...(hasAddedNodes - ? [$el("span", { textContent: "Nodes that have failed to load will show as red on the graph." })] - : []), - ]) - ); - this.logging.addEntry("Comfy.App", "warn", { - MissingNodes: missingNodeTypes, - }); - } - - async changeWorkflow(callback, workflow = null) { - try { - this.workflowManager.activeWorkflow?.changeTracker?.store() - } catch (error) { - console.error(error); - } - await callback(); - try { - this.workflowManager.setWorkflow(workflow); - this.workflowManager.activeWorkflow?.track() - } catch (error) { - console.error(error); - } - } - - /** - * Populates the graph with the specified workflow data - * @param {*} graphData A serialized graph object - * @param { boolean } clean If the graph state, e.g. images, should be cleared - * @param { boolean } restore_view If the graph position should be restored - * @param { import("./workflows.js").ComfyWorkflowInstance | null } workflow The workflow - */ - async loadGraphData(graphData, clean = true, restore_view = true, workflow = null) { - if (clean !== false) { - this.clean(); - } - - let reset_invalid_values = false; - if (!graphData) { - graphData = defaultGraph; - reset_invalid_values = true; - } - - if (typeof structuredClone === "undefined") - { - graphData = JSON.parse(JSON.stringify(graphData)); - }else - { - graphData = structuredClone(graphData); - } - - try { - this.workflowManager.setWorkflow(workflow); - } catch (error) { - console.error(error); - } - - const missingNodeTypes = []; - await this.#invokeExtensionsAsync("beforeConfigureGraph", graphData, missingNodeTypes); - for (let n of graphData.nodes) { - // Patch T2IAdapterLoader to ControlNetLoader since they are the same node now - if (n.type == "T2IAdapterLoader") n.type = "ControlNetLoader"; - if (n.type == "ConditioningAverage ") n.type = "ConditioningAverage"; //typo fix - if (n.type == "SDV_img2vid_Conditioning") n.type = "SVD_img2vid_Conditioning"; //typo fix - - // Find missing node types - if (!(n.type in LiteGraph.registered_node_types)) { - missingNodeTypes.push(n.type); - n.type = sanitizeNodeName(n.type); - } - } - - try { - this.graph.configure(graphData); - if (restore_view && this.enableWorkflowViewRestore.value && graphData.extra?.ds) { - this.canvas.ds.offset = graphData.extra.ds.offset; - this.canvas.ds.scale = graphData.extra.ds.scale; - } - - try { - this.workflowManager.activeWorkflow?.track() - } catch (error) { - } - } catch (error) { - let errorHint = []; - // Try extracting filename to see if it was caused by an extension script - const filename = error.fileName || (error.stack || "").match(/(\/extensions\/.*\.js)/)?.[1]; - const pos = (filename || "").indexOf("/extensions/"); - if (pos > -1) { - errorHint.push( - $el("span", { textContent: "This may be due to the following script:" }), - $el("br"), - $el("span", { - style: { - fontWeight: "bold", - }, - textContent: filename.substring(pos), - }) - ); - } - - // Show dialog to let the user know something went wrong loading the data - this.ui.dialog.show( - $el("div", [ - $el("p", { textContent: "Loading aborted due to error reloading workflow data" }), - $el("pre", { - style: { padding: "5px", backgroundColor: "rgba(255,0,0,0.2)" }, - textContent: error.toString(), - }), - $el("pre", { - style: { - padding: "5px", - color: "#ccc", - fontSize: "10px", - maxHeight: "50vh", - overflow: "auto", - backgroundColor: "rgba(0,0,0,0.2)", - }, - textContent: error.stack || "No stacktrace available", - }), - ...errorHint, - ]).outerHTML - ); - - return; - } - - for (const node of this.graph._nodes) { - const size = node.computeSize(); - size[0] = Math.max(node.size[0], size[0]); - size[1] = Math.max(node.size[1], size[1]); - node.size = size; - - if (node.widgets) { - // If you break something in the backend and want to patch workflows in the frontend - // This is the place to do this - for (let widget of node.widgets) { - if (node.type == "KSampler" || node.type == "KSamplerAdvanced") { - if (widget.name == "sampler_name") { - if (widget.value.startsWith("sample_")) { - widget.value = widget.value.slice(7); - } - if (widget.value === "euler_pp" || widget.value === "euler_ancestral_pp") { - widget.value = widget.value.slice(0, -3); - for (let w of node.widgets) { - if (w.name == "cfg") { - w.value *= 2.0; - } - } - } - } - } - if (node.type == "KSampler" || node.type == "KSamplerAdvanced" || node.type == "PrimitiveNode") { - if (widget.name == "control_after_generate") { - if (widget.value === true) { - widget.value = "randomize"; - } else if (widget.value === false) { - widget.value = "fixed"; - } - } - } - if (reset_invalid_values) { - if (widget.type == "combo") { - if (!widget.options.values.includes(widget.value) && widget.options.values.length > 0) { - widget.value = widget.options.values[0]; - } - } - } - } - } - - this.#invokeExtensions("loadedGraphNode", node); - } - - if (missingNodeTypes.length) { - this.showMissingNodesError(missingNodeTypes); - } - await this.#invokeExtensionsAsync("afterConfigureGraph", missingNodeTypes); - requestAnimationFrame(() => { - this.graph.setDirtyCanvas(true, true); - }); - } - - /** - * Converts the current graph workflow for sending to the API - * @returns The workflow and node links - */ - async graphToPrompt(graph = this.graph, clean = true) { - for (const outerNode of graph.computeExecutionOrder(false)) { - if (outerNode.widgets) { - for (const widget of outerNode.widgets) { - // Allow widgets to run callbacks before a prompt has been queued - // e.g. random seed before every gen - widget.beforeQueued?.(); - } - } - - const innerNodes = outerNode.getInnerNodes ? outerNode.getInnerNodes() : [outerNode]; - for (const node of innerNodes) { - if (node.isVirtualNode) { - // Don't serialize frontend only nodes but let them make changes - if (node.applyToGraph) { - node.applyToGraph(); - } - } - } - } - - const workflow = graph.serialize(); - const output = {}; - // Process nodes in order of execution - for (const outerNode of graph.computeExecutionOrder(false)) { - const skipNode = outerNode.mode === 2 || outerNode.mode === 4; - const innerNodes = (!skipNode && outerNode.getInnerNodes) ? outerNode.getInnerNodes() : [outerNode]; - for (const node of innerNodes) { - if (node.isVirtualNode) { - continue; - } - - if (node.mode === 2 || node.mode === 4) { - // Don't serialize muted nodes - continue; - } - - const inputs = {}; - const widgets = node.widgets; - - // Store all widget values - if (widgets) { - for (const i in widgets) { - const widget = widgets[i]; - if (!widget.options || widget.options.serialize !== false) { - inputs[widget.name] = widget.serializeValue ? await widget.serializeValue(node, i) : widget.value; - } - } - } - - // Store all node links - for (let i in node.inputs) { - let parent = node.getInputNode(i); - if (parent) { - let link = node.getInputLink(i); - while (parent.mode === 4 || parent.isVirtualNode) { - let found = false; - if (parent.isVirtualNode) { - link = parent.getInputLink(link.origin_slot); - if (link) { - parent = parent.getInputNode(link.target_slot); - if (parent) { - found = true; - } - } - } else if (link && parent.mode === 4) { - let all_inputs = [link.origin_slot]; - if (parent.inputs) { - all_inputs = all_inputs.concat(Object.keys(parent.inputs)) - for (let parent_input in all_inputs) { - parent_input = all_inputs[parent_input]; - if (parent.inputs[parent_input]?.type === node.inputs[i].type) { - link = parent.getInputLink(parent_input); - if (link) { - parent = parent.getInputNode(parent_input); - } - found = true; - break; - } - } - } - } - - if (!found) { - break; - } - } - - if (link) { - if (parent?.updateLink) { - link = parent.updateLink(link); - } - if (link) { - inputs[node.inputs[i].name] = [String(link.origin_id), parseInt(link.origin_slot)]; - } - } - } - } - - let node_data = { - inputs, - class_type: node.comfyClass, - }; - - if (this.ui.settings.getSettingValue("Comfy.DevMode")) { - // Ignored by the backend. - node_data["_meta"] = { - title: node.title, - } - } - - output[String(node.id)] = node_data; - } - } - - // Remove inputs connected to removed nodes - if(clean) { - for (const o in output) { - for (const i in output[o].inputs) { - if (Array.isArray(output[o].inputs[i]) - && output[o].inputs[i].length === 2 - && !output[output[o].inputs[i][0]]) { - delete output[o].inputs[i]; - } - } - } - } - - return { workflow, output }; - } - - #formatPromptError(error) { - if (error == null) { - return "(unknown error)" - } - else if (typeof error === "string") { - return error; - } - else if (error.stack && error.message) { - return error.toString() - } - else if (error.response) { - let message = error.response.error.message; - if (error.response.error.details) - message += ": " + error.response.error.details; - for (const [nodeID, nodeError] of Object.entries(error.response.node_errors)) { - message += "\n" + nodeError.class_type + ":" - for (const errorReason of nodeError.errors) { - message += "\n - " + errorReason.message + ": " + errorReason.details - } - } - return message - } - return "(unknown error)" - } - - #formatExecutionError(error) { - if (error == null) { - return "(unknown error)" - } - - const traceback = error.traceback.join("") - const nodeId = error.node_id - const nodeType = error.node_type - - return `Error occurred when executing ${nodeType}:\n\n${error.exception_message}\n\n${traceback}` - } - - async queuePrompt(number, batchCount = 1) { - this.#queueItems.push({ number, batchCount }); - - // Only have one action process the items so each one gets a unique seed correctly - if (this.#processingQueue) { - return; - } - - this.#processingQueue = true; - this.lastNodeErrors = null; - - try { - while (this.#queueItems.length) { - ({ number, batchCount } = this.#queueItems.pop()); - - for (let i = 0; i < batchCount; i++) { - const p = await this.graphToPrompt(); - - try { - const res = await api.queuePrompt(number, p); - this.lastNodeErrors = res.node_errors; - if (this.lastNodeErrors.length > 0) { - this.canvas.draw(true, true); - } else { - try { - this.workflowManager.storePrompt({ - id: res.prompt_id, - nodes: Object.keys(p.output) - }); - } catch (error) { - } - } - } catch (error) { - const formattedError = this.#formatPromptError(error) - this.ui.dialog.show(formattedError); - if (error.response) { - this.lastNodeErrors = error.response.node_errors; - this.canvas.draw(true, true); - } - break; - } - - for (const n of p.workflow.nodes) { - const node = graph.getNodeById(n.id); - if (node.widgets) { - for (const widget of node.widgets) { - // Allow widgets to run callbacks after a prompt has been queued - // e.g. random seed after every gen - if (widget.afterQueued) { - widget.afterQueued(); - } - } - } - } - - this.canvas.draw(true, true); - await this.ui.queue.update(); - } - } - } finally { - this.#processingQueue = false; - } - api.dispatchEvent(new CustomEvent("promptQueued", { detail: { number, batchCount } })); - return !this.lastNodeErrors; - } - - showErrorOnFileLoad(file) { - this.ui.dialog.show( - $el("div", [ - $el("p", {textContent: `Unable to find workflow in ${file.name}`}) - ]).outerHTML - ); - } - - /** - * Loads workflow data from the specified file - * @param {File} file - */ - async handleFile(file) { - const removeExt = f => { - if(!f) return f; - const p = f.lastIndexOf("."); - if(p === -1) return f; - return f.substring(0, p); - }; - - const fileName = removeExt(file.name); - if (file.type === "image/png") { - const pngInfo = await getPngMetadata(file); - if (pngInfo?.workflow) { - await this.loadGraphData(JSON.parse(pngInfo.workflow), true, true, fileName); - } else if (pngInfo?.prompt) { - this.loadApiJson(JSON.parse(pngInfo.prompt), fileName); - } else if (pngInfo?.parameters) { - this.changeWorkflow(() => { - importA1111(this.graph, pngInfo.parameters); - }, fileName) - } else { - this.showErrorOnFileLoad(file); - } - } else if (file.type === "image/webp") { - const pngInfo = await getWebpMetadata(file); - // Support loading workflows from that webp custom node. - const workflow = pngInfo?.workflow || pngInfo?.Workflow; - const prompt = pngInfo?.prompt || pngInfo?.Prompt; - - if (workflow) { - this.loadGraphData(JSON.parse(workflow), true, true, fileName); - } else if (prompt) { - this.loadApiJson(JSON.parse(prompt), fileName); - } else { - this.showErrorOnFileLoad(file); - } - } else if (file.type === "audio/flac" || file.type === "audio/x-flac") { - const pngInfo = await getFlacMetadata(file); - // Support loading workflows from that webp custom node. - const workflow = pngInfo?.workflow; - const prompt = pngInfo?.prompt; - - if (workflow) { - this.loadGraphData(JSON.parse(workflow), true, true, fileName); - } else if (prompt) { - this.loadApiJson(JSON.parse(prompt), fileName); - } else { - this.showErrorOnFileLoad(file); - } - } else if (file.type === "application/json" || file.name?.endsWith(".json")) { - const reader = new FileReader(); - reader.onload = async () => { - const jsonContent = JSON.parse(reader.result); - if (jsonContent?.templates) { - this.loadTemplateData(jsonContent); - } else if(this.isApiJson(jsonContent)) { - this.loadApiJson(jsonContent, fileName); - } else { - await this.loadGraphData(jsonContent, true, true, fileName); - } - }; - reader.readAsText(file); - } else if (file.name?.endsWith(".latent") || file.name?.endsWith(".safetensors")) { - const info = await getLatentMetadata(file); - if (info.workflow) { - await this.loadGraphData(JSON.parse(info.workflow), true, true, fileName); - } else if (info.prompt) { - this.loadApiJson(JSON.parse(info.prompt)); - } else { - this.showErrorOnFileLoad(file); - } - } else { - this.showErrorOnFileLoad(file); - } - } - - isApiJson(data) { - return Object.values(data).every((v) => v.class_type); - } - - loadApiJson(apiData, fileName) { - const missingNodeTypes = Object.values(apiData).filter((n) => !LiteGraph.registered_node_types[n.class_type]); - if (missingNodeTypes.length) { - this.showMissingNodesError(missingNodeTypes.map(t => t.class_type), false); - return; - } - - const ids = Object.keys(apiData); - app.graph.clear(); - for (const id of ids) { - const data = apiData[id]; - const node = LiteGraph.createNode(data.class_type); - node.id = isNaN(+id) ? id : +id; - node.title = data._meta?.title ?? node.title - app.graph.add(node); - } - - this.changeWorkflow(() => { - for (const id of ids) { - const data = apiData[id]; - const node = app.graph.getNodeById(id); - for (const input in data.inputs ?? {}) { - const value = data.inputs[input]; - if (value instanceof Array) { - const [fromId, fromSlot] = value; - const fromNode = app.graph.getNodeById(fromId); - let toSlot = node.inputs?.findIndex((inp) => inp.name === input); - if (toSlot == null || toSlot === -1) { - try { - // Target has no matching input, most likely a converted widget - const widget = node.widgets?.find((w) => w.name === input); - if (widget && node.convertWidgetToInput?.(widget)) { - toSlot = node.inputs?.length - 1; - } - } catch (error) {} - } - if (toSlot != null || toSlot !== -1) { - fromNode.connect(fromSlot, node, toSlot); - } - } else { - const widget = node.widgets?.find((w) => w.name === input); - if (widget) { - widget.value = value; - widget.callback?.(value); - } - } - } - } - app.graph.arrange(); - }, fileName); - } - - /** - * Registers a Comfy web extension with the app - * @param {ComfyExtension} extension - */ - registerExtension(extension) { - if (!extension.name) { - throw new Error("Extensions must have a 'name' property."); - } - if (this.extensions.find((ext) => ext.name === extension.name)) { - throw new Error(`Extension named '${extension.name}' already registered.`); - } - this.extensions.push(extension); - } - - /** - * Refresh combo list on whole nodes - */ - async refreshComboInNodes() { - const defs = await api.getNodeDefs(); - - for (const nodeId in defs) { - this.registerNodeDef(nodeId, defs[nodeId]); - } - - for(let nodeNum in this.graph._nodes) { - const node = this.graph._nodes[nodeNum]; - const def = defs[node.type]; - - // Allow primitive nodes to handle refresh - node.refreshComboInNode?.(defs); - - if(!def) - continue; - - for(const widgetNum in node.widgets) { - const widget = node.widgets[widgetNum] - if(widget.type == "combo" && def["input"]["required"][widget.name] !== undefined) { - widget.options.values = def["input"]["required"][widget.name][0]; - - if(widget.name != 'image' && !widget.options.values.includes(widget.value)) { - widget.value = widget.options.values[0]; - widget.callback(widget.value); - } - } - } - } - - await this.#invokeExtensionsAsync("refreshComboInNodes", defs); - } - - resetView() { - app.canvas.ds.scale = 1; - app.canvas.ds.offset = [0, 0] - app.graph.setDirtyCanvas(true, true); - } - - /** - * Clean current state - */ - clean() { - this.nodeOutputs = {}; - this.nodePreviewImages = {} - this.lastNodeErrors = null; - this.lastExecutionError = null; - this.runningNodeId = null; - } -} - -export const app = new ComfyApp(); diff --git a/web/scripts/changeTracker.js b/web/scripts/changeTracker.js deleted file mode 100644 index d7798dbe9..000000000 --- a/web/scripts/changeTracker.js +++ /dev/null @@ -1,255 +0,0 @@ -// @ts-check - -import { api } from "./api.js"; -import { clone } from "./utils.js"; - -export class ChangeTracker { - static MAX_HISTORY = 50; - #app; - undo = []; - redo = []; - activeState = null; - isOurLoad = false; - /** @type { import("./workflows").ComfyWorkflow | null } */ - workflow; - - ds; - nodeOutputs; - - get app() { - return this.#app ?? this.workflow.manager.app; - } - - constructor(workflow) { - this.workflow = workflow; - } - - #setApp(app) { - this.#app = app; - } - - store() { - this.ds = { scale: this.app.canvas.ds.scale, offset: [...this.app.canvas.ds.offset] }; - } - - restore() { - if (this.ds) { - this.app.canvas.ds.scale = this.ds.scale; - this.app.canvas.ds.offset = this.ds.offset; - } - if (this.nodeOutputs) { - this.app.nodeOutputs = this.nodeOutputs; - } - } - - checkState() { - if (!this.app.graph) return; - - const currentState = this.app.graph.serialize(); - if (!this.activeState) { - this.activeState = clone(currentState); - return; - } - if (!ChangeTracker.graphEqual(this.activeState, currentState)) { - this.undo.push(this.activeState); - if (this.undo.length > ChangeTracker.MAX_HISTORY) { - this.undo.shift(); - } - this.activeState = clone(currentState); - this.redo.length = 0; - this.workflow.unsaved = true; - api.dispatchEvent(new CustomEvent("graphChanged", { detail: this.activeState })); - } - } - - async updateState(source, target) { - const prevState = source.pop(); - if (prevState) { - target.push(this.activeState); - this.isOurLoad = true; - await this.app.loadGraphData(prevState, false, false, this.workflow); - this.activeState = prevState; - } - } - - async undoRedo(e) { - if (e.ctrlKey || e.metaKey) { - if (e.key === "y") { - this.updateState(this.redo, this.undo); - return true; - } else if (e.key === "z") { - this.updateState(this.undo, this.redo); - return true; - } - } - } - - /** @param { import("./app.js").ComfyApp } app */ - static init(app) { - const changeTracker = () => app.workflowManager.activeWorkflow?.changeTracker ?? globalTracker; - globalTracker.#setApp(app); - - const loadGraphData = app.loadGraphData; - app.loadGraphData = async function () { - const v = await loadGraphData.apply(this, arguments); - const ct = changeTracker(); - if (ct.isOurLoad) { - ct.isOurLoad = false; - } else { - ct.checkState(); - } - return v; - }; - - let keyIgnored = false; - window.addEventListener( - "keydown", - (e) => { - const activeEl = document.activeElement; - requestAnimationFrame(async () => { - let bindInputEl; - // If we are auto queue in change mode then we do want to trigger on inputs - if (!app.ui.autoQueueEnabled || app.ui.autoQueueMode === "instant") { - if (activeEl?.tagName === "INPUT" || activeEl?.["type"] === "textarea") { - // Ignore events on inputs, they have their native history - return; - } - bindInputEl = activeEl; - } - - keyIgnored = e.key === "Control" || e.key === "Shift" || e.key === "Alt" || e.key === "Meta"; - if (keyIgnored) return; - - // Check if this is a ctrl+z ctrl+y - if (await changeTracker().undoRedo(e)) return; - - // If our active element is some type of input then handle changes after they're done - if (ChangeTracker.bindInput(bindInputEl)) return; - changeTracker().checkState(); - }); - }, - true - ); - - window.addEventListener("keyup", (e) => { - if (keyIgnored) { - keyIgnored = false; - changeTracker().checkState(); - } - }); - - // Handle clicking DOM elements (e.g. widgets) - window.addEventListener("mouseup", () => { - changeTracker().checkState(); - }); - - // Handle prompt queue event for dynamic widget changes - api.addEventListener("promptQueued", () => { - changeTracker().checkState(); - }); - - // Handle litegraph clicks - const processMouseUp = LGraphCanvas.prototype.processMouseUp; - LGraphCanvas.prototype.processMouseUp = function (e) { - const v = processMouseUp.apply(this, arguments); - changeTracker().checkState(); - return v; - }; - const processMouseDown = LGraphCanvas.prototype.processMouseDown; - LGraphCanvas.prototype.processMouseDown = function (e) { - const v = processMouseDown.apply(this, arguments); - changeTracker().checkState(); - return v; - }; - - // Handle litegraph context menu for COMBO widgets - const close = LiteGraph.ContextMenu.prototype.close; - LiteGraph.ContextMenu.prototype.close = function (e) { - const v = close.apply(this, arguments); - changeTracker().checkState(); - return v; - }; - - // Detects nodes being added via the node search dialog - const onNodeAdded = LiteGraph.LGraph.prototype.onNodeAdded; - LiteGraph.LGraph.prototype.onNodeAdded = function () { - const v = onNodeAdded?.apply(this, arguments); - if (!app?.configuringGraph) { - const ct = changeTracker(); - if (!ct.isOurLoad) { - ct.checkState(); - } - } - return v; - }; - - // Store node outputs - api.addEventListener("executed", ({ detail }) => { - const prompt = app.workflowManager.queuedPrompts[detail.prompt_id]; - if (!prompt?.workflow) return; - const nodeOutputs = (prompt.workflow.changeTracker.nodeOutputs ??= {}); - const output = nodeOutputs[detail.node]; - if (detail.merge && output) { - for (const k in detail.output ?? {}) { - const v = output[k]; - if (v instanceof Array) { - output[k] = v.concat(detail.output[k]); - } else { - output[k] = detail.output[k]; - } - } - } else { - nodeOutputs[detail.node] = detail.output; - } - }); - } - - static bindInput(app, activeEl) { - if (activeEl && activeEl.tagName !== "CANVAS" && activeEl.tagName !== "BODY") { - for (const evt of ["change", "input", "blur"]) { - if (`on${evt}` in activeEl) { - const listener = () => { - app.workflowManager.activeWorkflow.changeTracker.checkState(); - activeEl.removeEventListener(evt, listener); - }; - activeEl.addEventListener(evt, listener); - return true; - } - } - } - } - - static graphEqual(a, b, path = "") { - if (a === b) return true; - - if (typeof a == "object" && a && typeof b == "object" && b) { - const keys = Object.getOwnPropertyNames(a); - - if (keys.length != Object.getOwnPropertyNames(b).length) { - return false; - } - - for (const key of keys) { - let av = a[key]; - let bv = b[key]; - if (!path && key === "nodes") { - // Nodes need to be sorted as the order changes when selecting nodes - av = [...av].sort((a, b) => a.id - b.id); - bv = [...bv].sort((a, b) => a.id - b.id); - } else if (path === "extra.ds") { - // Ignore view changes - continue; - } - if (!ChangeTracker.graphEqual(av, bv, path + (path ? "." : "") + key)) { - return false; - } - } - - return true; - } - - return false; - } -} - -const globalTracker = new ChangeTracker({}); \ No newline at end of file diff --git a/web/scripts/defaultGraph.js b/web/scripts/defaultGraph.js deleted file mode 100644 index 9b3cb4a7e..000000000 --- a/web/scripts/defaultGraph.js +++ /dev/null @@ -1,119 +0,0 @@ -export const defaultGraph = { - last_node_id: 9, - last_link_id: 9, - nodes: [ - { - id: 7, - type: "CLIPTextEncode", - pos: [413, 389], - size: { 0: 425.27801513671875, 1: 180.6060791015625 }, - flags: {}, - order: 3, - mode: 0, - inputs: [{ name: "clip", type: "CLIP", link: 5 }], - outputs: [{ name: "CONDITIONING", type: "CONDITIONING", links: [6], slot_index: 0 }], - properties: {}, - widgets_values: ["text, watermark"], - }, - { - id: 6, - type: "CLIPTextEncode", - pos: [415, 186], - size: { 0: 422.84503173828125, 1: 164.31304931640625 }, - flags: {}, - order: 2, - mode: 0, - inputs: [{ name: "clip", type: "CLIP", link: 3 }], - outputs: [{ name: "CONDITIONING", type: "CONDITIONING", links: [4], slot_index: 0 }], - properties: {}, - widgets_values: ["beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"], - }, - { - id: 5, - type: "EmptyLatentImage", - pos: [473, 609], - size: { 0: 315, 1: 106 }, - flags: {}, - order: 1, - mode: 0, - outputs: [{ name: "LATENT", type: "LATENT", links: [2], slot_index: 0 }], - properties: {}, - widgets_values: [512, 512, 1], - }, - { - id: 3, - type: "KSampler", - pos: [863, 186], - size: { 0: 315, 1: 262 }, - flags: {}, - order: 4, - mode: 0, - inputs: [ - { name: "model", type: "MODEL", link: 1 }, - { name: "positive", type: "CONDITIONING", link: 4 }, - { name: "negative", type: "CONDITIONING", link: 6 }, - { name: "latent_image", type: "LATENT", link: 2 }, - ], - outputs: [{ name: "LATENT", type: "LATENT", links: [7], slot_index: 0 }], - properties: {}, - widgets_values: [156680208700286, true, 20, 8, "euler", "normal", 1], - }, - { - id: 8, - type: "VAEDecode", - pos: [1209, 188], - size: { 0: 210, 1: 46 }, - flags: {}, - order: 5, - mode: 0, - inputs: [ - { name: "samples", type: "LATENT", link: 7 }, - { name: "vae", type: "VAE", link: 8 }, - ], - outputs: [{ name: "IMAGE", type: "IMAGE", links: [9], slot_index: 0 }], - properties: {}, - }, - { - id: 9, - type: "SaveImage", - pos: [1451, 189], - size: { 0: 210, 1: 26 }, - flags: {}, - order: 6, - mode: 0, - inputs: [{ name: "images", type: "IMAGE", link: 9 }], - properties: {}, - }, - { - id: 4, - type: "CheckpointLoaderSimple", - pos: [26, 474], - size: { 0: 315, 1: 98 }, - flags: {}, - order: 0, - mode: 0, - outputs: [ - { name: "MODEL", type: "MODEL", links: [1], slot_index: 0 }, - { name: "CLIP", type: "CLIP", links: [3, 5], slot_index: 1 }, - { name: "VAE", type: "VAE", links: [8], slot_index: 2 }, - ], - properties: {}, - widgets_values: ["v1-5-pruned-emaonly.ckpt"], - }, - ], - links: [ - [1, 4, 0, 3, 0, "MODEL"], - [2, 5, 0, 3, 3, "LATENT"], - [3, 4, 1, 6, 0, "CLIP"], - [4, 6, 0, 3, 1, "CONDITIONING"], - [5, 4, 1, 7, 0, "CLIP"], - [6, 7, 0, 3, 2, "CONDITIONING"], - [7, 3, 0, 8, 0, "LATENT"], - [8, 4, 2, 8, 1, "VAE"], - [9, 8, 0, 9, 0, "IMAGE"], - ], - groups: [], - config: {}, - extra: {}, - version: 0.4, -}; diff --git a/web/scripts/domWidget.js b/web/scripts/domWidget.js deleted file mode 100644 index 03ed2ff10..000000000 --- a/web/scripts/domWidget.js +++ /dev/null @@ -1,335 +0,0 @@ -import { app, ANIM_PREVIEW_WIDGET } from "./app.js"; - -const SIZE = Symbol(); - -function intersect(a, b) { - const x = Math.max(a.x, b.x); - const num1 = Math.min(a.x + a.width, b.x + b.width); - const y = Math.max(a.y, b.y); - const num2 = Math.min(a.y + a.height, b.y + b.height); - if (num1 >= x && num2 >= y) return [x, y, num1 - x, num2 - y]; - else return null; -} - -function getClipPath(node, element) { - const selectedNode = Object.values(app.canvas.selected_nodes)[0]; - if (selectedNode && selectedNode !== node) { - const elRect = element.getBoundingClientRect(); - const MARGIN = 7; - const scale = app.canvas.ds.scale; - - const bounding = selectedNode.getBounding(); - const intersection = intersect( - { x: elRect.x / scale, y: elRect.y / scale, width: elRect.width / scale, height: elRect.height / scale }, - { - x: selectedNode.pos[0] + app.canvas.ds.offset[0] - MARGIN, - y: selectedNode.pos[1] + app.canvas.ds.offset[1] - LiteGraph.NODE_TITLE_HEIGHT - MARGIN, - width: bounding[2] + MARGIN + MARGIN, - height: bounding[3] + MARGIN + MARGIN, - } - ); - - if (!intersection) { - return ""; - } - - const widgetRect = element.getBoundingClientRect(); - const clipX = elRect.left + intersection[0] - widgetRect.x / scale + "px"; - const clipY = elRect.top + intersection[1] - widgetRect.y / scale + "px"; - const clipWidth = intersection[2] + "px"; - const clipHeight = intersection[3] + "px"; - const path = `polygon(0% 0%, 0% 100%, ${clipX} 100%, ${clipX} ${clipY}, calc(${clipX} + ${clipWidth}) ${clipY}, calc(${clipX} + ${clipWidth}) calc(${clipY} + ${clipHeight}), ${clipX} calc(${clipY} + ${clipHeight}), ${clipX} 100%, 100% 100%, 100% 0%)`; - return path; - } - return ""; -} - -function computeSize(size) { - if (this.widgets?.[0]?.last_y == null) return; - - let y = this.widgets[0].last_y; - let freeSpace = size[1] - y; - - let widgetHeight = 0; - let dom = []; - for (const w of this.widgets) { - if (w.type === "converted-widget") { - // Ignore - delete w.computedHeight; - } else if (w.computeSize) { - widgetHeight += w.computeSize()[1] + 4; - } else if (w.element) { - // Extract DOM widget size info - const styles = getComputedStyle(w.element); - let minHeight = w.options.getMinHeight?.() ?? parseInt(styles.getPropertyValue("--comfy-widget-min-height")); - let maxHeight = w.options.getMaxHeight?.() ?? parseInt(styles.getPropertyValue("--comfy-widget-max-height")); - - let prefHeight = w.options.getHeight?.() ?? styles.getPropertyValue("--comfy-widget-height"); - if (prefHeight.endsWith?.("%")) { - prefHeight = size[1] * (parseFloat(prefHeight.substring(0, prefHeight.length - 1)) / 100); - } else { - prefHeight = parseInt(prefHeight); - if (isNaN(minHeight)) { - minHeight = prefHeight; - } - } - if (isNaN(minHeight)) { - minHeight = 50; - } - if (!isNaN(maxHeight)) { - if (!isNaN(prefHeight)) { - prefHeight = Math.min(prefHeight, maxHeight); - } else { - prefHeight = maxHeight; - } - } - dom.push({ - minHeight, - prefHeight, - w, - }); - } else { - widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 4; - } - } - - freeSpace -= widgetHeight; - - // Calculate sizes with all widgets at their min height - const prefGrow = []; // Nodes that want to grow to their prefd size - const canGrow = []; // Nodes that can grow to auto size - let growBy = 0; - for (const d of dom) { - freeSpace -= d.minHeight; - if (isNaN(d.prefHeight)) { - canGrow.push(d); - d.w.computedHeight = d.minHeight; - } else { - const diff = d.prefHeight - d.minHeight; - if (diff > 0) { - prefGrow.push(d); - growBy += diff; - d.diff = diff; - } else { - d.w.computedHeight = d.minHeight; - } - } - } - - if (this.imgs && !this.widgets.find((w) => w.name === ANIM_PREVIEW_WIDGET)) { - // Allocate space for image - freeSpace -= 220; - } - - this.freeWidgetSpace = freeSpace; - - if (freeSpace < 0) { - // Not enough space for all widgets so we need to grow - size[1] -= freeSpace; - this.graph.setDirtyCanvas(true); - } else { - // Share the space between each - const growDiff = freeSpace - growBy; - if (growDiff > 0) { - // All pref sizes can be fulfilled - freeSpace = growDiff; - for (const d of prefGrow) { - d.w.computedHeight = d.prefHeight; - } - } else { - // We need to grow evenly - const shared = -growDiff / prefGrow.length; - for (const d of prefGrow) { - d.w.computedHeight = d.prefHeight - shared; - } - freeSpace = 0; - } - - if (freeSpace > 0 && canGrow.length) { - // Grow any that are auto height - const shared = freeSpace / canGrow.length; - for (const d of canGrow) { - d.w.computedHeight += shared; - } - } - } - - // Position each of the widgets - for (const w of this.widgets) { - w.y = y; - if (w.computedHeight) { - y += w.computedHeight; - } else if (w.computeSize) { - y += w.computeSize()[1] + 4; - } else { - y += LiteGraph.NODE_WIDGET_HEIGHT + 4; - } - } -} - -// Override the compute visible nodes function to allow us to hide/show DOM elements when the node goes offscreen -const elementWidgets = new Set(); -const computeVisibleNodes = LGraphCanvas.prototype.computeVisibleNodes; -LGraphCanvas.prototype.computeVisibleNodes = function () { - const visibleNodes = computeVisibleNodes.apply(this, arguments); - for (const node of app.graph._nodes) { - if (elementWidgets.has(node)) { - const hidden = visibleNodes.indexOf(node) === -1; - for (const w of node.widgets) { - if (w.element) { - w.element.hidden = hidden; - w.element.style.display = hidden ? "none" : undefined; - if (hidden) { - w.options.onHide?.(w); - } - } - } - } - } - - return visibleNodes; -}; - -let enableDomClipping = true; - -export function addDomClippingSetting() { - app.ui.settings.addSetting({ - id: "Comfy.DOMClippingEnabled", - name: "Enable DOM element clipping (enabling may reduce performance)", - type: "boolean", - defaultValue: enableDomClipping, - onChange(value) { - enableDomClipping = !!value; - }, - }); -} - -LGraphNode.prototype.addDOMWidget = function (name, type, element, options) { - options = { hideOnZoom: true, selectOn: ["focus", "click"], ...options }; - - if (!element.parentElement) { - document.body.append(element); - } - element.hidden = true; - element.style.display = "none"; - - let mouseDownHandler; - if (element.blur) { - mouseDownHandler = (event) => { - if (!element.contains(event.target)) { - element.blur(); - } - }; - document.addEventListener("mousedown", mouseDownHandler); - } - - const { nodeData } = this.constructor; - const tooltip = (nodeData?.input.required?.[name] ?? nodeData?.input.optional?.[name])?.[1]?.tooltip; - if (tooltip && !element.title) { - element.title = tooltip; - } - - const widget = { - type, - name, - get value() { - return options.getValue?.() ?? undefined; - }, - set value(v) { - options.setValue?.(v); - widget.callback?.(widget.value); - }, - draw: function (ctx, node, widgetWidth, y, widgetHeight) { - if (widget.computedHeight == null) { - computeSize.call(node, node.size); - } - - const hidden = - node.flags?.collapsed || - (!!options.hideOnZoom && app.canvas.ds.scale < 0.5) || - widget.computedHeight <= 0 || - widget.type === "converted-widget"|| - widget.type === "hidden"; - element.hidden = hidden; - element.style.display = hidden ? "none" : null; - if (hidden) { - widget.options.onHide?.(widget); - return; - } - - const margin = 10; - const elRect = ctx.canvas.getBoundingClientRect(); - const transform = new DOMMatrix() - .scaleSelf(elRect.width / ctx.canvas.width, elRect.height / ctx.canvas.height) - .multiplySelf(ctx.getTransform()) - .translateSelf(margin, margin + y ); - - const scale = new DOMMatrix().scaleSelf(transform.a, transform.d); - - Object.assign(element.style, { - transformOrigin: "0 0", - transform: scale, - left: `${transform.a + transform.e + elRect.left}px`, - top: `${transform.d + transform.f + elRect.top}px`, - width: `${widgetWidth - margin * 2}px`, - height: `${(widget.computedHeight ?? 50) - margin * 2}px`, - position: "absolute", - zIndex: app.graph._nodes.indexOf(node), - }); - - if (enableDomClipping) { - element.style.clipPath = getClipPath(node, element); - element.style.willChange = "clip-path"; - } - - this.options.onDraw?.(widget); - }, - element, - options, - onRemove() { - if (mouseDownHandler) { - document.removeEventListener("mousedown", mouseDownHandler); - } - element.remove(); - }, - }; - - for (const evt of options.selectOn) { - element.addEventListener(evt, () => { - app.canvas.selectNode(this); - app.canvas.bringToFront(this); - }); - } - - this.addCustomWidget(widget); - elementWidgets.add(this); - - const collapse = this.collapse; - this.collapse = function() { - collapse.apply(this, arguments); - if(this.flags?.collapsed) { - element.hidden = true; - element.style.display = "none"; - } - } - - const onRemoved = this.onRemoved; - this.onRemoved = function () { - element.remove(); - elementWidgets.delete(this); - onRemoved?.apply(this, arguments); - }; - - if (!this[SIZE]) { - this[SIZE] = true; - const onResize = this.onResize; - this.onResize = function (size) { - options.beforeResize?.call(widget, this); - computeSize.call(this, size); - onResize?.apply(this, arguments); - options.afterResize?.call(widget, this); - }; - } - - return widget; -}; diff --git a/web/scripts/logging.js b/web/scripts/logging.js deleted file mode 100644 index 875dd970b..000000000 --- a/web/scripts/logging.js +++ /dev/null @@ -1,370 +0,0 @@ -import { $el, ComfyDialog } from "./ui.js"; -import { api } from "./api.js"; - -$el("style", { - textContent: ` - .comfy-logging-logs { - display: grid; - color: var(--fg-color); - white-space: pre-wrap; - } - .comfy-logging-log { - display: contents; - } - .comfy-logging-title { - background: var(--tr-even-bg-color); - font-weight: bold; - margin-bottom: 5px; - text-align: center; - } - .comfy-logging-log div { - background: var(--row-bg); - padding: 5px; - } - `, - parent: document.body, -}); - -// Stringify function supporting max depth and removal of circular references -// https://stackoverflow.com/a/57193345 -function stringify(val, depth, replacer, space, onGetObjID) { - depth = isNaN(+depth) ? 1 : depth; - var recursMap = new WeakMap(); - function _build(val, depth, o, a, r) { - // (JSON.stringify() has it's own rules, which we respect here by using it for property iteration) - return !val || typeof val != "object" - ? val - : ((r = recursMap.has(val)), - recursMap.set(val, true), - (a = Array.isArray(val)), - r - ? (o = (onGetObjID && onGetObjID(val)) || null) - : JSON.stringify(val, function (k, v) { - if (a || depth > 0) { - if (replacer) v = replacer(k, v); - if (!k) return (a = Array.isArray(v)), (val = v); - !o && (o = a ? [] : {}); - o[k] = _build(v, a ? depth : depth - 1); - } - }), - o === void 0 ? (a ? [] : {}) : o); - } - return JSON.stringify(_build(val, depth), null, space); -} - -const jsonReplacer = (k, v, ui) => { - if (v instanceof Array && v.length === 1) { - v = v[0]; - } - if (v instanceof Date) { - v = v.toISOString(); - if (ui) { - v = v.split("T")[1]; - } - } - if (v instanceof Error) { - let err = ""; - if (v.name) err += v.name + "\n"; - if (v.message) err += v.message + "\n"; - if (v.stack) err += v.stack + "\n"; - if (!err) { - err = v.toString(); - } - v = err; - } - return v; -}; - -const fileInput = $el("input", { - type: "file", - accept: ".json", - style: { display: "none" }, - parent: document.body, -}); - -class ComfyLoggingDialog extends ComfyDialog { - constructor(logging) { - super(); - this.logging = logging; - } - - clear() { - this.logging.clear(); - this.show(); - } - - export() { - const blob = new Blob([stringify([...this.logging.entries], 20, jsonReplacer, "\t")], { - type: "application/json", - }); - const url = URL.createObjectURL(blob); - const a = $el("a", { - href: url, - download: `comfyui-logs-${Date.now()}.json`, - style: { display: "none" }, - parent: document.body, - }); - a.click(); - setTimeout(function () { - a.remove(); - window.URL.revokeObjectURL(url); - }, 0); - } - - import() { - fileInput.onchange = () => { - const reader = new FileReader(); - reader.onload = () => { - fileInput.remove(); - try { - const obj = JSON.parse(reader.result); - if (obj instanceof Array) { - this.show(obj); - } else { - throw new Error("Invalid file selected."); - } - } catch (error) { - alert("Unable to load logs: " + error.message); - } - }; - reader.readAsText(fileInput.files[0]); - }; - fileInput.click(); - } - - createButtons() { - return [ - $el("button", { - type: "button", - textContent: "Clear", - onclick: () => this.clear(), - }), - $el("button", { - type: "button", - textContent: "Export logs...", - onclick: () => this.export(), - }), - $el("button", { - type: "button", - textContent: "View exported logs...", - onclick: () => this.import(), - }), - ...super.createButtons(), - ]; - } - - getTypeColor(type) { - switch (type) { - case "error": - return "red"; - case "warn": - return "orange"; - case "debug": - return "dodgerblue"; - } - } - - show(entries) { - if (!entries) entries = this.logging.entries; - this.element.style.width = "100%"; - const cols = { - source: "Source", - type: "Type", - timestamp: "Timestamp", - message: "Message", - }; - const keys = Object.keys(cols); - const headers = Object.values(cols).map((title) => - $el("div.comfy-logging-title", { - textContent: title, - }) - ); - const rows = entries.map((entry, i) => { - return $el( - "div.comfy-logging-log", - { - $: (el) => el.style.setProperty("--row-bg", `var(--tr-${i % 2 ? "even" : "odd"}-bg-color)`), - }, - keys.map((key) => { - let v = entry[key]; - let color; - if (key === "type") { - color = this.getTypeColor(v); - } else { - v = jsonReplacer(key, v, true); - - if (typeof v === "object") { - v = stringify(v, 5, jsonReplacer, " "); - } - } - - return $el("div", { - style: { - color, - }, - textContent: v, - }); - }) - ); - }); - - const grid = $el( - "div.comfy-logging-logs", - { - style: { - gridTemplateColumns: `repeat(${headers.length}, 1fr)`, - }, - }, - [...headers, ...rows] - ); - const els = [grid]; - if (!this.logging.enabled) { - els.unshift( - $el("h3", { - style: { textAlign: "center" }, - textContent: "Logging is disabled", - }) - ); - } - super.show($el("div", els)); - } -} - -export class ComfyLogging { - /** - * @type Array<{ source: string, type: string, timestamp: Date, message: any }> - */ - entries = []; - - #enabled; - #console = {}; - - get enabled() { - return this.#enabled; - } - - set enabled(value) { - if (value === this.#enabled) return; - if (value) { - this.patchConsole(); - } else { - this.unpatchConsole(); - } - this.#enabled = value; - } - - constructor(app) { - this.app = app; - - this.dialog = new ComfyLoggingDialog(this); - this.addSetting(); - this.catchUnhandled(); - this.addInitData(); - } - - addSetting() { - const settingId = "Comfy.Logging.Enabled"; - const htmlSettingId = settingId.replaceAll(".", "-"); - const setting = this.app.ui.settings.addSetting({ - id: settingId, - name: settingId, - defaultValue: true, - onChange: (value) => { - this.enabled = value; - }, - type: (name, setter, value) => { - return $el("tr", [ - $el("td", [ - $el("label", { - textContent: "Logging", - for: htmlSettingId, - }), - ]), - $el("td", [ - $el("input", { - id: htmlSettingId, - type: "checkbox", - checked: value, - onchange: (event) => { - setter(event.target.checked); - }, - }), - $el("button", { - textContent: "View Logs", - onclick: () => { - this.app.ui.settings.element.close(); - this.dialog.show(); - }, - style: { - fontSize: "14px", - display: "block", - marginTop: "5px", - }, - }), - ]), - ]); - }, - }); - this.enabled = setting.value; - } - - patchConsole() { - // Capture common console outputs - const self = this; - for (const type of ["log", "warn", "error", "debug"]) { - const orig = console[type]; - this.#console[type] = orig; - console[type] = function () { - orig.apply(console, arguments); - self.addEntry("console", type, ...arguments); - }; - } - } - - unpatchConsole() { - // Restore original console functions - for (const type of Object.keys(this.#console)) { - console[type] = this.#console[type]; - } - this.#console = {}; - } - - catchUnhandled() { - // Capture uncaught errors - window.addEventListener("error", (e) => { - this.addEntry("window", "error", e.error ?? "Unknown error"); - return false; - }); - - window.addEventListener("unhandledrejection", (e) => { - this.addEntry("unhandledrejection", "error", e.reason ?? "Unknown error"); - }); - } - - clear() { - this.entries = []; - } - - addEntry(source, type, ...args) { - if (this.enabled) { - this.entries.push({ - source, - type, - timestamp: new Date(), - message: args, - }); - } - } - - log(source, ...args) { - this.addEntry(source, "log", ...args); - } - - async addInitData() { - if (!this.enabled) return; - const source = "ComfyUI.Logging"; - this.addEntry(source, "debug", { UserAgent: navigator.userAgent }); - const systemStats = await api.getSystemStats(); - this.addEntry(source, "debug", systemStats); - } -} diff --git a/web/scripts/pnginfo.js b/web/scripts/pnginfo.js deleted file mode 100644 index 4477ed7a1..000000000 --- a/web/scripts/pnginfo.js +++ /dev/null @@ -1,507 +0,0 @@ -import { api } from "./api.js"; - -export function getPngMetadata(file) { - return new Promise((r) => { - const reader = new FileReader(); - reader.onload = (event) => { - // Get the PNG data as a Uint8Array - const pngData = new Uint8Array(event.target.result); - const dataView = new DataView(pngData.buffer); - - // Check that the PNG signature is present - if (dataView.getUint32(0) !== 0x89504e47) { - console.error("Not a valid PNG file"); - r(); - return; - } - - // Start searching for chunks after the PNG signature - let offset = 8; - let txt_chunks = {}; - // Loop through the chunks in the PNG file - while (offset < pngData.length) { - // Get the length of the chunk - const length = dataView.getUint32(offset); - // Get the chunk type - const type = String.fromCharCode(...pngData.slice(offset + 4, offset + 8)); - if (type === "tEXt" || type == "comf" || type === "iTXt") { - // Get the keyword - let keyword_end = offset + 8; - while (pngData[keyword_end] !== 0) { - keyword_end++; - } - const keyword = String.fromCharCode(...pngData.slice(offset + 8, keyword_end)); - // Get the text - const contentArraySegment = pngData.slice(keyword_end + 1, offset + 8 + length); - const contentJson = new TextDecoder("utf-8").decode(contentArraySegment); - txt_chunks[keyword] = contentJson; - } - - offset += 12 + length; - } - - r(txt_chunks); - }; - - reader.readAsArrayBuffer(file); - }); -} - -function parseExifData(exifData) { - // Check for the correct TIFF header (0x4949 for little-endian or 0x4D4D for big-endian) - const isLittleEndian = String.fromCharCode(...exifData.slice(0, 2)) === "II"; - - // Function to read 16-bit and 32-bit integers from binary data - function readInt(offset, isLittleEndian, length) { - let arr = exifData.slice(offset, offset + length) - if (length === 2) { - return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint16(0, isLittleEndian); - } else if (length === 4) { - return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint32(0, isLittleEndian); - } - } - - // Read the offset to the first IFD (Image File Directory) - const ifdOffset = readInt(4, isLittleEndian, 4); - - function parseIFD(offset) { - const numEntries = readInt(offset, isLittleEndian, 2); - const result = {}; - - for (let i = 0; i < numEntries; i++) { - const entryOffset = offset + 2 + i * 12; - const tag = readInt(entryOffset, isLittleEndian, 2); - const type = readInt(entryOffset + 2, isLittleEndian, 2); - const numValues = readInt(entryOffset + 4, isLittleEndian, 4); - const valueOffset = readInt(entryOffset + 8, isLittleEndian, 4); - - // Read the value(s) based on the data type - let value; - if (type === 2) { - // ASCII string - value = String.fromCharCode(...exifData.slice(valueOffset, valueOffset + numValues - 1)); - } - - result[tag] = value; - } - - return result; - } - - // Parse the first IFD - const ifdData = parseIFD(ifdOffset); - return ifdData; -} - -function splitValues(input) { - var output = {}; - for (var key in input) { - var value = input[key]; - var splitValues = value.split(':', 2); - output[splitValues[0]] = splitValues[1]; - } - return output; -} - -export function getWebpMetadata(file) { - return new Promise((r) => { - const reader = new FileReader(); - reader.onload = (event) => { - const webp = new Uint8Array(event.target.result); - const dataView = new DataView(webp.buffer); - - // Check that the WEBP signature is present - if (dataView.getUint32(0) !== 0x52494646 || dataView.getUint32(8) !== 0x57454250) { - console.error("Not a valid WEBP file"); - r(); - return; - } - - // Start searching for chunks after the WEBP signature - let offset = 12; - let txt_chunks = {}; - // Loop through the chunks in the WEBP file - while (offset < webp.length) { - const chunk_length = dataView.getUint32(offset + 4, true); - const chunk_type = String.fromCharCode(...webp.slice(offset, offset + 4)); - if (chunk_type === "EXIF") { - if (String.fromCharCode(...webp.slice(offset + 8, offset + 8 + 6)) == "Exif\0\0") { - offset += 6; - } - let data = parseExifData(webp.slice(offset + 8, offset + 8 + chunk_length)); - for (var key in data) { - var value = data[key]; - let index = value.indexOf(':'); - txt_chunks[value.slice(0, index)] = value.slice(index + 1); - } - break; - } - - offset += 8 + chunk_length; - } - - r(txt_chunks); - }; - - reader.readAsArrayBuffer(file); - }); -} - -export function getLatentMetadata(file) { - return new Promise((r) => { - const reader = new FileReader(); - reader.onload = (event) => { - const safetensorsData = new Uint8Array(event.target.result); - const dataView = new DataView(safetensorsData.buffer); - let header_size = dataView.getUint32(0, true); - let offset = 8; - let header = JSON.parse(new TextDecoder().decode(safetensorsData.slice(offset, offset + header_size))); - r(header.__metadata__); - }; - - var slice = file.slice(0, 1024 * 1024 * 4); - reader.readAsArrayBuffer(slice); - }); -} - - -function getString(dataView, offset, length) { - let string = ''; - for (let i = 0; i < length; i++) { - string += String.fromCharCode(dataView.getUint8(offset + i)); - } - return string; -} - -// Function to parse the Vorbis Comment block -function parseVorbisComment(dataView) { - let offset = 0; - const vendorLength = dataView.getUint32(offset, true); - offset += 4; - const vendorString = getString(dataView, offset, vendorLength); - offset += vendorLength; - - const userCommentListLength = dataView.getUint32(offset, true); - offset += 4; - const comments = {}; - for (let i = 0; i < userCommentListLength; i++) { - const commentLength = dataView.getUint32(offset, true); - offset += 4; - const comment = getString(dataView, offset, commentLength); - offset += commentLength; - - const ind = comment.indexOf('=') - const key = comment.substring(0, ind); - - comments[key] = comment.substring(ind+1); - } - - return comments; -} - -// Function to read a FLAC file and parse Vorbis comments -export function getFlacMetadata(file) { - return new Promise((r) => { - const reader = new FileReader(); - reader.onload = function(event) { - const arrayBuffer = event.target.result; - const dataView = new DataView(arrayBuffer); - - // Verify the FLAC signature - const signature = String.fromCharCode(...new Uint8Array(arrayBuffer, 0, 4)); - if (signature !== 'fLaC') { - console.error('Not a valid FLAC file'); - return; - } - - // Parse metadata blocks - let offset = 4; - let vorbisComment = null; - while (offset < dataView.byteLength) { - const isLastBlock = dataView.getUint8(offset) & 0x80; - const blockType = dataView.getUint8(offset) & 0x7F; - const blockSize = dataView.getUint32(offset, false) & 0xFFFFFF; - offset += 4; - - if (blockType === 4) { // Vorbis Comment block type - vorbisComment = parseVorbisComment(new DataView(arrayBuffer, offset, blockSize)); - } - - offset += blockSize; - if (isLastBlock) break; - } - - r(vorbisComment); - }; - reader.readAsArrayBuffer(file); - }); -} - -export async function importA1111(graph, parameters) { - const p = parameters.lastIndexOf("\nSteps:"); - if (p > -1) { - const embeddings = await api.getEmbeddings(); - const opts = parameters - .substr(p) - .split("\n")[1] - .match(new RegExp("\\s*([^:]+:\\s*([^\"\\{].*?|\".*?\"|\\{.*?\\}))\\s*(,|$)", "g")) - .reduce((p, n) => { - const s = n.split(":"); - if (s[1].endsWith(',')) { - s[1] = s[1].substr(0, s[1].length -1); - } - p[s[0].trim().toLowerCase()] = s[1].trim(); - return p; - }, {}); - const p2 = parameters.lastIndexOf("\nNegative prompt:", p); - if (p2 > -1) { - let positive = parameters.substr(0, p2).trim(); - let negative = parameters.substring(p2 + 18, p).trim(); - - const ckptNode = LiteGraph.createNode("CheckpointLoaderSimple"); - const clipSkipNode = LiteGraph.createNode("CLIPSetLastLayer"); - const positiveNode = LiteGraph.createNode("CLIPTextEncode"); - const negativeNode = LiteGraph.createNode("CLIPTextEncode"); - const samplerNode = LiteGraph.createNode("KSampler"); - const imageNode = LiteGraph.createNode("EmptyLatentImage"); - const vaeNode = LiteGraph.createNode("VAEDecode"); - const vaeLoaderNode = LiteGraph.createNode("VAELoader"); - const saveNode = LiteGraph.createNode("SaveImage"); - let hrSamplerNode = null; - let hrSteps = null; - - const ceil64 = (v) => Math.ceil(v / 64) * 64; - - function getWidget(node, name) { - return node.widgets.find((w) => w.name === name); - } - - function setWidgetValue(node, name, value, isOptionPrefix) { - const w = getWidget(node, name); - if (isOptionPrefix) { - const o = w.options.values.find((w) => w.startsWith(value)); - if (o) { - w.value = o; - } else { - console.warn(`Unknown value '${value}' for widget '${name}'`, node); - w.value = value; - } - } else { - w.value = value; - } - } - - function createLoraNodes(clipNode, text, prevClip, prevModel) { - const loras = []; - text = text.replace(/]+)>/g, function (m, c) { - const s = c.split(":"); - const weight = parseFloat(s[1]); - if (isNaN(weight)) { - console.warn("Invalid LORA", m); - } else { - loras.push({ name: s[0], weight }); - } - return ""; - }); - - for (const l of loras) { - const loraNode = LiteGraph.createNode("LoraLoader"); - graph.add(loraNode); - setWidgetValue(loraNode, "lora_name", l.name, true); - setWidgetValue(loraNode, "strength_model", l.weight); - setWidgetValue(loraNode, "strength_clip", l.weight); - prevModel.node.connect(prevModel.index, loraNode, 0); - prevClip.node.connect(prevClip.index, loraNode, 1); - prevModel = { node: loraNode, index: 0 }; - prevClip = { node: loraNode, index: 1 }; - } - - prevClip.node.connect(1, clipNode, 0); - prevModel.node.connect(0, samplerNode, 0); - if (hrSamplerNode) { - prevModel.node.connect(0, hrSamplerNode, 0); - } - - return { text, prevModel, prevClip }; - } - - function replaceEmbeddings(text) { - if(!embeddings.length) return text; - return text.replaceAll( - new RegExp( - "\\b(" + embeddings.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\b|\\b") + ")\\b", - "ig" - ), - "embedding:$1" - ); - } - - function popOpt(name) { - const v = opts[name]; - delete opts[name]; - return v; - } - - graph.clear(); - graph.add(ckptNode); - graph.add(clipSkipNode); - graph.add(positiveNode); - graph.add(negativeNode); - graph.add(samplerNode); - graph.add(imageNode); - graph.add(vaeNode); - graph.add(vaeLoaderNode); - graph.add(saveNode); - - ckptNode.connect(1, clipSkipNode, 0); - clipSkipNode.connect(0, positiveNode, 0); - clipSkipNode.connect(0, negativeNode, 0); - ckptNode.connect(0, samplerNode, 0); - positiveNode.connect(0, samplerNode, 1); - negativeNode.connect(0, samplerNode, 2); - imageNode.connect(0, samplerNode, 3); - vaeNode.connect(0, saveNode, 0); - samplerNode.connect(0, vaeNode, 0); - vaeLoaderNode.connect(0, vaeNode, 1); - - const handlers = { - model(v) { - setWidgetValue(ckptNode, "ckpt_name", v, true); - }, - "vae"(v) { - setWidgetValue(vaeLoaderNode, "vae_name", v, true); - }, - "cfg scale"(v) { - setWidgetValue(samplerNode, "cfg", +v); - }, - "clip skip"(v) { - setWidgetValue(clipSkipNode, "stop_at_clip_layer", -v); - }, - sampler(v) { - let name = v.toLowerCase().replace("++", "pp").replaceAll(" ", "_"); - if (name.includes("karras")) { - name = name.replace("karras", "").replace(/_+$/, ""); - setWidgetValue(samplerNode, "scheduler", "karras"); - } else { - setWidgetValue(samplerNode, "scheduler", "normal"); - } - const w = getWidget(samplerNode, "sampler_name"); - const o = w.options.values.find((w) => w === name || w === "sample_" + name); - if (o) { - setWidgetValue(samplerNode, "sampler_name", o); - } - }, - size(v) { - const wxh = v.split("x"); - const w = ceil64(+wxh[0]); - const h = ceil64(+wxh[1]); - const hrUp = popOpt("hires upscale"); - const hrSz = popOpt("hires resize"); - hrSteps = popOpt("hires steps"); - let hrMethod = popOpt("hires upscaler"); - - setWidgetValue(imageNode, "width", w); - setWidgetValue(imageNode, "height", h); - - if (hrUp || hrSz) { - let uw, uh; - if (hrUp) { - uw = w * hrUp; - uh = h * hrUp; - } else { - const s = hrSz.split("x"); - uw = +s[0]; - uh = +s[1]; - } - - let upscaleNode; - let latentNode; - - if (hrMethod.startsWith("Latent")) { - latentNode = upscaleNode = LiteGraph.createNode("LatentUpscale"); - graph.add(upscaleNode); - samplerNode.connect(0, upscaleNode, 0); - - switch (hrMethod) { - case "Latent (nearest-exact)": - hrMethod = "nearest-exact"; - break; - } - setWidgetValue(upscaleNode, "upscale_method", hrMethod, true); - } else { - const decode = LiteGraph.createNode("VAEDecodeTiled"); - graph.add(decode); - samplerNode.connect(0, decode, 0); - vaeLoaderNode.connect(0, decode, 1); - - const upscaleLoaderNode = LiteGraph.createNode("UpscaleModelLoader"); - graph.add(upscaleLoaderNode); - setWidgetValue(upscaleLoaderNode, "model_name", hrMethod, true); - - const modelUpscaleNode = LiteGraph.createNode("ImageUpscaleWithModel"); - graph.add(modelUpscaleNode); - decode.connect(0, modelUpscaleNode, 1); - upscaleLoaderNode.connect(0, modelUpscaleNode, 0); - - upscaleNode = LiteGraph.createNode("ImageScale"); - graph.add(upscaleNode); - modelUpscaleNode.connect(0, upscaleNode, 0); - - const vaeEncodeNode = (latentNode = LiteGraph.createNode("VAEEncodeTiled")); - graph.add(vaeEncodeNode); - upscaleNode.connect(0, vaeEncodeNode, 0); - vaeLoaderNode.connect(0, vaeEncodeNode, 1); - } - - setWidgetValue(upscaleNode, "width", ceil64(uw)); - setWidgetValue(upscaleNode, "height", ceil64(uh)); - - hrSamplerNode = LiteGraph.createNode("KSampler"); - graph.add(hrSamplerNode); - ckptNode.connect(0, hrSamplerNode, 0); - positiveNode.connect(0, hrSamplerNode, 1); - negativeNode.connect(0, hrSamplerNode, 2); - latentNode.connect(0, hrSamplerNode, 3); - hrSamplerNode.connect(0, vaeNode, 0); - } - }, - steps(v) { - setWidgetValue(samplerNode, "steps", +v); - }, - seed(v) { - setWidgetValue(samplerNode, "seed", +v); - }, - }; - - for (const opt in opts) { - if (opt in handlers) { - handlers[opt](popOpt(opt)); - } - } - - if (hrSamplerNode) { - setWidgetValue(hrSamplerNode, "steps", hrSteps? +hrSteps : getWidget(samplerNode, "steps").value); - setWidgetValue(hrSamplerNode, "cfg", getWidget(samplerNode, "cfg").value); - setWidgetValue(hrSamplerNode, "scheduler", getWidget(samplerNode, "scheduler").value); - setWidgetValue(hrSamplerNode, "sampler_name", getWidget(samplerNode, "sampler_name").value); - setWidgetValue(hrSamplerNode, "denoise", +(popOpt("denoising strength") || "1")); - } - - let n = createLoraNodes(positiveNode, positive, { node: clipSkipNode, index: 0 }, { node: ckptNode, index: 0 }); - positive = n.text; - n = createLoraNodes(negativeNode, negative, n.prevClip, n.prevModel); - negative = n.text; - - setWidgetValue(positiveNode, "text", replaceEmbeddings(positive)); - setWidgetValue(negativeNode, "text", replaceEmbeddings(negative)); - - graph.arrange(); - - for (const opt of ["model hash", "ensd", "version", "vae hash", "ti hashes", "lora hashes", "hashes"]) { - delete opts[opt]; - } - - console.warn("Unhandled parameters:", opts); - } - } -} diff --git a/web/scripts/ui.js b/web/scripts/ui.js deleted file mode 100644 index 05258e378..000000000 --- a/web/scripts/ui.js +++ /dev/null @@ -1,664 +0,0 @@ -import { api } from "./api.js"; -import { ComfyDialog as _ComfyDialog } from "./ui/dialog.js"; -import { toggleSwitch } from "./ui/toggleSwitch.js"; -import { ComfySettingsDialog } from "./ui/settings.js"; - -export const ComfyDialog = _ComfyDialog; - -/** - * @template { string | (keyof HTMLElementTagNameMap) } K - * @typedef { K extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[K] : HTMLElement } ElementType - */ - -/** - * @template { string | (keyof HTMLElementTagNameMap) } K - * @param { K } tag HTML Element Tag and optional classes e.g. div.class1.class2 - * @param { string | Element | Element[] | ({ - * parent?: Element, - * $?: (el: ElementType) => void, - * dataset?: DOMStringMap, - * style?: Partial, - * for?: string - * } & Omit>, "style">) | undefined } [propsOrChildren] - * @param { string | Element | Element[] | undefined } [children] - * @returns { ElementType } - */ -export function $el(tag, propsOrChildren, children) { - const split = tag.split("."); - const element = document.createElement(split.shift()); - if (split.length > 0) { - element.classList.add(...split); - } - - if (propsOrChildren) { - if (typeof propsOrChildren === "string") { - propsOrChildren = { textContent: propsOrChildren }; - } else if (propsOrChildren instanceof Element) { - propsOrChildren = [propsOrChildren]; - } - if (Array.isArray(propsOrChildren)) { - element.append(...propsOrChildren); - } else { - const {parent, $: cb, dataset, style} = propsOrChildren; - delete propsOrChildren.parent; - delete propsOrChildren.$; - delete propsOrChildren.dataset; - delete propsOrChildren.style; - - if (Object.hasOwn(propsOrChildren, "for")) { - element.setAttribute("for", propsOrChildren.for) - } - - if (style) { - Object.assign(element.style, style); - } - - if (dataset) { - Object.assign(element.dataset, dataset); - } - - Object.assign(element, propsOrChildren); - if (children) { - element.append(...(children instanceof Array ? children.filter(Boolean) : [children])); - } - - if (parent) { - parent.append(element); - } - - if (cb) { - cb(element); - } - } - } - return element; -} - -function dragElement(dragEl, settings) { - var posDiffX = 0, - posDiffY = 0, - posStartX = 0, - posStartY = 0, - newPosX = 0, - newPosY = 0; - if (dragEl.getElementsByClassName("drag-handle")[0]) { - // if present, the handle is where you move the DIV from: - dragEl.getElementsByClassName("drag-handle")[0].onmousedown = dragMouseDown; - } else { - // otherwise, move the DIV from anywhere inside the DIV: - dragEl.onmousedown = dragMouseDown; - } - - // When the element resizes (e.g. view queue) ensure it is still in the windows bounds - const resizeObserver = new ResizeObserver(() => { - ensureInBounds(); - }).observe(dragEl); - - function ensureInBounds() { - try { - newPosX = Math.min(document.body.clientWidth - dragEl.clientWidth, Math.max(0, dragEl.offsetLeft)); - newPosY = Math.min(document.body.clientHeight - dragEl.clientHeight, Math.max(0, dragEl.offsetTop)); - - positionElement(); - } - catch(exception){ - // robust - } - } - - function positionElement() { - if(dragEl.style.display === "none") return; - - const halfWidth = document.body.clientWidth / 2; - const anchorRight = newPosX + dragEl.clientWidth / 2 > halfWidth; - - // set the element's new position: - if (anchorRight) { - dragEl.style.left = "unset"; - dragEl.style.right = document.body.clientWidth - newPosX - dragEl.clientWidth + "px"; - } else { - dragEl.style.left = newPosX + "px"; - dragEl.style.right = "unset"; - } - - dragEl.style.top = newPosY + "px"; - dragEl.style.bottom = "unset"; - - if (savePos) { - localStorage.setItem( - "Comfy.MenuPosition", - JSON.stringify({ - x: dragEl.offsetLeft, - y: dragEl.offsetTop, - }) - ); - } - } - - function restorePos() { - let pos = localStorage.getItem("Comfy.MenuPosition"); - if (pos) { - pos = JSON.parse(pos); - newPosX = pos.x; - newPosY = pos.y; - positionElement(); - ensureInBounds(); - } - } - - let savePos = undefined; - settings.addSetting({ - id: "Comfy.MenuPosition", - name: "Save menu position", - type: "boolean", - defaultValue: savePos, - onChange(value) { - if (savePos === undefined && value) { - restorePos(); - } - savePos = value; - }, - }); - - function dragMouseDown(e) { - e = e || window.event; - e.preventDefault(); - // get the mouse cursor position at startup: - posStartX = e.clientX; - posStartY = e.clientY; - document.onmouseup = closeDragElement; - // call a function whenever the cursor moves: - document.onmousemove = elementDrag; - } - - function elementDrag(e) { - e = e || window.event; - e.preventDefault(); - - dragEl.classList.add("comfy-menu-manual-pos"); - - // calculate the new cursor position: - posDiffX = e.clientX - posStartX; - posDiffY = e.clientY - posStartY; - posStartX = e.clientX; - posStartY = e.clientY; - - newPosX = Math.min(document.body.clientWidth - dragEl.clientWidth, Math.max(0, dragEl.offsetLeft + posDiffX)); - newPosY = Math.min(document.body.clientHeight - dragEl.clientHeight, Math.max(0, dragEl.offsetTop + posDiffY)); - - positionElement(); - } - - window.addEventListener("resize", () => { - ensureInBounds(); - }); - - function closeDragElement() { - // stop moving when mouse button is released: - document.onmouseup = null; - document.onmousemove = null; - } - - return restorePos; -} - -class ComfyList { - #type; - #text; - #reverse; - - constructor(text, type, reverse) { - this.#text = text; - this.#type = type || text.toLowerCase(); - this.#reverse = reverse || false; - this.element = $el("div.comfy-list"); - this.element.style.display = "none"; - } - - get visible() { - return this.element.style.display !== "none"; - } - - async load() { - const items = await api.getItems(this.#type); - this.element.replaceChildren( - ...Object.keys(items).flatMap((section) => [ - $el("h4", { - textContent: section, - }), - $el("div.comfy-list-items", [ - ...(this.#reverse ? items[section].reverse() : items[section]).map((item) => { - // Allow items to specify a custom remove action (e.g. for interrupt current prompt) - const removeAction = item.remove || { - name: "Delete", - cb: () => api.deleteItem(this.#type, item.prompt[1]), - }; - return $el("div", {textContent: item.prompt[0] + ": "}, [ - $el("button", { - textContent: "Load", - onclick: async () => { - await app.loadGraphData(item.prompt[3].extra_pnginfo.workflow, true, false); - if (item.outputs) { - app.nodeOutputs = {}; - for (const [key, value] of Object.entries(item.outputs)) { - const realKey = item?.meta?.[key]?.display_node ?? key; - app.nodeOutputs[realKey] = value; - } - } - }, - }), - $el("button", { - textContent: removeAction.name, - onclick: async () => { - await removeAction.cb(); - await this.update(); - }, - }), - ]); - }), - ]), - ]), - $el("div.comfy-list-actions", [ - $el("button", { - textContent: "Clear " + this.#text, - onclick: async () => { - await api.clearItems(this.#type); - await this.load(); - }, - }), - $el("button", {textContent: "Refresh", onclick: () => this.load()}), - ]) - ); - } - - async update() { - if (this.visible) { - await this.load(); - } - } - - async show() { - this.element.style.display = "block"; - this.button.textContent = "Close"; - - await this.load(); - } - - hide() { - this.element.style.display = "none"; - this.button.textContent = "View " + this.#text; - } - - toggle() { - if (this.visible) { - this.hide(); - return false; - } else { - this.show(); - return true; - } - } -} - -export class ComfyUI { - constructor(app) { - this.app = app; - this.dialog = new ComfyDialog(); - this.settings = new ComfySettingsDialog(app); - - this.batchCount = 1; - this.lastQueueSize = 0; - this.queue = new ComfyList("Queue"); - this.history = new ComfyList("History", "history", true); - - api.addEventListener("status", () => { - this.queue.update(); - this.history.update(); - }); - - const confirmClear = this.settings.addSetting({ - id: "Comfy.ConfirmClear", - name: "Require confirmation when clearing workflow", - type: "boolean", - defaultValue: true, - }); - - const promptFilename = this.settings.addSetting({ - id: "Comfy.PromptFilename", - name: "Prompt for filename when saving workflow", - type: "boolean", - defaultValue: true, - }); - - /** - * file format for preview - * - * format;quality - * - * ex) - * webp;50 -> webp, quality 50 - * jpeg;80 -> rgb, jpeg, quality 80 - * - * @type {string} - */ - const previewImage = this.settings.addSetting({ - id: "Comfy.PreviewFormat", - name: "When displaying a preview in the image widget, convert it to a lightweight image, e.g. webp, jpeg, webp;50, etc.", - type: "text", - defaultValue: "", - }); - - this.settings.addSetting({ - id: "Comfy.DisableSliders", - name: "Disable sliders.", - type: "boolean", - defaultValue: false, - }); - - this.settings.addSetting({ - id: "Comfy.DisableFloatRounding", - name: "Disable rounding floats (requires page reload).", - type: "boolean", - defaultValue: false, - }); - - this.settings.addSetting({ - id: "Comfy.FloatRoundingPrecision", - name: "Decimal places [0 = auto] (requires page reload).", - type: "slider", - attrs: { - min: 0, - max: 6, - step: 1, - }, - defaultValue: 0, - }); - - const fileInput = $el("input", { - id: "comfy-file-input", - type: "file", - accept: ".json,image/png,.latent,.safetensors,image/webp,audio/flac", - style: {display: "none"}, - parent: document.body, - onchange: () => { - app.handleFile(fileInput.files[0]); - }, - }); - - this.loadFile = () => fileInput.click(); - - const autoQueueModeEl = toggleSwitch( - "autoQueueMode", - [ - { text: "instant", tooltip: "A new prompt will be queued as soon as the queue reaches 0" }, - { text: "change", tooltip: "A new prompt will be queued when the queue is at 0 and the graph is/has changed" }, - ], - { - onChange: (value) => { - this.autoQueueMode = value.item.value; - }, - } - ); - autoQueueModeEl.style.display = "none"; - - api.addEventListener("graphChanged", () => { - if (this.autoQueueMode === "change" && this.autoQueueEnabled === true) { - if (this.lastQueueSize === 0) { - this.graphHasChanged = false; - app.queuePrompt(0, this.batchCount); - } else { - this.graphHasChanged = true; - } - } - }); - - this.menuHamburger = $el( - "div.comfy-menu-hamburger", - { - parent: document.body, - onclick: () => { - this.menuContainer.style.display = "block"; - this.menuHamburger.style.display = "none"; - }, - }, - [$el("div"), $el("div"), $el("div")] - ); - - this.menuContainer = $el("div.comfy-menu", { parent: document.body }, [ - $el("div.drag-handle.comfy-menu-header", { - style: { - overflow: "hidden", - position: "relative", - width: "100%", - cursor: "default" - } - }, [ - $el("span.drag-handle"), - $el("span.comfy-menu-queue-size", { $: (q) => (this.queueSize = q) }), - $el("div.comfy-menu-actions", [ - $el("button.comfy-settings-btn", { - textContent: "⚙️", - onclick: () => this.settings.show(), - }), - $el("button.comfy-close-menu-btn", { - textContent: "\u00d7", - onclick: () => { - this.menuContainer.style.display = "none"; - this.menuHamburger.style.display = "flex"; - }, - }), - ]), - ]), - $el("button.comfy-queue-btn", { - id: "queue-button", - textContent: "Queue Prompt", - onclick: () => app.queuePrompt(0, this.batchCount), - }), - $el("div", {}, [ - $el("label", {innerHTML: "Extra options"}, [ - $el("input", { - type: "checkbox", - onchange: (i) => { - document.getElementById("extraOptions").style.display = i.srcElement.checked ? "block" : "none"; - this.batchCount = i.srcElement.checked ? document.getElementById("batchCountInputRange").value : 1; - document.getElementById("autoQueueCheckbox").checked = false; - this.autoQueueEnabled = false; - }, - }), - ]), - ]), - $el("div", {id: "extraOptions", style: {width: "100%", display: "none"}}, [ - $el("div",[ - - $el("label", {innerHTML: "Batch count"}), - $el("input", { - id: "batchCountInputNumber", - type: "number", - value: this.batchCount, - min: "1", - style: {width: "35%", "margin-left": "0.4em"}, - oninput: (i) => { - this.batchCount = i.target.value; - document.getElementById("batchCountInputRange").value = this.batchCount; - }, - }), - $el("input", { - id: "batchCountInputRange", - type: "range", - min: "1", - max: "100", - value: this.batchCount, - oninput: (i) => { - this.batchCount = i.srcElement.value; - document.getElementById("batchCountInputNumber").value = i.srcElement.value; - }, - }), - ]), - $el("div",[ - $el("label",{ - for:"autoQueueCheckbox", - innerHTML: "Auto Queue" - }), - $el("input", { - id: "autoQueueCheckbox", - type: "checkbox", - checked: false, - title: "Automatically queue prompt when the queue size hits 0", - onchange: (e) => { - this.autoQueueEnabled = e.target.checked; - autoQueueModeEl.style.display = this.autoQueueEnabled ? "" : "none"; - } - }), - autoQueueModeEl - ]) - ]), - $el("div.comfy-menu-btns", [ - $el("button", { - id: "queue-front-button", - textContent: "Queue Front", - onclick: () => app.queuePrompt(-1, this.batchCount) - }), - $el("button", { - $: (b) => (this.queue.button = b), - id: "comfy-view-queue-button", - textContent: "View Queue", - onclick: () => { - this.history.hide(); - this.queue.toggle(); - }, - }), - $el("button", { - $: (b) => (this.history.button = b), - id: "comfy-view-history-button", - textContent: "View History", - onclick: () => { - this.queue.hide(); - this.history.toggle(); - }, - }), - ]), - this.queue.element, - this.history.element, - $el("button", { - id: "comfy-save-button", - textContent: "Save", - onclick: () => { - let filename = "workflow.json"; - if (promptFilename.value) { - filename = prompt("Save workflow as:", filename); - if (!filename) return; - if (!filename.toLowerCase().endsWith(".json")) { - filename += ".json"; - } - } - app.graphToPrompt().then(p=>{ - const json = JSON.stringify(p.workflow, null, 2); // convert the data to a JSON string - const blob = new Blob([json], {type: "application/json"}); - const url = URL.createObjectURL(blob); - const a = $el("a", { - href: url, - download: filename, - style: {display: "none"}, - parent: document.body, - }); - a.click(); - setTimeout(function () { - a.remove(); - window.URL.revokeObjectURL(url); - }, 0); - }); - }, - }), - $el("button", { - id: "comfy-dev-save-api-button", - textContent: "Save (API Format)", - style: {width: "100%", display: "none"}, - onclick: () => { - let filename = "workflow_api.json"; - if (promptFilename.value) { - filename = prompt("Save workflow (API) as:", filename); - if (!filename) return; - if (!filename.toLowerCase().endsWith(".json")) { - filename += ".json"; - } - } - app.graphToPrompt().then(p=>{ - const json = JSON.stringify(p.output, null, 2); // convert the data to a JSON string - const blob = new Blob([json], {type: "application/json"}); - const url = URL.createObjectURL(blob); - const a = $el("a", { - href: url, - download: filename, - style: {display: "none"}, - parent: document.body, - }); - a.click(); - setTimeout(function () { - a.remove(); - window.URL.revokeObjectURL(url); - }, 0); - }); - }, - }), - $el("button", {id: "comfy-load-button", textContent: "Load", onclick: () => fileInput.click()}), - $el("button", { - id: "comfy-refresh-button", - textContent: "Refresh", - onclick: () => app.refreshComboInNodes() - }), - $el("button", {id: "comfy-clipspace-button", textContent: "Clipspace", onclick: () => app.openClipspace()}), - $el("button", { - id: "comfy-clear-button", textContent: "Clear", onclick: () => { - if (!confirmClear.value || confirm("Clear workflow?")) { - app.clean(); - app.graph.clear(); - app.resetView(); - } - } - }), - $el("button", { - id: "comfy-load-default-button", textContent: "Load Default", onclick: async () => { - if (!confirmClear.value || confirm("Load default workflow?")) { - app.resetView(); - await app.loadGraphData() - } - } - }), - $el("button", { - id: "comfy-reset-view-button", textContent: "Reset View", onclick: async () => { - app.resetView(); - } - }), - ]); - - const devMode = this.settings.addSetting({ - id: "Comfy.DevMode", - name: "Enable Dev mode Options", - type: "boolean", - defaultValue: false, - onChange: function(value) { document.getElementById("comfy-dev-save-api-button").style.display = value ? "flex" : "none"}, - }); - - this.restoreMenuPosition = dragElement(this.menuContainer, this.settings); - - this.setStatus({exec_info: {queue_remaining: "X"}}); - } - - setStatus(status) { - this.queueSize.textContent = "Queue size: " + (status ? status.exec_info.queue_remaining : "ERR"); - if (status) { - if ( - this.lastQueueSize != 0 && - status.exec_info.queue_remaining == 0 && - this.autoQueueEnabled && - (this.autoQueueMode === "instant" || this.graphHasChanged) && - !app.lastExecutionError - ) { - app.queuePrompt(0, this.batchCount); - status.exec_info.queue_remaining += this.batchCount; - this.graphHasChanged = false; - } - this.lastQueueSize = status.exec_info.queue_remaining; - } - } -} diff --git a/web/scripts/ui/components/asyncDialog.js b/web/scripts/ui/components/asyncDialog.js deleted file mode 100644 index 434ce4b30..000000000 --- a/web/scripts/ui/components/asyncDialog.js +++ /dev/null @@ -1,64 +0,0 @@ -import { ComfyDialog } from "../dialog.js"; -import { $el } from "../../ui.js"; - -export class ComfyAsyncDialog extends ComfyDialog { - #resolve; - - constructor(actions) { - super( - "dialog.comfy-dialog.comfyui-dialog", - actions?.map((opt) => { - if (typeof opt === "string") { - opt = { text: opt }; - } - return $el("button.comfyui-button", { - type: "button", - textContent: opt.text, - onclick: () => this.close(opt.value ?? opt.text), - }); - }) - ); - } - - show(html) { - this.element.addEventListener("close", () => { - this.close(); - }); - - super.show(html); - - return new Promise((resolve) => { - this.#resolve = resolve; - }); - } - - showModal(html) { - this.element.addEventListener("close", () => { - this.close(); - }); - - super.show(html); - this.element.showModal(); - - return new Promise((resolve) => { - this.#resolve = resolve; - }); - } - - close(result = null) { - this.#resolve(result); - this.element.close(); - super.close(); - } - - static async prompt({ title = null, message, actions }) { - const dialog = new ComfyAsyncDialog(actions); - const content = [$el("span", message)]; - if (title) { - content.unshift($el("h3", title)); - } - const res = await dialog.showModal(content); - dialog.element.remove(); - return res; - } -} diff --git a/web/scripts/ui/components/button.js b/web/scripts/ui/components/button.js deleted file mode 100644 index 25e5aeebd..000000000 --- a/web/scripts/ui/components/button.js +++ /dev/null @@ -1,163 +0,0 @@ -// @ts-check - -import { $el } from "../../ui.js"; -import { applyClasses, toggleElement } from "../utils.js"; -import { prop } from "../../utils.js"; - -/** - * @typedef {{ - * icon?: string; - * overIcon?: string; - * iconSize?: number; - * content?: string | HTMLElement; - * tooltip?: string; - * enabled?: boolean; - * action?: (e: Event, btn: ComfyButton) => void, - * classList?: import("../utils.js").ClassList, - * visibilitySetting?: { id: string, showValue: any }, - * app?: import("../../app.js").ComfyApp - * }} ComfyButtonProps - */ -export class ComfyButton { - #over = 0; - #popupOpen = false; - isOver = false; - iconElement = $el("i.mdi"); - contentElement = $el("span"); - /** - * @type {import("./popup.js").ComfyPopup} - */ - popup; - - /** - * @param {ComfyButtonProps} opts - */ - constructor({ - icon, - overIcon, - iconSize, - content, - tooltip, - action, - classList = "comfyui-button", - visibilitySetting, - app, - enabled = true, - }) { - this.element = $el("button", { - onmouseenter: () => { - this.isOver = true; - if(this.overIcon) { - this.updateIcon(); - } - }, - onmouseleave: () => { - this.isOver = false; - if(this.overIcon) { - this.updateIcon(); - } - } - - }, [this.iconElement, this.contentElement]); - - this.icon = prop(this, "icon", icon, toggleElement(this.iconElement, { onShow: this.updateIcon })); - this.overIcon = prop(this, "overIcon", overIcon, () => { - if(this.isOver) { - this.updateIcon(); - } - }); - this.iconSize = prop(this, "iconSize", iconSize, this.updateIcon); - this.content = prop( - this, - "content", - content, - toggleElement(this.contentElement, { - onShow: (el, v) => { - if (typeof v === "string") { - el.textContent = v; - } else { - el.replaceChildren(v); - } - }, - }) - ); - - this.tooltip = prop(this, "tooltip", tooltip, (v) => { - if (v) { - this.element.title = v; - } else { - this.element.removeAttribute("title"); - } - }); - this.classList = prop(this, "classList", classList, this.updateClasses); - this.hidden = prop(this, "hidden", false, this.updateClasses); - this.enabled = prop(this, "enabled", enabled, () => { - this.updateClasses(); - this.element.disabled = !this.enabled; - }); - this.action = prop(this, "action", action); - this.element.addEventListener("click", (e) => { - if (this.popup) { - // we are either a touch device or triggered by click not hover - if (!this.#over) { - this.popup.toggle(); - } - } - this.action?.(e, this); - }); - - if (visibilitySetting?.id) { - const settingUpdated = () => { - this.hidden = app.ui.settings.getSettingValue(visibilitySetting.id) !== visibilitySetting.showValue; - }; - app.ui.settings.addEventListener(visibilitySetting.id + ".change", settingUpdated); - settingUpdated(); - } - } - - updateIcon = () => (this.iconElement.className = `mdi mdi-${(this.isOver && this.overIcon) || this.icon}${this.iconSize ? " mdi-" + this.iconSize + "px" : ""}`); - updateClasses = () => { - const internalClasses = []; - if (this.hidden) { - internalClasses.push("hidden"); - } - if (!this.enabled) { - internalClasses.push("disabled"); - } - if (this.popup) { - if (this.#popupOpen) { - internalClasses.push("popup-open"); - } else { - internalClasses.push("popup-closed"); - } - } - applyClasses(this.element, this.classList, ...internalClasses); - }; - - /** - * - * @param { import("./popup.js").ComfyPopup } popup - * @param { "click" | "hover" } mode - */ - withPopup(popup, mode = "click") { - this.popup = popup; - - if (mode === "hover") { - for (const el of [this.element, this.popup.element]) { - el.addEventListener("mouseenter", () => { - this.popup.open = !!++this.#over; - }); - el.addEventListener("mouseleave", () => { - this.popup.open = !!--this.#over; - }); - } - } - - popup.addEventListener("change", () => { - this.#popupOpen = popup.open; - this.updateClasses(); - }); - - return this; - } -} diff --git a/web/scripts/ui/components/buttonGroup.js b/web/scripts/ui/components/buttonGroup.js deleted file mode 100644 index 573572fd0..000000000 --- a/web/scripts/ui/components/buttonGroup.js +++ /dev/null @@ -1,45 +0,0 @@ -// @ts-check - -import { $el } from "../../ui.js"; -import { ComfyButton } from "./button.js"; -import { prop } from "../../utils.js"; - -export class ComfyButtonGroup { - element = $el("div.comfyui-button-group"); - - /** @param {Array} buttons */ - constructor(...buttons) { - this.buttons = prop(this, "buttons", buttons, () => this.update()); - } - - /** - * @param {ComfyButton} button - * @param {number} index - */ - insert(button, index) { - this.buttons.splice(index, 0, button); - this.update(); - } - - /** @param {ComfyButton} button */ - append(button) { - this.buttons.push(button); - this.update(); - } - - /** @param {ComfyButton|number} indexOrButton */ - remove(indexOrButton) { - if (typeof indexOrButton !== "number") { - indexOrButton = this.buttons.indexOf(indexOrButton); - } - if (indexOrButton > -1) { - const r = this.buttons.splice(indexOrButton, 1); - this.update(); - return r; - } - } - - update() { - this.element.replaceChildren(...this.buttons.map((b) => b["element"] ?? b)); - } -} diff --git a/web/scripts/ui/components/popup.js b/web/scripts/ui/components/popup.js deleted file mode 100644 index ee59b35d9..000000000 --- a/web/scripts/ui/components/popup.js +++ /dev/null @@ -1,128 +0,0 @@ -// @ts-check - -import { prop } from "../../utils.js"; -import { $el } from "../../ui.js"; -import { applyClasses } from "../utils.js"; - -export class ComfyPopup extends EventTarget { - element = $el("div.comfyui-popup"); - - /** - * @param {{ - * target: HTMLElement, - * container?: HTMLElement, - * classList?: import("../utils.js").ClassList, - * ignoreTarget?: boolean, - * closeOnEscape?: boolean, - * position?: "absolute" | "relative", - * horizontal?: "left" | "right" - * }} param0 - * @param {...HTMLElement} children - */ - constructor( - { - target, - container = document.body, - classList = "", - ignoreTarget = true, - closeOnEscape = true, - position = "absolute", - horizontal = "left", - }, - ...children - ) { - super(); - this.target = target; - this.ignoreTarget = ignoreTarget; - this.container = container; - this.position = position; - this.closeOnEscape = closeOnEscape; - this.horizontal = horizontal; - - container.append(this.element); - - this.children = prop(this, "children", children, () => { - this.element.replaceChildren(...this.children); - this.update(); - }); - this.classList = prop(this, "classList", classList, () => applyClasses(this.element, this.classList, "comfyui-popup", horizontal)); - this.open = prop(this, "open", false, (v, o) => { - if (v === o) return; - if (v) { - this.#show(); - } else { - this.#hide(); - } - }); - } - - toggle() { - this.open = !this.open; - } - - #hide() { - this.element.classList.remove("open"); - window.removeEventListener("resize", this.update); - window.removeEventListener("click", this.#clickHandler, { capture: true }); - window.removeEventListener("keydown", this.#escHandler, { capture: true }); - - this.dispatchEvent(new CustomEvent("close")); - this.dispatchEvent(new CustomEvent("change")); - } - - #show() { - this.element.classList.add("open"); - this.update(); - - window.addEventListener("resize", this.update); - window.addEventListener("click", this.#clickHandler, { capture: true }); - if (this.closeOnEscape) { - window.addEventListener("keydown", this.#escHandler, { capture: true }); - } - - this.dispatchEvent(new CustomEvent("open")); - this.dispatchEvent(new CustomEvent("change")); - } - - #escHandler = (e) => { - if (e.key === "Escape") { - this.open = false; - e.preventDefault(); - e.stopImmediatePropagation(); - } - }; - - #clickHandler = (e) => { - /** @type {any} */ - const target = e.target; - if (!this.element.contains(target) && this.ignoreTarget && !this.target.contains(target)) { - this.open = false; - } - }; - - update = () => { - const rect = this.target.getBoundingClientRect(); - this.element.style.setProperty("--bottom", "unset"); - if (this.position === "absolute") { - if (this.horizontal === "left") { - this.element.style.setProperty("--left", rect.left + "px"); - } else { - this.element.style.setProperty("--left", rect.right - this.element.clientWidth + "px"); - } - this.element.style.setProperty("--top", rect.bottom + "px"); - this.element.style.setProperty("--limit", rect.bottom + "px"); - } else { - this.element.style.setProperty("--left", 0 + "px"); - this.element.style.setProperty("--top", rect.height + "px"); - this.element.style.setProperty("--limit", rect.height + "px"); - } - - const thisRect = this.element.getBoundingClientRect(); - if (thisRect.height < 30) { - // Move up instead - this.element.style.setProperty("--top", "unset"); - this.element.style.setProperty("--bottom", rect.height + 5 + "px"); - this.element.style.setProperty("--limit", rect.height + 5 + "px"); - } - }; -} diff --git a/web/scripts/ui/components/splitButton.js b/web/scripts/ui/components/splitButton.js deleted file mode 100644 index 2b4e6d9f8..000000000 --- a/web/scripts/ui/components/splitButton.js +++ /dev/null @@ -1,43 +0,0 @@ -// @ts-check - -import { $el } from "../../ui.js"; -import { ComfyButton } from "./button.js"; -import { prop } from "../../utils.js"; -import { ComfyPopup } from "./popup.js"; - -export class ComfySplitButton { - /** - * @param {{ - * primary: ComfyButton, - * mode?: "hover" | "click", - * horizontal?: "left" | "right", - * position?: "relative" | "absolute" - * }} param0 - * @param {Array | Array} items - */ - constructor({ primary, mode, horizontal = "left", position = "relative" }, ...items) { - this.arrow = new ComfyButton({ - icon: "chevron-down", - }); - this.element = $el("div.comfyui-split-button" + (mode === "hover" ? ".hover" : ""), [ - $el("div.comfyui-split-primary", primary.element), - $el("div.comfyui-split-arrow", this.arrow.element), - ]); - this.popup = new ComfyPopup({ - target: this.element, - container: position === "relative" ? this.element : document.body, - classList: "comfyui-split-button-popup" + (mode === "hover" ? " hover" : ""), - closeOnEscape: mode === "click", - position, - horizontal, - }); - - this.arrow.withPopup(this.popup, mode); - - this.items = prop(this, "items", items, () => this.update()); - } - - update() { - this.popup.element.replaceChildren(...this.items.map((b) => b.element ?? b)); - } -} diff --git a/web/scripts/ui/dialog.js b/web/scripts/ui/dialog.js deleted file mode 100644 index 803a97a2b..000000000 --- a/web/scripts/ui/dialog.js +++ /dev/null @@ -1,38 +0,0 @@ -import { $el } from "../ui.js"; - -export class ComfyDialog extends EventTarget { - #buttons; - - constructor(type = "div", buttons = null) { - super(); - this.#buttons = buttons; - this.element = $el(type + ".comfy-modal", { parent: document.body }, [ - $el("div.comfy-modal-content", [$el("p", { $: (p) => (this.textElement = p) }), ...this.createButtons()]), - ]); - } - - createButtons() { - return ( - this.#buttons ?? [ - $el("button", { - type: "button", - textContent: "Close", - onclick: () => this.close(), - }), - ] - ); - } - - close() { - this.element.style.display = "none"; - } - - show(html) { - if (typeof html === "string") { - this.textElement.innerHTML = html; - } else { - this.textElement.replaceChildren(...(html instanceof Array ? html : [html])); - } - this.element.style.display = "flex"; - } -} diff --git a/web/scripts/ui/draggableList.js b/web/scripts/ui/draggableList.js deleted file mode 100644 index d53594886..000000000 --- a/web/scripts/ui/draggableList.js +++ /dev/null @@ -1,287 +0,0 @@ -// @ts-check -/* - Original implementation: - https://github.com/TahaSh/drag-to-reorder - MIT License - - Copyright (c) 2023 Taha Shashtari - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -import { $el } from "../ui.js"; - -$el("style", { - parent: document.head, - textContent: ` - .draggable-item { - position: relative; - will-change: transform; - user-select: none; - } - .draggable-item.is-idle { - transition: 0.25s ease transform; - } - .draggable-item.is-draggable { - z-index: 10; - } - ` -}); - -export class DraggableList extends EventTarget { - listContainer; - draggableItem; - pointerStartX; - pointerStartY; - scrollYMax; - itemsGap = 0; - items = []; - itemSelector; - handleClass = "drag-handle"; - off = []; - offDrag = []; - - constructor(element, itemSelector) { - super(); - this.listContainer = element; - this.itemSelector = itemSelector; - - if (!this.listContainer) return; - - this.off.push(this.on(this.listContainer, "mousedown", this.dragStart)); - this.off.push(this.on(this.listContainer, "touchstart", this.dragStart)); - this.off.push(this.on(document, "mouseup", this.dragEnd)); - this.off.push(this.on(document, "touchend", this.dragEnd)); - } - - getAllItems() { - if (!this.items?.length) { - this.items = Array.from(this.listContainer.querySelectorAll(this.itemSelector)); - this.items.forEach((element) => { - element.classList.add("is-idle"); - }); - } - return this.items; - } - - getIdleItems() { - return this.getAllItems().filter((item) => item.classList.contains("is-idle")); - } - - isItemAbove(item) { - return item.hasAttribute("data-is-above"); - } - - isItemToggled(item) { - return item.hasAttribute("data-is-toggled"); - } - - on(source, event, listener, options) { - listener = listener.bind(this); - source.addEventListener(event, listener, options); - return () => source.removeEventListener(event, listener); - } - - dragStart(e) { - if (e.target.classList.contains(this.handleClass)) { - this.draggableItem = e.target.closest(this.itemSelector); - } - - if (!this.draggableItem) return; - - this.pointerStartX = e.clientX || e.touches[0].clientX; - this.pointerStartY = e.clientY || e.touches[0].clientY; - this.scrollYMax = this.listContainer.scrollHeight - this.listContainer.clientHeight; - - this.setItemsGap(); - this.initDraggableItem(); - this.initItemsState(); - - this.offDrag.push(this.on(document, "mousemove", this.drag)); - this.offDrag.push(this.on(document, "touchmove", this.drag, { passive: false })); - - this.dispatchEvent( - new CustomEvent("dragstart", { - detail: { element: this.draggableItem, position: this.getAllItems().indexOf(this.draggableItem) }, - }) - ); - } - - setItemsGap() { - if (this.getIdleItems().length <= 1) { - this.itemsGap = 0; - return; - } - - const item1 = this.getIdleItems()[0]; - const item2 = this.getIdleItems()[1]; - - const item1Rect = item1.getBoundingClientRect(); - const item2Rect = item2.getBoundingClientRect(); - - this.itemsGap = Math.abs(item1Rect.bottom - item2Rect.top); - } - - initItemsState() { - this.getIdleItems().forEach((item, i) => { - if (this.getAllItems().indexOf(this.draggableItem) > i) { - item.dataset.isAbove = ""; - } - }); - } - - initDraggableItem() { - this.draggableItem.classList.remove("is-idle"); - this.draggableItem.classList.add("is-draggable"); - } - - drag(e) { - if (!this.draggableItem) return; - - e.preventDefault(); - - const clientX = e.clientX || e.touches[0].clientX; - const clientY = e.clientY || e.touches[0].clientY; - - const listRect = this.listContainer.getBoundingClientRect(); - - if (clientY > listRect.bottom) { - if (this.listContainer.scrollTop < this.scrollYMax) { - this.listContainer.scrollBy(0, 10); - this.pointerStartY -= 10; - } - } else if (clientY < listRect.top && this.listContainer.scrollTop > 0) { - this.pointerStartY += 10; - this.listContainer.scrollBy(0, -10); - } - - const pointerOffsetX = clientX - this.pointerStartX; - const pointerOffsetY = clientY - this.pointerStartY; - - this.updateIdleItemsStateAndPosition(); - this.draggableItem.style.transform = `translate(${pointerOffsetX}px, ${pointerOffsetY}px)`; - } - - updateIdleItemsStateAndPosition() { - const draggableItemRect = this.draggableItem.getBoundingClientRect(); - const draggableItemY = draggableItemRect.top + draggableItemRect.height / 2; - - // Update state - this.getIdleItems().forEach((item) => { - const itemRect = item.getBoundingClientRect(); - const itemY = itemRect.top + itemRect.height / 2; - if (this.isItemAbove(item)) { - if (draggableItemY <= itemY) { - item.dataset.isToggled = ""; - } else { - delete item.dataset.isToggled; - } - } else { - if (draggableItemY >= itemY) { - item.dataset.isToggled = ""; - } else { - delete item.dataset.isToggled; - } - } - }); - - // Update position - this.getIdleItems().forEach((item) => { - if (this.isItemToggled(item)) { - const direction = this.isItemAbove(item) ? 1 : -1; - item.style.transform = `translateY(${direction * (draggableItemRect.height + this.itemsGap)}px)`; - } else { - item.style.transform = ""; - } - }); - } - - dragEnd() { - if (!this.draggableItem) return; - - this.applyNewItemsOrder(); - this.cleanup(); - } - - applyNewItemsOrder() { - const reorderedItems = []; - - let oldPosition = -1; - this.getAllItems().forEach((item, index) => { - if (item === this.draggableItem) { - oldPosition = index; - return; - } - if (!this.isItemToggled(item)) { - reorderedItems[index] = item; - return; - } - const newIndex = this.isItemAbove(item) ? index + 1 : index - 1; - reorderedItems[newIndex] = item; - }); - - for (let index = 0; index < this.getAllItems().length; index++) { - const item = reorderedItems[index]; - if (typeof item === "undefined") { - reorderedItems[index] = this.draggableItem; - } - } - - reorderedItems.forEach((item) => { - this.listContainer.appendChild(item); - }); - - this.items = reorderedItems; - - this.dispatchEvent( - new CustomEvent("dragend", { - detail: { element: this.draggableItem, oldPosition, newPosition: reorderedItems.indexOf(this.draggableItem) }, - }) - ); - } - - cleanup() { - this.itemsGap = 0; - this.items = []; - this.unsetDraggableItem(); - this.unsetItemState(); - - this.offDrag.forEach((f) => f()); - this.offDrag = []; - } - - unsetDraggableItem() { - this.draggableItem.style = null; - this.draggableItem.classList.remove("is-draggable"); - this.draggableItem.classList.add("is-idle"); - this.draggableItem = null; - } - - unsetItemState() { - this.getIdleItems().forEach((item, i) => { - delete item.dataset.isAbove; - delete item.dataset.isToggled; - item.style.transform = ""; - }); - } - - dispose() { - this.off.forEach((f) => f()); - } -} diff --git a/web/scripts/ui/imagePreview.js b/web/scripts/ui/imagePreview.js deleted file mode 100644 index 2a7f66b8f..000000000 --- a/web/scripts/ui/imagePreview.js +++ /dev/null @@ -1,97 +0,0 @@ -import { $el } from "../ui.js"; - -export function calculateImageGrid(imgs, dw, dh) { - let best = 0; - let w = imgs[0].naturalWidth; - let h = imgs[0].naturalHeight; - const numImages = imgs.length; - - let cellWidth, cellHeight, cols, rows, shiftX; - // compact style - for (let c = 1; c <= numImages; c++) { - const r = Math.ceil(numImages / c); - const cW = dw / c; - const cH = dh / r; - const scaleX = cW / w; - const scaleY = cH / h; - - const scale = Math.min(scaleX, scaleY, 1); - const imageW = w * scale; - const imageH = h * scale; - const area = imageW * imageH * numImages; - - if (area > best) { - best = area; - cellWidth = imageW; - cellHeight = imageH; - cols = c; - rows = r; - shiftX = c * ((cW - imageW) / 2); - } - } - - return { cellWidth, cellHeight, cols, rows, shiftX }; -} - -export function createImageHost(node) { - const el = $el("div.comfy-img-preview"); - let currentImgs; - let first = true; - - function updateSize() { - let w = null; - let h = null; - - if (currentImgs) { - let elH = el.clientHeight; - if (first) { - first = false; - // On first run, if we are small then grow a bit - if (elH < 190) { - elH = 190; - } - el.style.setProperty("--comfy-widget-min-height", elH); - } else { - el.style.setProperty("--comfy-widget-min-height", null); - } - - const nw = node.size[0]; - ({ cellWidth: w, cellHeight: h } = calculateImageGrid(currentImgs, nw - 20, elH)); - w += "px"; - h += "px"; - - el.style.setProperty("--comfy-img-preview-width", w); - el.style.setProperty("--comfy-img-preview-height", h); - } - } - return { - el, - updateImages(imgs) { - if (imgs !== currentImgs) { - if (currentImgs == null) { - requestAnimationFrame(() => { - updateSize(); - }); - } - el.replaceChildren(...imgs); - currentImgs = imgs; - node.onResize(node.size); - node.graph.setDirtyCanvas(true, true); - } - }, - getHeight() { - updateSize(); - }, - onDraw() { - // Element from point uses a hittest find elements so we need to toggle pointer events - el.style.pointerEvents = "all"; - const over = document.elementFromPoint(app.canvas.mouse[0], app.canvas.mouse[1]); - el.style.pointerEvents = "none"; - - if(!over) return; - // Set the overIndex so Open Image etc work - const idx = currentImgs.indexOf(over); - node.overIndex = idx; - }, - }; -} diff --git a/web/scripts/ui/menu/index.js b/web/scripts/ui/menu/index.js deleted file mode 100644 index 1e00b3d22..000000000 --- a/web/scripts/ui/menu/index.js +++ /dev/null @@ -1,302 +0,0 @@ -// @ts-check - -import { $el } from "../../ui.js"; -import { downloadBlob } from "../../utils.js"; -import { ComfyButton } from "../components/button.js"; -import { ComfyButtonGroup } from "../components/buttonGroup.js"; -import { ComfySplitButton } from "../components/splitButton.js"; -import { ComfyViewHistoryButton } from "./viewHistory.js"; -import { ComfyQueueButton } from "./queueButton.js"; -import { ComfyWorkflowsMenu } from "./workflows.js"; -import { ComfyViewQueueButton } from "./viewQueue.js"; -import { getInteruptButton } from "./interruptButton.js"; - -const collapseOnMobile = (t) => { - (t.element ?? t).classList.add("comfyui-menu-mobile-collapse"); - return t; -}; -const showOnMobile = (t) => { - (t.element ?? t).classList.add("lt-lg-show"); - return t; -}; - -export class ComfyAppMenu { - #sizeBreak = "lg"; - #lastSizeBreaks = { - lg: null, - md: null, - sm: null, - xs: null, - }; - #sizeBreaks = Object.keys(this.#lastSizeBreaks); - #cachedInnerSize = null; - #cacheTimeout = null; - - /** - * @param { import("../../app.js").ComfyApp } app - */ - constructor(app) { - this.app = app; - - this.workflows = new ComfyWorkflowsMenu(app); - const getSaveButton = (t) => - new ComfyButton({ - icon: "content-save", - tooltip: "Save the current workflow", - action: () => app.workflowManager.activeWorkflow.save(), - content: t, - }); - - this.logo = $el("h1.comfyui-logo.nlg-hide", { title: "ComfyUI" }, "ComfyUI"); - this.saveButton = new ComfySplitButton( - { - primary: getSaveButton(), - mode: "hover", - position: "absolute", - }, - getSaveButton("Save"), - new ComfyButton({ - icon: "content-save-edit", - content: "Save As", - tooltip: "Save the current graph as a new workflow", - action: () => app.workflowManager.activeWorkflow.save(true), - }), - new ComfyButton({ - icon: "download", - content: "Export", - tooltip: "Export the current workflow as JSON", - action: () => this.exportWorkflow("workflow", "workflow"), - }), - new ComfyButton({ - icon: "api", - content: "Export (API Format)", - tooltip: "Export the current workflow as JSON for use with the ComfyUI API", - action: () => this.exportWorkflow("workflow_api", "output"), - visibilitySetting: { id: "Comfy.DevMode", showValue: true }, - app, - }) - ); - this.actionsGroup = new ComfyButtonGroup( - new ComfyButton({ - icon: "refresh", - content: "Refresh", - tooltip: "Refresh widgets in nodes to find new models or files", - action: () => app.refreshComboInNodes(), - }), - new ComfyButton({ - icon: "clipboard-edit-outline", - content: "Clipspace", - tooltip: "Open Clipspace window", - action: () => app["openClipspace"](), - }), - new ComfyButton({ - icon: "fit-to-page-outline", - content: "Reset View", - tooltip: "Reset the canvas view", - action: () => app.resetView(), - }), - new ComfyButton({ - icon: "cancel", - content: "Clear", - tooltip: "Clears current workflow", - action: () => { - if (!app.ui.settings.getSettingValue("Comfy.ConfirmClear", true) || confirm("Clear workflow?")) { - app.clean(); - app.graph.clear(); - } - }, - }) - ); - this.settingsGroup = new ComfyButtonGroup( - new ComfyButton({ - icon: "cog", - content: "Settings", - tooltip: "Open settings", - action: () => { - app.ui.settings.show(); - }, - }) - ); - this.viewGroup = new ComfyButtonGroup( - new ComfyViewHistoryButton(app).element, - new ComfyViewQueueButton(app).element, - getInteruptButton("nlg-hide").element - ); - this.mobileMenuButton = new ComfyButton({ - icon: "menu", - action: (_, btn) => { - btn.icon = this.element.classList.toggle("expanded") ? "menu-open" : "menu"; - window.dispatchEvent(new Event("resize")); - }, - classList: "comfyui-button comfyui-menu-button", - }); - - this.element = $el("nav.comfyui-menu.lg", { style: { display: "none" } }, [ - this.logo, - this.workflows.element, - this.saveButton.element, - collapseOnMobile(this.actionsGroup).element, - $el("section.comfyui-menu-push"), - collapseOnMobile(this.settingsGroup).element, - collapseOnMobile(this.viewGroup).element, - - getInteruptButton("lt-lg-show").element, - new ComfyQueueButton(app).element, - showOnMobile(this.mobileMenuButton).element, - ]); - - let resizeHandler; - this.menuPositionSetting = app.ui.settings.addSetting({ - id: "Comfy.UseNewMenu", - defaultValue: "Disabled", - name: "[Beta] Use new menu and workflow management. Note: On small screens the menu will always be at the top.", - type: "combo", - options: ["Disabled", "Top", "Bottom"], - onChange: async (v) => { - if (v && v !== "Disabled") { - if (!resizeHandler) { - resizeHandler = () => { - this.calculateSizeBreak(); - }; - window.addEventListener("resize", resizeHandler); - } - this.updatePosition(v); - } else { - if (resizeHandler) { - window.removeEventListener("resize", resizeHandler); - resizeHandler = null; - } - document.body.style.removeProperty("display"); - app.ui.menuContainer.style.removeProperty("display"); - this.element.style.display = "none"; - app.ui.restoreMenuPosition(); - } - window.dispatchEvent(new Event("resize")); - }, - }); - } - - updatePosition(v) { - document.body.style.display = "grid"; - this.app.ui.menuContainer.style.display = "none"; - this.element.style.removeProperty("display"); - this.position = v; - if (v === "Bottom") { - this.app.bodyBottom.append(this.element); - } else { - this.app.bodyTop.prepend(this.element); - } - this.calculateSizeBreak(); - } - - updateSizeBreak(idx, prevIdx, direction) { - const newSize = this.#sizeBreaks[idx]; - if (newSize === this.#sizeBreak) return; - this.#cachedInnerSize = null; - clearTimeout(this.#cacheTimeout); - - this.#sizeBreak = this.#sizeBreaks[idx]; - for (let i = 0; i < this.#sizeBreaks.length; i++) { - const sz = this.#sizeBreaks[i]; - if (sz === this.#sizeBreak) { - this.element.classList.add(sz); - } else { - this.element.classList.remove(sz); - } - if (i < idx) { - this.element.classList.add("lt-" + sz); - } else { - this.element.classList.remove("lt-" + sz); - } - } - - if (idx) { - // We're on a small screen, force the menu at the top - if (this.position !== "Top") { - this.updatePosition("Top"); - } - } else if (this.position != this.menuPositionSetting.value) { - // Restore user position - this.updatePosition(this.menuPositionSetting.value); - } - - // Allow multiple updates, but prevent bouncing - if (!direction) { - direction = prevIdx - idx; - } else if (direction != prevIdx - idx) { - return; - } - this.calculateSizeBreak(direction); - } - - calculateSizeBreak(direction = 0) { - let idx = this.#sizeBreaks.indexOf(this.#sizeBreak); - const currIdx = idx; - const innerSize = this.calculateInnerSize(idx); - if (window.innerWidth >= this.#lastSizeBreaks[this.#sizeBreaks[idx - 1]]) { - if (idx > 0) { - idx--; - } - } else if (innerSize > this.element.clientWidth) { - this.#lastSizeBreaks[this.#sizeBreak] = Math.max(window.innerWidth, innerSize); - // We need to shrink - if (idx < this.#sizeBreaks.length - 1) { - idx++; - } - } - - this.updateSizeBreak(idx, currIdx, direction); - } - - calculateInnerSize(idx) { - // Cache the inner size to prevent too much calculation when resizing the window - clearTimeout(this.#cacheTimeout); - if (this.#cachedInnerSize) { - // Extend cache time - this.#cacheTimeout = setTimeout(() => (this.#cachedInnerSize = null), 100); - } else { - let innerSize = 0; - let count = 1; - for (const c of this.element.children) { - if (c.classList.contains("comfyui-menu-push")) continue; // ignore right push - if (idx && c.classList.contains("comfyui-menu-mobile-collapse")) continue; // ignore collapse items - innerSize += c.clientWidth; - count++; - } - innerSize += 8 * count; - this.#cachedInnerSize = innerSize; - this.#cacheTimeout = setTimeout(() => (this.#cachedInnerSize = null), 100); - } - return this.#cachedInnerSize; - } - - /** - * @param {string} defaultName - */ - getFilename(defaultName) { - if (this.app.ui.settings.getSettingValue("Comfy.PromptFilename", true)) { - defaultName = prompt("Save workflow as:", defaultName); - if (!defaultName) return; - if (!defaultName.toLowerCase().endsWith(".json")) { - defaultName += ".json"; - } - } - return defaultName; - } - - /** - * @param {string} [filename] - * @param { "workflow" | "output" } [promptProperty] - */ - async exportWorkflow(filename, promptProperty) { - if (this.app.workflowManager.activeWorkflow?.path) { - filename = this.app.workflowManager.activeWorkflow.name; - } - const p = await this.app.graphToPrompt(); - const json = JSON.stringify(p[promptProperty], null, 2); - const blob = new Blob([json], { type: "application/json" }); - const file = this.getFilename(filename); - if (!file) return; - downloadBlob(file, blob); - } -} diff --git a/web/scripts/ui/menu/interruptButton.js b/web/scripts/ui/menu/interruptButton.js deleted file mode 100644 index 4db3328db..000000000 --- a/web/scripts/ui/menu/interruptButton.js +++ /dev/null @@ -1,23 +0,0 @@ -// @ts-check - -import { api } from "../../api.js"; -import { ComfyButton } from "../components/button.js"; - -export function getInteruptButton(visibility) { - const btn = new ComfyButton({ - icon: "close", - tooltip: "Cancel current generation", - enabled: false, - action: () => { - api.interrupt(); - }, - classList: ["comfyui-button", "comfyui-interrupt-button", visibility], - }); - - api.addEventListener("status", ({ detail }) => { - const sz = detail?.exec_info?.queue_remaining; - btn.enabled = sz > 0; - }); - - return btn; -} diff --git a/web/scripts/ui/menu/menu.css b/web/scripts/ui/menu/menu.css deleted file mode 100644 index 1d8b9ad0a..000000000 --- a/web/scripts/ui/menu/menu.css +++ /dev/null @@ -1,710 +0,0 @@ -.relative { - position: relative; -} -.hidden { - display: none !important; -} -.mdi.rotate270::before { - transform: rotate(270deg); -} - -/* Generic */ -.comfyui-button { - display: flex; - align-items: center; - gap: 0.5em; - cursor: pointer; - border: none; - border-radius: 4px; - padding: 4px 8px; - box-sizing: border-box; - margin: 0; - transition: box-shadow 0.1s; -} - -.comfyui-button:active { - box-shadow: inset 1px 1px 10px rgba(0, 0, 0, 0.5); -} -.comfyui-button:disabled { - opacity: 0.5; - cursor: not-allowed; -} -.primary .comfyui-button, -.primary.comfyui-button { - background-color: var(--primary-bg) !important; - color: var(--primary-fg) !important; -} - -.primary .comfyui-button:not(:disabled):hover, -.primary.comfyui-button:not(:disabled):hover { - background-color: var(--primary-hover-bg) !important; - color: var(--primary-hover-fg) !important; -} - -/* Popup */ -.comfyui-popup { - position: absolute; - left: var(--left); - right: var(--right); - top: var(--top); - bottom: var(--bottom); - z-index: 2000; - max-height: calc(100vh - var(--limit) - 10px); - box-shadow: 3px 3px 5px 0px rgba(0, 0, 0, 0.3); -} - -.comfyui-popup:not(.open) { - display: none; -} - -.comfyui-popup.right.open { - border-top-left-radius: 4px; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; - overflow: hidden; -} -/* Split button */ -.comfyui-split-button { - position: relative; - display: flex; -} - -.comfyui-split-primary { - flex: auto; -} - -.comfyui-split-primary .comfyui-button { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-right: 1px solid var(--comfy-menu-bg); - width: 100%; -} - -.comfyui-split-arrow .comfyui-button { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - padding-left: 2px; - padding-right: 2px; -} - -.comfyui-split-button-popup { - white-space: nowrap; - background-color: var(--content-bg); - color: var(--content-fg); - display: flex; - flex-direction: column; - overflow: auto; -} - -.comfyui-split-button-popup.hover { - z-index: 2001; -} -.comfyui-split-button-popup > .comfyui-button { - border: none; - background-color: transparent; - color: var(--fg-color); - padding: 8px 12px 8px 8px; -} - -.comfyui-split-button-popup > .comfyui-button:not(:disabled):hover { - background-color: var(--comfy-input-bg); -} - -/* Button group */ -.comfyui-button-group { - display: flex; - border-radius: 4px; - overflow: hidden; -} - -.comfyui-button-group > .comfyui-button, -.comfyui-button-group > .comfyui-button-wrapper > .comfyui-button { - padding: 4px 10px; - border-radius: 0; -} - -/* Menu */ -.comfyui-menu { - width: 100vw; - background: var(--comfy-menu-bg); - color: var(--fg-color); - font-family: Arial, Helvetica, sans-serif; - font-size: 0.8em; - display: flex; - padding: 4px 8px; - align-items: center; - gap: 8px; - box-sizing: border-box; - z-index: 1000; - order: 0; - grid-column: 1/-1; - overflow: auto; - max-height: 90vh; -} - -.comfyui-menu>* { - flex-shrink: 0; -} -.comfyui-menu .mdi::before { - font-size: 18px; -} - -.comfyui-menu .comfyui-button { - background: var(--comfy-input-bg); - color: var(--fg-color); - white-space: nowrap; -} - -.comfyui-menu .comfyui-button:not(:disabled):hover { - background: var(--border-color); - color: var(--content-fg); -} - -.comfyui-menu .comfyui-split-button-popup > .comfyui-button { - border-radius: 0; - background-color: transparent; -} - -.comfyui-menu .comfyui-split-button-popup > .comfyui-button:not(:disabled):hover { - background-color: var(--comfy-input-bg); -} - -.comfyui-menu .comfyui-split-button-popup.left { - border-top-right-radius: 4px; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; -} - -.comfyui-menu .comfyui-button.popup-open { - background-color: var(--content-bg); - color: var(--content-fg); -} - -.comfyui-menu-push { - margin-left: -0.8em; - flex: auto; -} - -.comfyui-logo { - font-size: 1.2em; - margin: 0; - user-select: none; - cursor: default; -} - -/* Workflows */ -.comfyui-workflows-button { - flex-direction: row-reverse; - max-width: 200px; - position: relative; - z-index: 0; -} - -.comfyui-workflows-button.popup-open { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} -.comfyui-workflows-button.unsaved { - font-style: italic; -} -.comfyui-workflows-button-progress { - position: absolute; - top: 0; - left: 0; - background-color: green; - height: 100%; - border-radius: 4px; - z-index: -1; -} - -.comfyui-workflows-button > span { - flex: auto; - text-align: left; - overflow: hidden; -} -.comfyui-workflows-button-inner { - display: flex; - align-items: center; - gap: 7px; - width: 150px; -} -.comfyui-workflows-label { - overflow: hidden; - text-overflow: ellipsis; - direction: rtl; - flex: auto; - position: relative; -} - -.comfyui-workflows-button.unsaved .comfyui-workflows-label { - padding-left: 8px; -} - -.comfyui-workflows-button.unsaved .comfyui-workflows-label:after { - content: "*"; - position: absolute; - top: 0; - left: 0; -} -.comfyui-workflows-button-inner .mdi-graph::before { - transform: rotate(-90deg); -} - -.comfyui-workflows-popup { - font-family: Arial, Helvetica, sans-serif; - font-size: 0.8em; - padding: 10px; - overflow: auto; - background-color: var(--content-bg); - color: var(--content-fg); - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; - z-index: 400; -} - -.comfyui-workflows-panel { - min-height: 150px; -} - -.comfyui-workflows-panel .lds-ring { - transform: translate(-50%); - position: absolute; - left: 50%; - top: 75px; -} - -.comfyui-workflows-panel h3 { - margin: 10px 0 10px 0; - font-size: 11px; - opacity: 0.8; -} - -.comfyui-workflows-panel section header { - display: flex; - justify-content: space-between; - align-items: center; -} -.comfy-ui-workflows-search .mdi { - position: relative; - top: 2px; - pointer-events: none; -} -.comfy-ui-workflows-search input { - background-color: var(--comfy-input-bg); - color: var(--input-text); - border: none; - border-radius: 4px; - padding: 4px 10px; - margin-left: -24px; - text-indent: 18px; -} -.comfy-ui-workflows-search input:placeholder-shown { - width: 10px; -} -.comfy-ui-workflows-search input:placeholder-shown:focus { - width: auto; -} -.comfyui-workflows-actions { - display: flex; - gap: 10px; - margin-bottom: 10px; -} - -.comfyui-workflows-actions .comfyui-button { - background: var(--comfy-input-bg); - color: var(--input-text); -} - -.comfyui-workflows-actions .comfyui-button:not(:disabled):hover { - background: var(--primary-bg); - color: var(--primary-fg); -} - -.comfyui-workflows-favorites, -.comfyui-workflows-open { - border-bottom: 1px solid var(--comfy-input-bg); - padding-bottom: 5px; - margin-bottom: 5px; -} - -.comfyui-workflows-open .active { - font-weight: bold; - color: var(--primary-fg); -} - -.comfyui-workflows-favorites:empty { - display: none; -} - -.comfyui-workflows-tree { - padding: 0; - margin: 0; -} - -.comfyui-workflows-tree:empty::after { - content: "No saved workflows"; - display: block; - text-align: center; -} -.comfyui-workflows-tree > ul { - padding: 0; -} - -.comfyui-workflows-tree > ul ul { - margin: 0; - padding: 0 0 0 25px; -} - -.comfyui-workflows-tree:not(.filtered) .closed > ul { - display: none; -} - -.comfyui-workflows-tree li, -.comfyui-workflows-tree-file { - --item-height: 32px; - list-style-type: none; - height: var(--item-height); - display: flex; - align-items: center; - gap: 5px; - cursor: pointer; - user-select: none; -} - -.comfyui-workflows-tree-file.active::before, -.comfyui-workflows-tree li:hover::before, -.comfyui-workflows-tree-file:hover::before { - content: ""; - position: absolute; - width: 100%; - left: 0; - height: var(--item-height); - background-color: var(--content-hover-bg); - color: var(--content-hover-fg); - z-index: -1; -} - -.comfyui-workflows-tree-file.active::before { - background-color: var(--primary-bg); - color: var(--primary-fg); -} - -.comfyui-workflows-tree-file.running:not(:hover)::before { - content: ""; - position: absolute; - width: var(--progress, 0); - left: 0; - height: var(--item-height); - background-color: green; - z-index: -1; -} - -.comfyui-workflows-tree-file.unsaved span { - font-style: italic; -} - -.comfyui-workflows-tree-file span { - flex: auto; -} - -.comfyui-workflows-tree-file span + .comfyui-workflows-file-action { - margin-left: 10px; -} - -.comfyui-workflows-tree-file .comfyui-workflows-file-action { - background-color: transparent; - color: var(--fg-color); - padding: 2px 4px; -} - -.comfyui-workflows-tree-file.active .comfyui-workflows-file-action { - color: var(--primary-fg); -} - -.lg ~ .comfyui-workflows-popup .comfyui-workflows-tree-file:not(:hover) .comfyui-workflows-file-action { - opacity: 0; -} - -.comfyui-workflows-tree-file .comfyui-workflows-file-action:hover { - background-color: var(--primary-bg); - color: var(--primary-fg); -} - -.comfyui-workflows-tree-file .comfyui-workflows-file-action-primary { - background-color: transparent; - color: var(--fg-color); - padding: 2px 4px; - margin: 0 -4px; -} - -.comfyui-workflows-file-action-favorite .mdi-star { - color: orange; -} - -/* View List */ -.comfyui-view-list-popup { - padding: 10px; - background-color: var(--content-bg); - color: var(--content-fg); - min-width: 170px; - min-height: 435px; - display: flex; - flex-direction: column; - align-items: center; - box-sizing: border-box; -} -.comfyui-view-list-popup h3 { - margin: 0 0 5px 0; -} -.comfyui-view-list-items { - width: 100%; - background: var(--comfy-menu-bg); - border-radius: 5px; - display: flex; - justify-content: center; - flex: auto; - align-items: center; - flex-direction: column; -} -.comfyui-view-list-items section { - max-height: 400px; - overflow: auto; - width: 100%; - display: grid; - grid-template-columns: auto auto auto; - align-items: center; - justify-content: center; - gap: 5px; - padding: 5px 0; -} -.comfyui-view-list-items section + section { - border-top: 1px solid var(--border-color); - margin-top: 10px; - padding-top: 5px; -} -.comfyui-view-list-items section h5 { - grid-column: 1 / 4; - text-align: center; - margin: 5px; -} -.comfyui-view-list-items span { - text-align: center; - padding: 0 2px; -} -.comfyui-view-list-popup header { - margin-bottom: 10px; - display: flex; - gap: 5px; -} -.comfyui-view-list-popup header .comfyui-button { - border: 1px solid transparent; -} -.comfyui-view-list-popup header .comfyui-button:not(:disabled):hover { - border: 1px solid var(--comfy-menu-bg); -} -/* Queue button */ -.comfyui-queue-button .comfyui-split-primary .comfyui-button { - padding-right: 12px; -} -.comfyui-queue-count { - margin-left: 5px; - border-radius: 10px; - background-color: rgb(8, 80, 153); - padding: 2px 4px; - font-size: 10px; - min-width: 1em; - display: inline-block; -} -/* Queue options*/ -.comfyui-queue-options { - padding: 10px; - font-family: Arial, Helvetica, sans-serif; - font-size: 12px; - display: flex; - gap: 10px; -} - -.comfyui-queue-batch { - display: flex; - flex-direction: column; - border-right: 1px solid var(--comfy-menu-bg); - padding-right: 10px; - gap: 5px; -} - -.comfyui-queue-batch input { - width: 145px; -} - -.comfyui-queue-batch .comfyui-queue-batch-value { - width: 70px; -} - -.comfyui-queue-mode { - display: flex; - flex-direction: column; -} - -.comfyui-queue-mode span { - font-weight: bold; - margin-bottom: 2px; -} - -.comfyui-queue-mode label { - display: flex; - flex-direction: row-reverse; - justify-content: start; - gap: 5px; - padding: 2px 0; -} - -.comfyui-queue-mode label input { - padding: 0; - margin: 0; -} - -/** Send to workflow widget selection dialog */ -.comfy-widget-selection-dialog { - border: none; -} - -.comfy-widget-selection-dialog div { - color: var(--fg-color); - font-family: Arial, Helvetica, sans-serif; -} - -.comfy-widget-selection-dialog h2 { - margin-top: 0; -} - -.comfy-widget-selection-dialog section { - width: fit-content; - display: flex; - flex-direction: column; -} - -.comfy-widget-selection-item { - display: flex; - gap: 10px; - align-items: center; -} - -.comfy-widget-selection-item span { - margin-right: auto; -} - -.comfy-widget-selection-item span::before { - content: '#' attr(data-id); - opacity: 0.5; - margin-right: 5px; -} - -.comfy-modal .comfy-widget-selection-item button { - font-size: 1em; -} - -/***** Responsive *****/ -.lg.comfyui-menu .lt-lg-show { - display: none !important; -} -.comfyui-menu:not(.lg) .nlg-hide { - display: none !important; -} -/** Large screen */ -.lg.comfyui-menu>.comfyui-menu-mobile-collapse .comfyui-button span, -.lg.comfyui-menu>.comfyui-menu-mobile-collapse.comfyui-button span { - display: none; -} -.lg.comfyui-menu>.comfyui-menu-mobile-collapse .comfyui-popup .comfyui-button span { - display: unset; -} - -/** Non large screen */ -.lt-lg.comfyui-menu { - flex-wrap: wrap; -} - -.lt-lg.comfyui-menu > *:not(.comfyui-menu-mobile-collapse) { - order: 1; -} - -.lt-lg.comfyui-menu > .comfyui-menu-mobile-collapse { - order: 9999; - width: 100%; -} - -.comfyui-body-bottom .lt-lg.comfyui-menu > .comfyui-menu-mobile-collapse { - order: -1; -} - -.comfyui-body-bottom .lt-lg.comfyui-menu > .comfyui-menu-button { - top: unset; - bottom: 4px; -} - -.lt-lg.comfyui-menu > .comfyui-menu-mobile-collapse.comfyui-button-group { - flex-wrap: wrap; -} - -.lt-lg.comfyui-menu > .comfyui-menu-mobile-collapse .comfyui-button, -.lt-lg.comfyui-menu > .comfyui-menu-mobile-collapse.comfyui-button { - padding: 10px; -} -.lt-lg.comfyui-menu > .comfyui-menu-mobile-collapse .comfyui-button, -.lt-lg.comfyui-menu > .comfyui-menu-mobile-collapse .comfyui-button-wrapper { - width: 100%; -} - -.lt-lg.comfyui-menu > .comfyui-menu-mobile-collapse .comfyui-popup { - position: static; - background-color: var(--comfy-input-bg); - max-width: unset; - max-height: 50vh; - overflow: auto; -} - -.lt-lg.comfyui-menu:not(.expanded) > .comfyui-menu-mobile-collapse { - display: none; -} - -.lt-lg .comfyui-queue-button { - margin-right: 44px; -} - -.lt-lg .comfyui-menu-button { - position: absolute; - top: 4px; - right: 8px; -} - -.lt-lg.comfyui-menu > .comfyui-menu-mobile-collapse .comfyui-view-list-popup { - border-radius: 0; -} - -.lt-lg.comfyui-menu .comfyui-workflows-popup { - width: 100vw; -} - -/** Small */ -.lt-md .comfyui-workflows-button-inner { - width: unset !important; -} -.lt-md .comfyui-workflows-label { - display: none; -} - -/** Extra small */ -.lt-sm .comfyui-queue-button { - margin-right: 0; - width: 100%; -} -.lt-sm .comfyui-queue-button .comfyui-button { - justify-content: center; -} -.lt-sm .comfyui-interrupt-button { - margin-right: 45px; -} -.comfyui-body-bottom .lt-sm.comfyui-menu > .comfyui-menu-button{ - bottom: 41px; -} \ No newline at end of file diff --git a/web/scripts/ui/menu/queueButton.js b/web/scripts/ui/menu/queueButton.js deleted file mode 100644 index 608f4cc9b..000000000 --- a/web/scripts/ui/menu/queueButton.js +++ /dev/null @@ -1,93 +0,0 @@ -// @ts-check - -import { ComfyButton } from "../components/button.js"; -import { $el } from "../../ui.js"; -import { api } from "../../api.js"; -import { ComfySplitButton } from "../components/splitButton.js"; -import { ComfyQueueOptions } from "./queueOptions.js"; -import { prop } from "../../utils.js"; - -export class ComfyQueueButton { - element = $el("div.comfyui-queue-button"); - #internalQueueSize = 0; - - queuePrompt = async (e) => { - this.#internalQueueSize += this.queueOptions.batchCount; - // Hold shift to queue front, event is undefined when auto-queue is enabled - await this.app.queuePrompt(e?.shiftKey ? -1 : 0, this.queueOptions.batchCount); - }; - - constructor(app) { - this.app = app; - this.queueSizeElement = $el("span.comfyui-queue-count", { - textContent: "?", - }); - - const queue = new ComfyButton({ - content: $el("div", [ - $el("span", { - textContent: "Queue", - }), - this.queueSizeElement, - ]), - icon: "play", - classList: "comfyui-button", - action: this.queuePrompt, - }); - - this.queueOptions = new ComfyQueueOptions(app); - - const btn = new ComfySplitButton( - { - primary: queue, - mode: "click", - position: "absolute", - horizontal: "right", - }, - this.queueOptions.element - ); - btn.element.classList.add("primary"); - this.element.append(btn.element); - - this.autoQueueMode = prop(this, "autoQueueMode", "", () => { - switch (this.autoQueueMode) { - case "instant": - queue.icon = "infinity"; - break; - case "change": - queue.icon = "auto-mode"; - break; - default: - queue.icon = "play"; - break; - } - }); - - this.queueOptions.addEventListener("autoQueueMode", (e) => (this.autoQueueMode = e["detail"])); - - api.addEventListener("graphChanged", () => { - if (this.autoQueueMode === "change") { - if (this.#internalQueueSize) { - this.graphHasChanged = true; - } else { - this.graphHasChanged = false; - this.queuePrompt(); - } - } - }); - - api.addEventListener("status", ({ detail }) => { - this.#internalQueueSize = detail?.exec_info?.queue_remaining; - if (this.#internalQueueSize != null) { - this.queueSizeElement.textContent = this.#internalQueueSize > 99 ? "99+" : this.#internalQueueSize + ""; - this.queueSizeElement.title = `${this.#internalQueueSize} prompts in queue`; - if (!this.#internalQueueSize && !app.lastExecutionError) { - if (this.autoQueueMode === "instant" || (this.autoQueueMode === "change" && this.graphHasChanged)) { - this.graphHasChanged = false; - this.queuePrompt(); - } - } - } - }); - } -} diff --git a/web/scripts/ui/menu/queueOptions.js b/web/scripts/ui/menu/queueOptions.js deleted file mode 100644 index f3d34e74e..000000000 --- a/web/scripts/ui/menu/queueOptions.js +++ /dev/null @@ -1,77 +0,0 @@ -// @ts-check - -import { $el } from "../../ui.js"; -import { prop } from "../../utils.js"; - -export class ComfyQueueOptions extends EventTarget { - element = $el("div.comfyui-queue-options"); - - constructor(app) { - super(); - this.app = app; - - this.batchCountInput = $el("input", { - className: "comfyui-queue-batch-value", - type: "number", - min: "1", - value: "1", - oninput: () => (this.batchCount = +this.batchCountInput.value), - }); - - this.batchCountRange = $el("input", { - type: "range", - min: "1", - max: "100", - value: "1", - oninput: () => (this.batchCount = +this.batchCountRange.value), - }); - - this.element.append( - $el("div.comfyui-queue-batch", [ - $el( - "label", - { - textContent: "Batch count: ", - }, - this.batchCountInput - ), - this.batchCountRange, - ]) - ); - - const createOption = (text, value, checked = false) => - $el( - "label", - { textContent: text }, - $el("input", { - type: "radio", - name: "AutoQueueMode", - checked, - value, - oninput: (e) => (this.autoQueueMode = e.target["value"]), - }) - ); - - this.autoQueueEl = $el("div.comfyui-queue-mode", [ - $el("span", "Auto Queue:"), - createOption("Disabled", "", true), - createOption("Instant", "instant"), - createOption("On Change", "change"), - ]); - - this.element.append(this.autoQueueEl); - - this.batchCount = prop(this, "batchCount", 1, () => { - this.batchCountInput.value = this.batchCount + ""; - this.batchCountRange.value = this.batchCount + ""; - }); - - this.autoQueueMode = prop(this, "autoQueueMode", "Disabled", () => { - this.dispatchEvent( - new CustomEvent("autoQueueMode", { - detail: this.autoQueueMode, - }) - ); - }); - } -} diff --git a/web/scripts/ui/menu/viewHistory.js b/web/scripts/ui/menu/viewHistory.js deleted file mode 100644 index de6b343dc..000000000 --- a/web/scripts/ui/menu/viewHistory.js +++ /dev/null @@ -1,27 +0,0 @@ -// @ts-check - -import { ComfyButton } from "../components/button.js"; -import { ComfyViewList, ComfyViewListButton } from "./viewList.js"; - -export class ComfyViewHistoryButton extends ComfyViewListButton { - constructor(app) { - super(app, { - button: new ComfyButton({ - content: "View History", - icon: "history", - tooltip: "View history", - classList: "comfyui-button comfyui-history-button", - }), - list: ComfyViewHistoryList, - mode: "History", - }); - } -} - -export class ComfyViewHistoryList extends ComfyViewList { - async loadItems() { - const items = await super.loadItems(); - items["History"].reverse(); - return items; - } -} diff --git a/web/scripts/ui/menu/viewList.js b/web/scripts/ui/menu/viewList.js deleted file mode 100644 index 693ca335c..000000000 --- a/web/scripts/ui/menu/viewList.js +++ /dev/null @@ -1,203 +0,0 @@ -// @ts-check - -import { ComfyButton } from "../components/button.js"; -import { $el } from "../../ui.js"; -import { api } from "../../api.js"; -import { ComfyPopup } from "../components/popup.js"; - -export class ComfyViewListButton { - get open() { - return this.popup.open; - } - - set open(open) { - this.popup.open = open; - } - - constructor(app, { button, list, mode }) { - this.app = app; - this.button = button; - this.element = $el("div.comfyui-button-wrapper", this.button.element); - this.popup = new ComfyPopup({ - target: this.element, - container: this.element, - horizontal: "right", - }); - this.list = new (list ?? ComfyViewList)(app, mode, this.popup); - this.popup.children = [this.list.element]; - this.popup.addEventListener("open", () => { - this.list.update(); - }); - this.popup.addEventListener("close", () => { - this.list.close(); - }); - this.button.withPopup(this.popup); - - api.addEventListener("status", () => { - if (this.popup.open) { - this.popup.update(); - } - }); - } -} - -export class ComfyViewList { - popup; - - constructor(app, mode, popup) { - this.app = app; - this.mode = mode; - this.popup = popup; - this.type = mode.toLowerCase(); - - this.items = $el(`div.comfyui-${this.type}-items.comfyui-view-list-items`); - this.clear = new ComfyButton({ - icon: "cancel", - content: "Clear", - action: async () => { - this.showSpinner(false); - await api.clearItems(this.type); - await this.update(); - }, - }); - - this.refresh = new ComfyButton({ - icon: "refresh", - content: "Refresh", - action: async () => { - await this.update(false); - }, - }); - - this.element = $el(`div.comfyui-${this.type}-popup.comfyui-view-list-popup`, [ - $el("h3", mode), - $el("header", [this.clear.element, this.refresh.element]), - this.items, - ]); - - api.addEventListener("status", () => { - if (this.popup.open) { - this.update(); - } - }); - } - - async close() { - this.items.replaceChildren(); - } - - async update(resize = true) { - this.showSpinner(resize); - const res = await this.loadItems(); - let any = false; - - const names = Object.keys(res); - const sections = names - .map((section) => { - const items = res[section]; - if (items?.length) { - any = true; - } else { - return; - } - - const rows = []; - if (names.length > 1) { - rows.push($el("h5", section)); - } - rows.push(...items.flatMap((item) => this.createRow(item, section))); - return $el("section", rows); - }) - .filter(Boolean); - - if (any) { - this.items.replaceChildren(...sections); - } else { - this.items.replaceChildren($el("h5", "None")); - } - - this.popup.update(); - this.clear.enabled = this.refresh.enabled = true; - this.element.style.removeProperty("height"); - } - - showSpinner(resize = true) { - // if (!this.spinner) { - // this.spinner = createSpinner(); - // } - // if (!resize) { - // this.element.style.height = this.element.clientHeight + "px"; - // } - // this.clear.enabled = this.refresh.enabled = false; - // this.items.replaceChildren( - // $el( - // "div", - // { - // style: { - // fontSize: "18px", - // }, - // }, - // this.spinner - // ) - // ); - // this.popup.update(); - } - - async loadItems() { - return await api.getItems(this.type); - } - - getRow(item, section) { - return { - text: item.prompt[0] + "", - actions: [ - { - text: "Load", - action: async () => { - try { - await this.app.loadGraphData(item.prompt[3].extra_pnginfo.workflow); - if (item.outputs) { - this.app.nodeOutputs = item.outputs; - } - } catch (error) { - alert("Error loading workflow: " + error.message); - console.error(error); - } - }, - }, - { - text: "Delete", - action: async () => { - try { - await api.deleteItem(this.type, item.prompt[1]); - this.update(); - } catch (error) {} - }, - }, - ], - }; - } - - createRow = (item, section) => { - const row = this.getRow(item, section); - return [ - $el("span", row.text), - ...row.actions.map( - (a) => - new ComfyButton({ - content: a.text, - action: async (e, btn) => { - btn.enabled = false; - try { - await a.action(); - } catch (error) { - throw error; - } finally { - btn.enabled = true; - } - }, - }).element - ), - ]; - }; -} diff --git a/web/scripts/ui/menu/viewQueue.js b/web/scripts/ui/menu/viewQueue.js deleted file mode 100644 index 97d012986..000000000 --- a/web/scripts/ui/menu/viewQueue.js +++ /dev/null @@ -1,55 +0,0 @@ -// @ts-check - -import { ComfyButton } from "../components/button.js"; -import { ComfyViewList, ComfyViewListButton } from "./viewList.js"; -import { api } from "../../api.js"; - -export class ComfyViewQueueButton extends ComfyViewListButton { - constructor(app) { - super(app, { - button: new ComfyButton({ - content: "View Queue", - icon: "format-list-numbered", - tooltip: "View queue", - classList: "comfyui-button comfyui-queue-button", - }), - list: ComfyViewQueueList, - mode: "Queue", - }); - } -} - -export class ComfyViewQueueList extends ComfyViewList { - getRow = (item, section) => { - if (section !== "Running") { - return super.getRow(item, section); - } - return { - text: item.prompt[0] + "", - actions: [ - { - text: "Load", - action: async () => { - try { - await this.app.loadGraphData(item.prompt[3].extra_pnginfo.workflow); - if (item.outputs) { - this.app.nodeOutputs = item.outputs; - } - } catch (error) { - alert("Error loading workflow: " + error.message); - console.error(error); - } - }, - }, - { - text: "Cancel", - action: async () => { - try { - await api.interrupt(); - } catch (error) {} - }, - }, - ], - }; - } -} diff --git a/web/scripts/ui/menu/workflows.js b/web/scripts/ui/menu/workflows.js deleted file mode 100644 index 3b904fb4b..000000000 --- a/web/scripts/ui/menu/workflows.js +++ /dev/null @@ -1,770 +0,0 @@ -// @ts-check - -import { ComfyButton } from "../components/button.js"; -import { prop, getStorageValue, setStorageValue } from "../../utils.js"; -import { $el } from "../../ui.js"; -import { api } from "../../api.js"; -import { ComfyPopup } from "../components/popup.js"; -import { createSpinner } from "../spinner.js"; -import { ComfyWorkflow, trimJsonExt } from "../../workflows.js"; -import { ComfyAsyncDialog } from "../components/asyncDialog.js"; - -export class ComfyWorkflowsMenu { - #first = true; - element = $el("div.comfyui-workflows"); - - get open() { - return this.popup.open; - } - - set open(open) { - this.popup.open = open; - } - - /** - * @param {import("../../app.js").ComfyApp} app - */ - constructor(app) { - this.app = app; - this.#bindEvents(); - - const classList = { - "comfyui-workflows-button": true, - "comfyui-button": true, - unsaved: getStorageValue("Comfy.PreviousWorkflowUnsaved") === "true", - running: false, - }; - this.buttonProgress = $el("div.comfyui-workflows-button-progress"); - this.workflowLabel = $el("span.comfyui-workflows-label", ""); - this.button = new ComfyButton({ - content: $el("div.comfyui-workflows-button-inner", [$el("i.mdi.mdi-graph"), this.workflowLabel, this.buttonProgress]), - icon: "chevron-down", - classList, - }); - - this.element.append(this.button.element); - - this.popup = new ComfyPopup({ target: this.element, classList: "comfyui-workflows-popup" }); - this.content = new ComfyWorkflowsContent(app, this.popup); - this.popup.children = [this.content.element]; - this.popup.addEventListener("change", () => { - this.button.icon = "chevron-" + (this.popup.open ? "up" : "down"); - }); - this.button.withPopup(this.popup); - - this.unsaved = prop(this, "unsaved", classList.unsaved, (v) => { - classList.unsaved = v; - this.button.classList = classList; - setStorageValue("Comfy.PreviousWorkflowUnsaved", v); - }); - } - - #updateProgress = () => { - const prompt = this.app.workflowManager.activePrompt; - let percent = 0; - if (this.app.workflowManager.activeWorkflow === prompt?.workflow) { - const total = Object.values(prompt.nodes); - const done = total.filter(Boolean); - percent = (done.length / total.length) * 100; - } - this.buttonProgress.style.width = percent + "%"; - }; - - #updateActive = () => { - const active = this.app.workflowManager.activeWorkflow; - this.button.tooltip = active.path; - this.workflowLabel.textContent = active.name; - this.unsaved = active.unsaved; - - if (this.#first) { - this.#first = false; - this.content.load(); - } - - this.#updateProgress(); - }; - - #bindEvents() { - this.app.workflowManager.addEventListener("changeWorkflow", this.#updateActive); - this.app.workflowManager.addEventListener("rename", this.#updateActive); - this.app.workflowManager.addEventListener("delete", this.#updateActive); - - this.app.workflowManager.addEventListener("save", () => { - this.unsaved = this.app.workflowManager.activeWorkflow.unsaved; - }); - - this.app.workflowManager.addEventListener("execute", (e) => { - this.#updateProgress(); - }); - - api.addEventListener("graphChanged", () => { - this.unsaved = true; - }); - } - - #getMenuOptions(callback) { - const menu = []; - const directories = new Map(); - for (const workflow of this.app.workflowManager.workflows || []) { - const path = workflow.pathParts; - if (!path) continue; - let parent = menu; - let currentPath = ""; - for (let i = 0; i < path.length - 1; i++) { - currentPath += "/" + path[i]; - let newParent = directories.get(currentPath); - if (!newParent) { - newParent = { - title: path[i], - has_submenu: true, - submenu: { - options: [], - }, - }; - parent.push(newParent); - newParent = newParent.submenu.options; - directories.set(currentPath, newParent); - } - parent = newParent; - } - parent.push({ - title: trimJsonExt(path[path.length - 1]), - callback: () => callback(workflow), - }); - } - return menu; - } - - #getFavoriteMenuOptions(callback) { - const menu = []; - for (const workflow of this.app.workflowManager.workflows || []) { - if (workflow.isFavorite) { - menu.push({ - title: "⭐ " + workflow.name, - callback: () => callback(workflow), - }); - } - } - return menu; - } - - /** - * @param {import("../../app.js").ComfyApp} app - */ - registerExtension(app) { - const self = this; - app.registerExtension({ - name: "Comfy.Workflows", - async beforeRegisterNodeDef(nodeType) { - function getImageWidget(node) { - const inputs = { ...node.constructor?.nodeData?.input?.required, ...node.constructor?.nodeData?.input?.optional }; - for (const input in inputs) { - if (inputs[input][0] === "IMAGEUPLOAD") { - const imageWidget = node.widgets.find((w) => w.name === (inputs[input]?.[1]?.widget ?? "image")); - if (imageWidget) return imageWidget; - } - } - } - - function setWidgetImage(node, widget, img) { - const url = new URL(img.src); - const filename = url.searchParams.get("filename"); - const subfolder = url.searchParams.get("subfolder"); - const type = url.searchParams.get("type"); - const imageId = `${subfolder ? subfolder + "/" : ""}${filename} [${type}]`; - widget.value = imageId; - node.imgs = [img]; - app.graph.setDirtyCanvas(true, true); - } - - /** - * @param {HTMLImageElement} img - * @param {ComfyWorkflow} workflow - */ - async function sendToWorkflow(img, workflow) { - const openWorkflow = app.workflowManager.openWorkflows.find((w) => w.path === workflow.path); - if (openWorkflow) { - workflow = openWorkflow; - } - - await workflow.load(); - let options = []; - const nodes = app.graph.computeExecutionOrder(false); - for (const node of nodes) { - const widget = getImageWidget(node); - if (widget == null) continue; - - if (node.title?.toLowerCase().includes("input")) { - options = [{ widget, node }]; - break; - } else { - options.push({ widget, node }); - } - } - - if (!options.length) { - alert("No image nodes have been found in this workflow!"); - return; - } else if (options.length > 1) { - const dialog = new WidgetSelectionDialog(options); - const res = await dialog.show(app); - if (!res) return; - options = [res]; - } - - setWidgetImage(options[0].node, options[0].widget, img); - } - - const getExtraMenuOptions = nodeType.prototype["getExtraMenuOptions"]; - nodeType.prototype["getExtraMenuOptions"] = function (_, options) { - const r = getExtraMenuOptions?.apply?.(this, arguments); - - const setting = app.ui.settings.getSettingValue("Comfy.UseNewMenu", false); - if (setting && setting != "Disabled") { - const t = /** @type { {imageIndex?: number, overIndex?: number, imgs: string[]} } */ /** @type {any} */ (this); - let img; - if (t.imageIndex != null) { - // An image is selected so select that - img = t.imgs?.[t.imageIndex]; - } else if (t.overIndex != null) { - // No image is selected but one is hovered - img = t.img?.s[t.overIndex]; - } - - if (img) { - let pos = options.findIndex((o) => o.content === "Save Image"); - if (pos === -1) { - pos = 0; - } else { - pos++; - } - - options.splice(pos, 0, { - content: "Send to workflow", - has_submenu: true, - submenu: { - options: [ - { - callback: () => sendToWorkflow(img, app.workflowManager.activeWorkflow), - title: "[Current workflow]", - }, - ...self.#getFavoriteMenuOptions(sendToWorkflow.bind(null, img)), - null, - ...self.#getMenuOptions(sendToWorkflow.bind(null, img)), - ], - }, - }); - } - } - - return r; - }; - }, - }); - } -} - -export class ComfyWorkflowsContent { - element = $el("div.comfyui-workflows-panel"); - treeState = {}; - treeFiles = {}; - /** @type { Map } */ - openFiles = new Map(); - /** @type {WorkflowElement} */ - activeElement = null; - - /** - * @param {import("../../app.js").ComfyApp} app - * @param {ComfyPopup} popup - */ - constructor(app, popup) { - this.app = app; - this.popup = popup; - this.actions = $el("div.comfyui-workflows-actions", [ - new ComfyButton({ - content: "Default", - icon: "file-code", - iconSize: 18, - classList: "comfyui-button primary", - tooltip: "Load default workflow", - action: () => { - popup.open = false; - app.loadGraphData(); - app.resetView(); - }, - }).element, - new ComfyButton({ - content: "Browse", - icon: "folder", - iconSize: 18, - tooltip: "Browse for an image or exported workflow", - action: () => { - popup.open = false; - app.ui.loadFile(); - }, - }).element, - new ComfyButton({ - content: "Blank", - icon: "plus-thick", - iconSize: 18, - tooltip: "Create a new blank workflow", - action: () => { - app.workflowManager.setWorkflow(null); - app.clean(); - app.graph.clear(); - app.workflowManager.activeWorkflow.track(); - popup.open = false; - }, - }).element, - ]); - - this.spinner = createSpinner(); - this.element.replaceChildren(this.actions, this.spinner); - - this.popup.addEventListener("open", () => this.load()); - this.popup.addEventListener("close", () => this.element.replaceChildren(this.actions, this.spinner)); - - this.app.workflowManager.addEventListener("favorite", (e) => { - const workflow = e["detail"]; - const button = this.treeFiles[workflow.path]?.primary; - if (!button) return; // Can happen when a workflow is renamed - button.icon = this.#getFavoriteIcon(workflow); - button.overIcon = this.#getFavoriteOverIcon(workflow); - this.updateFavorites(); - }); - - for (const e of ["save", "open", "close", "changeWorkflow"]) { - // TODO: dont be lazy and just update the specific element - app.workflowManager.addEventListener(e, () => this.updateOpen()); - } - this.app.workflowManager.addEventListener("rename", () => this.load()); - this.app.workflowManager.addEventListener("execute", (e) => this.#updateActive()); - } - - async load() { - await this.app.workflowManager.loadWorkflows(); - this.updateTree(); - this.updateFavorites(); - this.updateOpen(); - this.element.replaceChildren(this.actions, this.openElement, this.favoritesElement, this.treeElement); - } - - updateOpen() { - const current = this.openElement; - this.openFiles.clear(); - - this.openElement = $el("div.comfyui-workflows-open", [ - $el("h3", "Open"), - ...this.app.workflowManager.openWorkflows.map((w) => { - const wrapper = new WorkflowElement(this, w, { - primary: { element: $el("i.mdi.mdi-18px.mdi-progress-pencil") }, - buttons: [ - this.#getRenameButton(w), - new ComfyButton({ - icon: "close", - iconSize: 18, - classList: "comfyui-button comfyui-workflows-file-action", - tooltip: "Close workflow", - action: (e) => { - e.stopImmediatePropagation(); - this.app.workflowManager.closeWorkflow(w); - }, - }), - ], - }); - if (w.unsaved) { - wrapper.element.classList.add("unsaved"); - } - if(w === this.app.workflowManager.activeWorkflow) { - wrapper.element.classList.add("active"); - } - - this.openFiles.set(w, wrapper); - return wrapper.element; - }), - ]); - - this.#updateActive(); - current?.replaceWith(this.openElement); - } - - updateFavorites() { - const current = this.favoritesElement; - const favorites = [...this.app.workflowManager.workflows.filter((w) => w.isFavorite)]; - - this.favoritesElement = $el("div.comfyui-workflows-favorites", [ - $el("h3", "Favorites"), - ...favorites - .map((w) => { - return this.#getWorkflowElement(w).element; - }) - .filter(Boolean), - ]); - - current?.replaceWith(this.favoritesElement); - } - - filterTree() { - if (!this.filterText) { - this.treeRoot.classList.remove("filtered"); - // Unfilter whole tree - for (const item of Object.values(this.treeFiles)) { - item.element.parentElement.style.removeProperty("display"); - this.showTreeParents(item.element.parentElement); - } - return; - } - this.treeRoot.classList.add("filtered"); - const searchTerms = this.filterText.toLocaleLowerCase().split(" "); - for (const item of Object.values(this.treeFiles)) { - const parts = item.workflow.pathParts; - let termIndex = 0; - let valid = false; - for (const part of parts) { - let currentIndex = 0; - do { - currentIndex = part.indexOf(searchTerms[termIndex], currentIndex); - if (currentIndex > -1) currentIndex += searchTerms[termIndex].length; - } while (currentIndex !== -1 && ++termIndex < searchTerms.length); - - if (termIndex >= searchTerms.length) { - valid = true; - break; - } - } - if (valid) { - item.element.parentElement.style.removeProperty("display"); - this.showTreeParents(item.element.parentElement); - } else { - item.element.parentElement.style.display = "none"; - this.hideTreeParents(item.element.parentElement); - } - } - } - - hideTreeParents(element) { - // Hide all parents if no children are visible - if (element.parentElement?.classList.contains("comfyui-workflows-tree") === false) { - for (let i = 1; i < element.parentElement.children.length; i++) { - const c = element.parentElement.children[i]; - if (c.style.display !== "none") { - return; - } - } - element.parentElement.style.display = "none"; - this.hideTreeParents(element.parentElement); - } - } - - showTreeParents(element) { - if (element.parentElement?.classList.contains("comfyui-workflows-tree") === false) { - element.parentElement.style.removeProperty("display"); - this.showTreeParents(element.parentElement); - } - } - - updateTree() { - const current = this.treeElement; - const nodes = {}; - let typingTimeout; - - this.treeFiles = {}; - this.treeRoot = $el("ul.comfyui-workflows-tree"); - this.treeElement = $el("section", [ - $el("header", [ - $el("h3", "Browse"), - $el("div.comfy-ui-workflows-search", [ - $el("i.mdi.mdi-18px.mdi-magnify"), - $el("input", { - placeholder: "Search", - value: this.filterText ?? "", - oninput: (e) => { - this.filterText = e.target["value"]?.trim(); - clearTimeout(typingTimeout); - typingTimeout = setTimeout(() => this.filterTree(), 250); - }, - }), - ]), - ]), - this.treeRoot, - ]); - - for (const workflow of this.app.workflowManager.workflows) { - if (!workflow.pathParts) continue; - - let currentPath = ""; - let currentRoot = this.treeRoot; - - for (let i = 0; i < workflow.pathParts.length; i++) { - currentPath += (currentPath ? "\\" : "") + workflow.pathParts[i]; - const parentNode = nodes[currentPath] ?? this.#createNode(currentPath, workflow, i, currentRoot); - - nodes[currentPath] = parentNode; - currentRoot = parentNode; - } - } - - current?.replaceWith(this.treeElement); - this.filterTree(); - } - - #expandNode(el, workflow, thisPath, i) { - const expanded = !el.classList.toggle("closed"); - if (expanded) { - let c = ""; - for (let j = 0; j <= i; j++) { - c += (c ? "\\" : "") + workflow.pathParts[j]; - this.treeState[c] = true; - } - } else { - let c = thisPath; - for (let j = i + 1; j < workflow.pathParts.length; j++) { - c += (c ? "\\" : "") + workflow.pathParts[j]; - delete this.treeState[c]; - } - delete this.treeState[thisPath]; - } - } - - #updateActive() { - this.#removeActive(); - - const active = this.app.workflowManager.activePrompt; - if (!active?.workflow) return; - - const open = this.openFiles.get(active.workflow); - if (!open) return; - - this.activeElement = open; - - const total = Object.values(active.nodes); - const done = total.filter(Boolean); - const percent = done.length / total.length; - open.element.classList.add("running"); - open.element.style.setProperty("--progress", percent * 100 + "%"); - open.primary.element.classList.remove("mdi-progress-pencil"); - open.primary.element.classList.add("mdi-play"); - } - - #removeActive() { - if (!this.activeElement) return; - this.activeElement.element.classList.remove("running"); - this.activeElement.element.style.removeProperty("--progress"); - this.activeElement.primary.element.classList.add("mdi-progress-pencil"); - this.activeElement.primary.element.classList.remove("mdi-play"); - } - - /** @param {ComfyWorkflow} workflow */ - #getFavoriteIcon(workflow) { - return workflow.isFavorite ? "star" : "file-outline"; - } - - /** @param {ComfyWorkflow} workflow */ - #getFavoriteOverIcon(workflow) { - return workflow.isFavorite ? "star-off" : "star-outline"; - } - - /** @param {ComfyWorkflow} workflow */ - #getFavoriteTooltip(workflow) { - return workflow.isFavorite ? "Remove this workflow from your favorites" : "Add this workflow to your favorites"; - } - - /** @param {ComfyWorkflow} workflow */ - #getFavoriteButton(workflow, primary) { - return new ComfyButton({ - icon: this.#getFavoriteIcon(workflow), - overIcon: this.#getFavoriteOverIcon(workflow), - iconSize: 18, - classList: "comfyui-button comfyui-workflows-file-action-favorite" + (primary ? " comfyui-workflows-file-action-primary" : ""), - tooltip: this.#getFavoriteTooltip(workflow), - action: (e) => { - e.stopImmediatePropagation(); - workflow.favorite(!workflow.isFavorite); - }, - }); - } - - /** @param {ComfyWorkflow} workflow */ - #getDeleteButton(workflow) { - const deleteButton = new ComfyButton({ - icon: "delete", - tooltip: "Delete this workflow", - classList: "comfyui-button comfyui-workflows-file-action", - iconSize: 18, - action: async (e, btn) => { - e.stopImmediatePropagation(); - - if (btn.icon === "delete-empty") { - btn.enabled = false; - await workflow.delete(); - await this.load(); - } else { - btn.icon = "delete-empty"; - btn.element.style.background = "red"; - } - }, - }); - deleteButton.element.addEventListener("mouseleave", () => { - deleteButton.icon = "delete"; - deleteButton.element.style.removeProperty("background"); - }); - return deleteButton; - } - - /** @param {ComfyWorkflow} workflow */ - #getInsertButton(workflow) { - return new ComfyButton({ - icon: "file-move-outline", - iconSize: 18, - tooltip: "Insert this workflow into the current workflow", - classList: "comfyui-button comfyui-workflows-file-action", - action: (e) => { - if (!this.app.shiftDown) { - this.popup.open = false; - } - e.stopImmediatePropagation(); - if (!this.app.shiftDown) { - this.popup.open = false; - } - workflow.insert(); - }, - }); - } - - /** @param {ComfyWorkflow} workflow */ - #getRenameButton(workflow) { - return new ComfyButton({ - icon: "pencil", - tooltip: workflow.path ? "Rename this workflow" : "This workflow can't be renamed as it hasn't been saved.", - classList: "comfyui-button comfyui-workflows-file-action", - iconSize: 18, - enabled: !!workflow.path, - action: async (e) => { - e.stopImmediatePropagation(); - const newName = prompt("Enter new name", workflow.path); - if (newName) { - await workflow.rename(newName); - } - }, - }); - } - - /** @param {ComfyWorkflow} workflow */ - #getWorkflowElement(workflow) { - return new WorkflowElement(this, workflow, { - primary: this.#getFavoriteButton(workflow, true), - buttons: [this.#getInsertButton(workflow), this.#getRenameButton(workflow), this.#getDeleteButton(workflow)], - }); - } - - /** @param {ComfyWorkflow} workflow */ - #createLeafNode(workflow) { - const fileNode = this.#getWorkflowElement(workflow); - this.treeFiles[workflow.path] = fileNode; - return fileNode; - } - - #createNode(currentPath, workflow, i, currentRoot) { - const part = workflow.pathParts[i]; - - const parentNode = $el("ul" + (this.treeState[currentPath] ? "" : ".closed"), { - $: (el) => { - el.onclick = (e) => { - this.#expandNode(el, workflow, currentPath, i); - e.stopImmediatePropagation(); - }; - }, - }); - currentRoot.append(parentNode); - - // Create a node for the current part and an inner UL for its children if it isnt a leaf node - const leaf = i === workflow.pathParts.length - 1; - let nodeElement; - if (leaf) { - nodeElement = this.#createLeafNode(workflow).element; - } else { - nodeElement = $el("li", [$el("i.mdi.mdi-18px.mdi-folder"), $el("span", part)]); - } - parentNode.append(nodeElement); - return parentNode; - } -} - -class WorkflowElement { - /** - * @param { ComfyWorkflowsContent } parent - * @param { ComfyWorkflow } workflow - */ - constructor(parent, workflow, { tagName = "li", primary, buttons }) { - this.parent = parent; - this.workflow = workflow; - this.primary = primary; - this.buttons = buttons; - - this.element = $el( - tagName + ".comfyui-workflows-tree-file", - { - onclick: () => { - workflow.load(); - this.parent.popup.open = false; - }, - title: this.workflow.path, - }, - [this.primary?.element, $el("span", workflow.name), ...buttons.map((b) => b.element)] - ); - } -} - -class WidgetSelectionDialog extends ComfyAsyncDialog { - #options; - - /** - * @param {Array<{widget: {name: string}, node: {pos: [number, number], title: string, id: string, type: string}}>} options - */ - constructor(options) { - super(); - this.#options = options; - } - - show(app) { - this.element.classList.add("comfy-widget-selection-dialog"); - return super.show( - $el("div", [ - $el("h2", "Select image target"), - $el( - "p", - "This workflow has multiple image loader nodes, you can rename a node to include 'input' in the title for it to be automatically selected, or select one below." - ), - $el( - "section", - this.#options.map((opt) => { - return $el("div.comfy-widget-selection-item", [ - $el("span", { dataset: { id: opt.node.id } }, `${opt.node.title ?? opt.node.type} ${opt.widget.name}`), - $el( - "button.comfyui-button", - { - onclick: () => { - app.canvas.ds.offset[0] = -opt.node.pos[0] + 50; - app.canvas.ds.offset[1] = -opt.node.pos[1] + 50; - app.canvas.selectNode(opt.node); - app.graph.setDirtyCanvas(true, true); - }, - }, - "Show" - ), - $el( - "button.comfyui-button.primary", - { - onclick: () => { - this.close(opt); - }, - }, - "Select" - ), - ]); - }) - ), - ]) - ); - } -} \ No newline at end of file diff --git a/web/scripts/ui/settings.js b/web/scripts/ui/settings.js deleted file mode 100644 index 819e4e7d6..000000000 --- a/web/scripts/ui/settings.js +++ /dev/null @@ -1,333 +0,0 @@ -import { $el } from "../ui.js"; -import { api } from "../api.js"; -import { ComfyDialog } from "./dialog.js"; - -export class ComfySettingsDialog extends ComfyDialog { - constructor(app) { - super(); - this.app = app; - this.settingsValues = {}; - this.settingsLookup = {}; - this.element = $el( - "dialog", - { - id: "comfy-settings-dialog", - parent: document.body, - }, - [ - $el("table.comfy-modal-content.comfy-table", [ - $el( - "caption", - { textContent: "Settings" }, - $el("button.comfy-btn", { - type: "button", - textContent: "\u00d7", - onclick: () => { - this.element.close(); - }, - }) - ), - $el("tbody", { $: (tbody) => (this.textElement = tbody) }), - $el("button", { - type: "button", - textContent: "Close", - style: { - cursor: "pointer", - }, - onclick: () => { - this.element.close(); - }, - }), - ]), - ] - ); - } - - get settings() { - return Object.values(this.settingsLookup); - } - - #dispatchChange(id, value, oldValue) { - this.dispatchEvent( - new CustomEvent(id + ".change", { - detail: { - value, - oldValue - }, - }) - ); - } - - async load() { - if (this.app.storageLocation === "browser") { - this.settingsValues = localStorage; - } else { - this.settingsValues = await api.getSettings(); - } - - // Trigger onChange for any settings added before load - for (const id in this.settingsLookup) { - const value = this.settingsValues[this.getId(id)]; - this.settingsLookup[id].onChange?.(value); - this.#dispatchChange(id, value); - } - } - - getId(id) { - if (this.app.storageLocation === "browser") { - id = "Comfy.Settings." + id; - } - return id; - } - - getSettingValue(id, defaultValue) { - let value = this.settingsValues[this.getId(id)]; - if(value != null) { - if(this.app.storageLocation === "browser") { - try { - value = JSON.parse(value); - } catch (error) { - } - } - } - return value ?? defaultValue; - } - - async setSettingValueAsync(id, value) { - const json = JSON.stringify(value); - localStorage["Comfy.Settings." + id] = json; // backwards compatibility for extensions keep setting in storage - - let oldValue = this.getSettingValue(id, undefined); - this.settingsValues[this.getId(id)] = value; - - if (id in this.settingsLookup) { - this.settingsLookup[id].onChange?.(value, oldValue); - } - this.#dispatchChange(id, value, oldValue); - - await api.storeSetting(id, value); - } - - setSettingValue(id, value) { - this.setSettingValueAsync(id, value).catch((err) => { - alert(`Error saving setting '${id}'`); - console.error(err); - }); - } - - addSetting({ id, name, type, defaultValue, onChange, attrs = {}, tooltip = "", options = undefined }) { - if (!id) { - throw new Error("Settings must have an ID"); - } - - if (id in this.settingsLookup) { - throw new Error(`Setting ${id} of type ${type} must have a unique ID.`); - } - - let skipOnChange = false; - let value = this.getSettingValue(id); - if (value == null) { - if (this.app.isNewUserSession) { - // Check if we have a localStorage value but not a setting value and we are a new user - const localValue = localStorage["Comfy.Settings." + id]; - if (localValue) { - value = JSON.parse(localValue); - this.setSettingValue(id, value); // Store on the server - } - } - if (value == null) { - value = defaultValue; - } - } - - // Trigger initial setting of value - if (!skipOnChange) { - onChange?.(value, undefined); - } - - this.settingsLookup[id] = { - id, - onChange, - name, - render: () => { - if (type === "hidden") return; - - const setter = (v) => { - if (onChange) { - onChange(v, value); - } - - this.setSettingValue(id, v); - value = v; - }; - value = this.getSettingValue(id, defaultValue); - - let element; - const htmlID = id.replaceAll(".", "-"); - - const labelCell = $el("td", [ - $el("label", { - for: htmlID, - classList: [tooltip !== "" ? "comfy-tooltip-indicator" : ""], - textContent: name, - }), - ]); - - if (typeof type === "function") { - element = type(name, setter, value, attrs); - } else { - switch (type) { - case "boolean": - element = $el("tr", [ - labelCell, - $el("td", [ - $el("input", { - id: htmlID, - type: "checkbox", - checked: value, - onchange: (event) => { - const isChecked = event.target.checked; - if (onChange !== undefined) { - onChange(isChecked); - } - this.setSettingValue(id, isChecked); - }, - }), - ]), - ]); - break; - case "number": - element = $el("tr", [ - labelCell, - $el("td", [ - $el("input", { - type, - value, - id: htmlID, - oninput: (e) => { - setter(e.target.value); - }, - ...attrs, - }), - ]), - ]); - break; - case "slider": - element = $el("tr", [ - labelCell, - $el("td", [ - $el( - "div", - { - style: { - display: "grid", - gridAutoFlow: "column", - }, - }, - [ - $el("input", { - ...attrs, - value, - type: "range", - oninput: (e) => { - setter(e.target.value); - e.target.nextElementSibling.value = e.target.value; - }, - }), - $el("input", { - ...attrs, - value, - id: htmlID, - type: "number", - style: { maxWidth: "4rem" }, - oninput: (e) => { - setter(e.target.value); - e.target.previousElementSibling.value = e.target.value; - }, - }), - ] - ), - ]), - ]); - break; - case "combo": - element = $el("tr", [ - labelCell, - $el("td", [ - $el( - "select", - { - oninput: (e) => { - setter(e.target.value); - }, - }, - (typeof options === "function" ? options(value) : options || []).map((opt) => { - if (typeof opt === "string") { - opt = { text: opt }; - } - const v = opt.value ?? opt.text; - return $el("option", { - value: v, - textContent: opt.text, - selected: value + "" === v + "", - }); - }) - ), - ]), - ]); - break; - case "text": - default: - if (type !== "text") { - console.warn(`Unsupported setting type '${type}, defaulting to text`); - } - - element = $el("tr", [ - labelCell, - $el("td", [ - $el("input", { - value, - id: htmlID, - oninput: (e) => { - setter(e.target.value); - }, - ...attrs, - }), - ]), - ]); - break; - } - } - if (tooltip) { - element.title = tooltip; - } - - return element; - }, - }; - - const self = this; - return { - get value() { - return self.getSettingValue(id, defaultValue); - }, - set value(v) { - self.setSettingValue(id, v); - }, - }; - } - - show() { - this.textElement.replaceChildren( - $el( - "tr", - { - style: { display: "none" }, - }, - [$el("th"), $el("th", { style: { width: "33%" } })] - ), - ...this.settings.sort((a, b) => a.name.localeCompare(b.name)).map((s) => s.render()).filter(Boolean) - ); - this.element.showModal(); - } -} diff --git a/web/scripts/ui/spinner.css b/web/scripts/ui/spinner.css deleted file mode 100644 index 56da6072e..000000000 --- a/web/scripts/ui/spinner.css +++ /dev/null @@ -1,34 +0,0 @@ -.lds-ring { - display: inline-block; - position: relative; - width: 1em; - height: 1em; -} -.lds-ring div { - box-sizing: border-box; - display: block; - position: absolute; - width: 100%; - height: 100%; - border: 0.15em solid #fff; - border-radius: 50%; - animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; - border-color: #fff transparent transparent transparent; -} -.lds-ring div:nth-child(1) { - animation-delay: -0.45s; -} -.lds-ring div:nth-child(2) { - animation-delay: -0.3s; -} -.lds-ring div:nth-child(3) { - animation-delay: -0.15s; -} -@keyframes lds-ring { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} diff --git a/web/scripts/ui/spinner.js b/web/scripts/ui/spinner.js deleted file mode 100644 index d049786f6..000000000 --- a/web/scripts/ui/spinner.js +++ /dev/null @@ -1,9 +0,0 @@ -import { addStylesheet } from "../utils.js"; - -addStylesheet(import.meta.url); - -export function createSpinner() { - const div = document.createElement("div"); - div.innerHTML = `
`; - return div.firstElementChild; -} diff --git a/web/scripts/ui/toggleSwitch.js b/web/scripts/ui/toggleSwitch.js deleted file mode 100644 index 59597ef90..000000000 --- a/web/scripts/ui/toggleSwitch.js +++ /dev/null @@ -1,60 +0,0 @@ -import { $el } from "../ui.js"; - -/** - * @typedef { { text: string, value?: string, tooltip?: string } } ToggleSwitchItem - */ -/** - * Creates a toggle switch element - * @param { string } name - * @param { Array void } [opts.onChange] - */ -export function toggleSwitch(name, items, { onChange } = {}) { - let selectedIndex; - let elements; - - function updateSelected(index) { - if (selectedIndex != null) { - elements[selectedIndex].classList.remove("comfy-toggle-selected"); - } - onChange?.({ item: items[index], prev: selectedIndex == null ? undefined : items[selectedIndex] }); - selectedIndex = index; - elements[selectedIndex].classList.add("comfy-toggle-selected"); - } - - elements = items.map((item, i) => { - if (typeof item === "string") item = { text: item }; - if (!item.value) item.value = item.text; - - const toggle = $el( - "label", - { - textContent: item.text, - title: item.tooltip ?? "", - }, - $el("input", { - name, - type: "radio", - value: item.value ?? item.text, - checked: item.selected, - onchange: () => { - updateSelected(i); - }, - }) - ); - if (item.selected) { - updateSelected(i); - } - return toggle; - }); - - const container = $el("div.comfy-toggle-switch", elements); - - if (selectedIndex == null) { - elements[0].children[0].checked = true; - updateSelected(0); - } - - return container; -} diff --git a/web/scripts/ui/userSelection.css b/web/scripts/ui/userSelection.css deleted file mode 100644 index 35c9d6614..000000000 --- a/web/scripts/ui/userSelection.css +++ /dev/null @@ -1,135 +0,0 @@ -.comfy-user-selection { - width: 100vw; - height: 100vh; - position: absolute; - top: 0; - left: 0; - z-index: 999; - display: flex; - align-items: center; - justify-content: center; - font-family: sans-serif; - background: linear-gradient(var(--tr-even-bg-color), var(--tr-odd-bg-color)); -} - -.comfy-user-selection-inner { - background: var(--comfy-menu-bg); - margin-top: -30vh; - padding: 20px 40px; - border-radius: 10px; - min-width: 365px; - position: relative; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); -} - -.comfy-user-selection-inner form { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; -} - -.comfy-user-selection-inner h1 { - margin: 10px 0 30px 0; - font-weight: normal; -} - -.comfy-user-selection-inner label { - display: flex; - flex-direction: column; - width: 100%; -} - -.comfy-user-selection input, -.comfy-user-selection select { - background-color: var(--comfy-input-bg); - color: var(--input-text); - border: 0; - border-radius: 5px; - padding: 5px; - margin-top: 10px; -} - -.comfy-user-selection input::placeholder { - color: var(--descrip-text); - opacity: 1; -} - -.comfy-user-existing { - width: 100%; -} - -.no-users .comfy-user-existing { - display: none; -} - -.comfy-user-selection-inner .or-separator { - margin: 10px 0; - padding: 10px; - display: block; - text-align: center; - width: 100%; - color: var(--descrip-text); -} - -.comfy-user-selection-inner .or-separator { - overflow: hidden; - text-align: center; - margin-left: -10px; -} - -.comfy-user-selection-inner .or-separator::before, -.comfy-user-selection-inner .or-separator::after { - content: ""; - background-color: var(--border-color); - position: relative; - height: 1px; - vertical-align: middle; - display: inline-block; - width: calc(50% - 20px); - top: -1px; -} - -.comfy-user-selection-inner .or-separator::before { - right: 10px; - margin-left: -50%; -} - -.comfy-user-selection-inner .or-separator::after { - left: 10px; - margin-right: -50%; -} - -.comfy-user-selection-inner section { - width: 100%; - padding: 10px; - margin: -10px; - transition: background-color 0.2s; -} - -.comfy-user-selection-inner section.selected { - background: var(--border-color); - border-radius: 5px; -} - -.comfy-user-selection-inner footer { - display: flex; - flex-direction: column; - align-items: center; - margin-top: 20px; -} - -.comfy-user-selection-inner .comfy-user-error { - color: var(--error-text); - margin-bottom: 10px; -} - -.comfy-user-button-next { - font-size: 16px; - padding: 6px 10px; - width: 100px; - display: flex; - gap: 5px; - align-items: center; - justify-content: center; -} \ No newline at end of file diff --git a/web/scripts/ui/userSelection.js b/web/scripts/ui/userSelection.js deleted file mode 100644 index f9f1ca807..000000000 --- a/web/scripts/ui/userSelection.js +++ /dev/null @@ -1,114 +0,0 @@ -import { api } from "../api.js"; -import { $el } from "../ui.js"; -import { addStylesheet } from "../utils.js"; -import { createSpinner } from "./spinner.js"; - -export class UserSelectionScreen { - async show(users, user) { - // This will rarely be hit so move the loading to on demand - await addStylesheet(import.meta.url); - const userSelection = document.getElementById("comfy-user-selection"); - userSelection.style.display = ""; - return new Promise((resolve) => { - const input = userSelection.getElementsByTagName("input")[0]; - const select = userSelection.getElementsByTagName("select")[0]; - const inputSection = input.closest("section"); - const selectSection = select.closest("section"); - const form = userSelection.getElementsByTagName("form")[0]; - const error = userSelection.getElementsByClassName("comfy-user-error")[0]; - const button = userSelection.getElementsByClassName("comfy-user-button-next")[0]; - - let inputActive = null; - input.addEventListener("focus", () => { - inputSection.classList.add("selected"); - selectSection.classList.remove("selected"); - inputActive = true; - }); - select.addEventListener("focus", () => { - inputSection.classList.remove("selected"); - selectSection.classList.add("selected"); - inputActive = false; - select.style.color = ""; - }); - select.addEventListener("blur", () => { - if (!select.value) { - select.style.color = "var(--descrip-text)"; - } - }); - - form.addEventListener("submit", async (e) => { - e.preventDefault(); - if (inputActive == null) { - error.textContent = "Please enter a username or select an existing user."; - } else if (inputActive) { - const username = input.value.trim(); - if (!username) { - error.textContent = "Please enter a username."; - return; - } - - // Create new user - input.disabled = select.disabled = input.readonly = select.readonly = true; - const spinner = createSpinner(); - button.prepend(spinner); - try { - const resp = await api.createUser(username); - if (resp.status >= 300) { - let message = "Error creating user: " + resp.status + " " + resp.statusText; - try { - const res = await resp.json(); - if(res.error) { - message = res.error; - } - } catch (error) { - } - throw new Error(message); - } - - resolve({ username, userId: await resp.json(), created: true }); - } catch (err) { - spinner.remove(); - error.textContent = err.message ?? err.statusText ?? err ?? "An unknown error occurred."; - input.disabled = select.disabled = input.readonly = select.readonly = false; - return; - } - } else if (!select.value) { - error.textContent = "Please select an existing user."; - return; - } else { - resolve({ username: users[select.value], userId: select.value, created: false }); - } - }); - - if (user) { - const name = localStorage["Comfy.userName"]; - if (name) { - input.value = name; - } - } - if (input.value) { - // Focus the input, do this separately as sometimes browsers like to fill in the value - input.focus(); - } - - const userIds = Object.keys(users ?? {}); - if (userIds.length) { - for (const u of userIds) { - $el("option", { textContent: users[u], value: u, parent: select }); - } - select.style.color = "var(--descrip-text)"; - - if (select.value) { - // Focus the select, do this separately as sometimes browsers like to fill in the value - select.focus(); - } - } else { - userSelection.classList.add("no-users"); - input.focus(); - } - }).then((r) => { - userSelection.remove(); - return r; - }); - } -} diff --git a/web/scripts/ui/utils.js b/web/scripts/ui/utils.js deleted file mode 100644 index e37d8b41e..000000000 --- a/web/scripts/ui/utils.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @typedef { string | string[] | Record } ClassList - */ - -/** - * @param { HTMLElement } element - * @param { ClassList } classList - * @param { string[] } requiredClasses - */ -export function applyClasses(element, classList, ...requiredClasses) { - classList ??= ""; - - let str; - if (typeof classList === "string") { - str = classList; - } else if (classList instanceof Array) { - str = classList.join(" "); - } else { - str = Object.entries(classList).reduce((p, c) => { - if (c[1]) { - p += (p.length ? " " : "") + c[0]; - } - return p; - }, ""); - } - element.className = str; - if (requiredClasses) { - element.classList.add(...requiredClasses); - } -} - -/** - * @param { HTMLElement } element - * @param { { onHide?: (el: HTMLElement) => void, onShow?: (el: HTMLElement, value) => void } } [param1] - * @returns - */ -export function toggleElement(element, { onHide, onShow } = {}) { - let placeholder; - let hidden; - return (value) => { - if (value) { - if (hidden) { - hidden = false; - placeholder.replaceWith(element); - } - onShow?.(element, value); - } else { - if (!placeholder) { - placeholder = document.createComment(""); - } - hidden = true; - element.replaceWith(placeholder); - onHide?.(element); - } - }; -} diff --git a/web/scripts/utils.js b/web/scripts/utils.js deleted file mode 100644 index cda7600f6..000000000 --- a/web/scripts/utils.js +++ /dev/null @@ -1,156 +0,0 @@ -import { $el } from "./ui.js"; -import { api } from "./api.js"; - -// Simple date formatter -const parts = { - d: (d) => d.getDate(), - M: (d) => d.getMonth() + 1, - h: (d) => d.getHours(), - m: (d) => d.getMinutes(), - s: (d) => d.getSeconds(), -}; -const format = - Object.keys(parts) - .map((k) => k + k + "?") - .join("|") + "|yyy?y?"; - -function formatDate(text, date) { - return text.replace(new RegExp(format, "g"), function (text) { - if (text === "yy") return (date.getFullYear() + "").substring(2); - if (text === "yyyy") return date.getFullYear(); - if (text[0] in parts) { - const p = parts[text[0]](date); - return (p + "").padStart(text.length, "0"); - } - return text; - }); -} - - -export function clone(obj) { - try { - if (typeof structuredClone !== "undefined") { - return structuredClone(obj); - } - } catch (error) { - // structuredClone is stricter than using JSON.parse/stringify so fallback to that - } - - return JSON.parse(JSON.stringify(obj)); -} - -export function applyTextReplacements(app, value) { - return value.replace(/%([^%]+)%/g, function (match, text) { - const split = text.split("."); - if (split.length !== 2) { - // Special handling for dates - if (split[0].startsWith("date:")) { - return formatDate(split[0].substring(5), new Date()); - } - - if (text !== "width" && text !== "height") { - // Dont warn on standard replacements - console.warn("Invalid replacement pattern", text); - } - return match; - } - - // Find node with matching S&R property name - let nodes = app.graph._nodes.filter((n) => n.properties?.["Node name for S&R"] === split[0]); - // If we cant, see if there is a node with that title - if (!nodes.length) { - nodes = app.graph._nodes.filter((n) => n.title === split[0]); - } - if (!nodes.length) { - console.warn("Unable to find node", split[0]); - return match; - } - - if (nodes.length > 1) { - console.warn("Multiple nodes matched", split[0], "using first match"); - } - - const node = nodes[0]; - - const widget = node.widgets?.find((w) => w.name === split[1]); - if (!widget) { - console.warn("Unable to find widget", split[1], "on node", split[0], node); - return match; - } - - return ((widget.value ?? "") + "").replaceAll(/\/|\\/g, "_"); - }); -} - -export async function addStylesheet(urlOrFile, relativeTo) { - return new Promise((res, rej) => { - let url; - if (urlOrFile.endsWith(".js")) { - url = urlOrFile.substr(0, urlOrFile.length - 2) + "css"; - } else { - url = new URL(urlOrFile, relativeTo ?? `${window.location.protocol}//${window.location.host}`).toString(); - } - $el("link", { - parent: document.head, - rel: "stylesheet", - type: "text/css", - href: url, - onload: res, - onerror: rej, - }); - }); -} - -/** - * @param { string } filename - * @param { Blob } blob - */ -export function downloadBlob(filename, blob) { - const url = URL.createObjectURL(blob); - const a = $el("a", { - href: url, - download: filename, - style: { display: "none" }, - parent: document.body, - }); - a.click(); - setTimeout(function () { - a.remove(); - window.URL.revokeObjectURL(url); - }, 0); -} - -/** - * @template T - * @param {string} name - * @param {T} [defaultValue] - * @param {(currentValue: any, previousValue: any)=>void} [onChanged] - * @returns {T} - */ -export function prop(target, name, defaultValue, onChanged) { - let currentValue; - Object.defineProperty(target, name, { - get() { - return currentValue; - }, - set(newValue) { - const prevValue = currentValue; - currentValue = newValue; - onChanged?.(currentValue, prevValue, target, name); - }, - }); - return defaultValue; -} - -export function getStorageValue(id) { - const clientId = api.clientId ?? api.initialClientId; - return (clientId && sessionStorage.getItem(`${id}:${clientId}`)) ?? localStorage.getItem(id); -} - -export function setStorageValue(id, value) { - const clientId = api.clientId ?? api.initialClientId; - if (clientId) { - sessionStorage.setItem(`${id}:${clientId}`, value); - } - localStorage.setItem(id, value); -} \ No newline at end of file diff --git a/web/scripts/widgets.js b/web/scripts/widgets.js deleted file mode 100644 index 59b17fd13..000000000 --- a/web/scripts/widgets.js +++ /dev/null @@ -1,533 +0,0 @@ -import { api } from "./api.js" -import "./domWidget.js"; - -let controlValueRunBefore = false; -export function updateControlWidgetLabel(widget) { - let replacement = "after"; - let find = "before"; - if (controlValueRunBefore) { - [find, replacement] = [replacement, find] - } - widget.label = (widget.label ?? widget.name).replace(find, replacement); -} - -const IS_CONTROL_WIDGET = Symbol(); -const HAS_EXECUTED = Symbol(); - -function getNumberDefaults(inputData, defaultStep, precision, enable_rounding) { - let defaultVal = inputData[1]["default"]; - let { min, max, step, round} = inputData[1]; - - if (defaultVal == undefined) defaultVal = 0; - if (min == undefined) min = 0; - if (max == undefined) max = 2048; - if (step == undefined) step = defaultStep; - // precision is the number of decimal places to show. - // by default, display the the smallest number of decimal places such that changes of size step are visible. - if (precision == undefined) { - precision = Math.max(-Math.floor(Math.log10(step)),0); - } - - if (enable_rounding && (round == undefined || round === true)) { - // by default, round the value to those decimal places shown. - round = Math.round(1000000*Math.pow(0.1,precision))/1000000; - } - - return { val: defaultVal, config: { min, max, step: 10.0 * step, round, precision } }; -} - -export function addValueControlWidget(node, targetWidget, defaultValue = "randomize", values, widgetName, inputData) { - let name = inputData[1]?.control_after_generate; - if(typeof name !== "string") { - name = widgetName; - } - const widgets = addValueControlWidgets(node, targetWidget, defaultValue, { - addFilterList: false, - controlAfterGenerateName: name - }, inputData); - return widgets[0]; -} - -export function addValueControlWidgets(node, targetWidget, defaultValue = "randomize", options, inputData) { - if (!defaultValue) defaultValue = "randomize"; - if (!options) options = {}; - - const getName = (defaultName, optionName) => { - let name = defaultName; - if (options[optionName]) { - name = options[optionName]; - } else if (typeof inputData?.[1]?.[defaultName] === "string") { - name = inputData?.[1]?.[defaultName]; - } else if (inputData?.[1]?.control_prefix) { - name = inputData?.[1]?.control_prefix + " " + name - } - return name; - } - - const widgets = []; - const valueControl = node.addWidget( - "combo", - getName("control_after_generate", "controlAfterGenerateName"), - defaultValue, - function () {}, - { - values: ["fixed", "increment", "decrement", "randomize"], - serialize: false, // Don't include this in prompt. - } - ); - valueControl.tooltip = "Allows the linked widget to be changed automatically, for example randomizing the noise seed."; - valueControl[IS_CONTROL_WIDGET] = true; - updateControlWidgetLabel(valueControl); - widgets.push(valueControl); - - const isCombo = targetWidget.type === "combo"; - let comboFilter; - if (isCombo) { - valueControl.options.values.push("increment-wrap"); - } - if (isCombo && options.addFilterList !== false) { - comboFilter = node.addWidget( - "string", - getName("control_filter_list", "controlFilterListName"), - "", - function () {}, - { - serialize: false, // Don't include this in prompt. - } - ); - updateControlWidgetLabel(comboFilter); - comboFilter.tooltip = "Allows for filtering the list of values when changing the value via the control generate mode. Allows for RegEx matches in the format /abc/ to only filter to values containing 'abc'." - - widgets.push(comboFilter); - } - - const applyWidgetControl = () => { - var v = valueControl.value; - - if (isCombo && v !== "fixed") { - let values = targetWidget.options.values; - const filter = comboFilter?.value; - if (filter) { - let check; - if (filter.startsWith("/") && filter.endsWith("/")) { - try { - const regex = new RegExp(filter.substring(1, filter.length - 1)); - check = (item) => regex.test(item); - } catch (error) { - console.error("Error constructing RegExp filter for node " + node.id, filter, error); - } - } - if (!check) { - const lower = filter.toLocaleLowerCase(); - check = (item) => item.toLocaleLowerCase().includes(lower); - } - values = values.filter(item => check(item)); - if (!values.length && targetWidget.options.values.length) { - console.warn("Filter for node " + node.id + " has filtered out all items", filter); - } - } - let current_index = values.indexOf(targetWidget.value); - let current_length = values.length; - - switch (v) { - case "increment": - current_index += 1; - break; - case "increment-wrap": - current_index += 1; - if ( current_index >= current_length ) { - current_index = 0; - } - break; - case "decrement": - current_index -= 1; - break; - case "randomize": - current_index = Math.floor(Math.random() * current_length); - default: - break; - } - current_index = Math.max(0, current_index); - current_index = Math.min(current_length - 1, current_index); - if (current_index >= 0) { - let value = values[current_index]; - targetWidget.value = value; - targetWidget.callback(value); - } - } else { - //number - let min = targetWidget.options.min; - let max = targetWidget.options.max; - // limit to something that javascript can handle - max = Math.min(1125899906842624, max); - min = Math.max(-1125899906842624, min); - let range = (max - min) / (targetWidget.options.step / 10); - - //adjust values based on valueControl Behaviour - switch (v) { - case "fixed": - break; - case "increment": - targetWidget.value += targetWidget.options.step / 10; - break; - case "decrement": - targetWidget.value -= targetWidget.options.step / 10; - break; - case "randomize": - targetWidget.value = Math.floor(Math.random() * range) * (targetWidget.options.step / 10) + min; - default: - break; - } - /*check if values are over or under their respective - * ranges and set them to min or max.*/ - if (targetWidget.value < min) targetWidget.value = min; - - if (targetWidget.value > max) - targetWidget.value = max; - targetWidget.callback(targetWidget.value); - } - }; - - valueControl.beforeQueued = () => { - if (controlValueRunBefore) { - // Don't run on first execution - if (valueControl[HAS_EXECUTED]) { - applyWidgetControl(); - } - } - valueControl[HAS_EXECUTED] = true; - }; - - valueControl.afterQueued = () => { - if (!controlValueRunBefore) { - applyWidgetControl(); - } - }; - - return widgets; -}; - -function seedWidget(node, inputName, inputData, app, widgetName) { - const seed = createIntWidget(node, inputName, inputData, app, true); - const seedControl = addValueControlWidget(node, seed.widget, "randomize", undefined, widgetName, inputData); - - seed.widget.linkedWidgets = [seedControl]; - return seed; -} - -function createIntWidget(node, inputName, inputData, app, isSeedInput) { - const control = inputData[1]?.control_after_generate; - if (!isSeedInput && control) { - return seedWidget(node, inputName, inputData, app, typeof control === "string" ? control : undefined); - } - - let widgetType = isSlider(inputData[1]["display"], app); - const { val, config } = getNumberDefaults(inputData, 1, 0, true); - Object.assign(config, { precision: 0 }); - return { - widget: node.addWidget( - widgetType, - inputName, - val, - function (v) { - const s = this.options.step / 10; - let sh = this.options.min % s; - if (isNaN(sh)) { - sh = 0; - } - this.value = Math.round((v - sh) / s) * s + sh; - }, - config - ), - }; -} - -function addMultilineWidget(node, name, opts, app) { - const inputEl = document.createElement("textarea"); - inputEl.className = "comfy-multiline-input"; - inputEl.value = opts.defaultVal; - inputEl.placeholder = opts.placeholder || name; - - const widget = node.addDOMWidget(name, "customtext", inputEl, { - getValue() { - return inputEl.value; - }, - setValue(v) { - inputEl.value = v; - }, - }); - widget.inputEl = inputEl; - - inputEl.addEventListener("input", () => { - widget.callback?.(widget.value); - }); - - return { minWidth: 400, minHeight: 200, widget }; -} - -function isSlider(display, app) { - if (app.ui.settings.getSettingValue("Comfy.DisableSliders")) { - return "number" - } - - return (display==="slider") ? "slider" : "number" -} - -export function initWidgets(app) { - app.ui.settings.addSetting({ - id: "Comfy.WidgetControlMode", - name: "Widget Value Control Mode", - type: "combo", - defaultValue: "after", - options: ["before", "after"], - tooltip: "Controls when widget values are updated (randomize/increment/decrement), either before the prompt is queued or after.", - onChange(value) { - controlValueRunBefore = value === "before"; - for (const n of app.graph._nodes) { - if (!n.widgets) continue; - for (const w of n.widgets) { - if (w[IS_CONTROL_WIDGET]) { - updateControlWidgetLabel(w); - if (w.linkedWidgets) { - for (const l of w.linkedWidgets) { - updateControlWidgetLabel(l); - } - } - } - } - } - app.graph.setDirtyCanvas(true); - }, - }); -} - -export const ComfyWidgets = { - "INT:seed": seedWidget, - "INT:noise_seed": seedWidget, - FLOAT(node, inputName, inputData, app) { - let widgetType = isSlider(inputData[1]["display"], app); - let precision = app.ui.settings.getSettingValue("Comfy.FloatRoundingPrecision"); - let disable_rounding = app.ui.settings.getSettingValue("Comfy.DisableFloatRounding") - if (precision == 0) precision = undefined; - const { val, config } = getNumberDefaults(inputData, 0.5, precision, !disable_rounding); - return { widget: node.addWidget(widgetType, inputName, val, - function (v) { - if (config.round) { - this.value = Math.round((v + Number.EPSILON)/config.round)*config.round; - if (this.value > config.max) this.value = config.max; - if (this.value < config.min) this.value = config.min; - } else { - this.value = v; - } - }, config) }; - }, - INT(node, inputName, inputData, app) { - return createIntWidget(node, inputName, inputData, app); - }, - BOOLEAN(node, inputName, inputData) { - let defaultVal = false; - let options = {}; - if (inputData[1]) { - if (inputData[1].default) - defaultVal = inputData[1].default; - if (inputData[1].label_on) - options["on"] = inputData[1].label_on; - if (inputData[1].label_off) - options["off"] = inputData[1].label_off; - } - return { - widget: node.addWidget( - "toggle", - inputName, - defaultVal, - () => {}, - options, - ) - }; - }, - STRING(node, inputName, inputData, app) { - const defaultVal = inputData[1].default || ""; - const multiline = !!inputData[1].multiline; - - let res; - if (multiline) { - res = addMultilineWidget(node, inputName, { defaultVal, ...inputData[1] }, app); - } else { - res = { widget: node.addWidget("text", inputName, defaultVal, () => {}, {}) }; - } - - if(inputData[1].dynamicPrompts != undefined) - res.widget.dynamicPrompts = inputData[1].dynamicPrompts; - - return res; - }, - COMBO(node, inputName, inputData) { - const type = inputData[0]; - let defaultValue = type[0]; - if (inputData[1] && inputData[1].default) { - defaultValue = inputData[1].default; - } - const res = { widget: node.addWidget("combo", inputName, defaultValue, () => {}, { values: type }) }; - if (inputData[1]?.control_after_generate) { - res.widget.linkedWidgets = addValueControlWidgets(node, res.widget, undefined, undefined, inputData); - } - return res; - }, - IMAGEUPLOAD(node, inputName, inputData, app) { - const imageWidget = node.widgets.find((w) => w.name === (inputData[1]?.widget ?? "image")); - let uploadWidget; - - function showImage(name) { - const img = new Image(); - img.onload = () => { - node.imgs = [img]; - app.graph.setDirtyCanvas(true); - }; - let folder_separator = name.lastIndexOf("/"); - let subfolder = ""; - if (folder_separator > -1) { - subfolder = name.substring(0, folder_separator); - name = name.substring(folder_separator + 1); - } - img.src = api.apiURL(`/view?filename=${encodeURIComponent(name)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`); - node.setSizeForImage?.(); - } - - var default_value = imageWidget.value; - Object.defineProperty(imageWidget, "value", { - set : function(value) { - this._real_value = value; - }, - - get : function() { - let value = ""; - if (this._real_value) { - value = this._real_value; - } else { - return default_value; - } - - if (value.filename) { - let real_value = value; - value = ""; - if (real_value.subfolder) { - value = real_value.subfolder + "/"; - } - - value += real_value.filename; - - if(real_value.type && real_value.type !== "input") - value += ` [${real_value.type}]`; - } - return value; - } - }); - - // Add our own callback to the combo widget to render an image when it changes - const cb = node.callback; - imageWidget.callback = function () { - showImage(imageWidget.value); - if (cb) { - return cb.apply(this, arguments); - } - }; - - // On load if we have a value then render the image - // The value isnt set immediately so we need to wait a moment - // No change callbacks seem to be fired on initial setting of the value - requestAnimationFrame(() => { - if (imageWidget.value) { - showImage(imageWidget.value); - } - }); - - async function uploadFile(file, updateNode, pasted = false) { - try { - // Wrap file in formdata so it includes filename - const body = new FormData(); - body.append("image", file); - if (pasted) body.append("subfolder", "pasted"); - const resp = await api.fetchApi("/upload/image", { - method: "POST", - body, - }); - - if (resp.status === 200) { - const data = await resp.json(); - // Add the file to the dropdown list and update the widget value - let path = data.name; - if (data.subfolder) path = data.subfolder + "/" + path; - - if (!imageWidget.options.values.includes(path)) { - imageWidget.options.values.push(path); - } - - if (updateNode) { - showImage(path); - imageWidget.value = path; - } - } else { - alert(resp.status + " - " + resp.statusText); - } - } catch (error) { - alert(error); - } - } - - const fileInput = document.createElement("input"); - Object.assign(fileInput, { - type: "file", - accept: "image/jpeg,image/png,image/webp", - style: "display: none", - onchange: async () => { - if (fileInput.files.length) { - await uploadFile(fileInput.files[0], true); - } - }, - }); - document.body.append(fileInput); - - // Create the button widget for selecting the files - uploadWidget = node.addWidget("button", inputName, "image", () => { - fileInput.click(); - }); - uploadWidget.label = "choose file to upload"; - uploadWidget.serialize = false; - - // Add handler to check if an image is being dragged over our node - node.onDragOver = function (e) { - if (e.dataTransfer && e.dataTransfer.items) { - const image = [...e.dataTransfer.items].find((f) => f.kind === "file"); - return !!image; - } - - return false; - }; - - // On drop upload files - node.onDragDrop = function (e) { - console.log("onDragDrop called"); - let handled = false; - for (const file of e.dataTransfer.files) { - if (file.type.startsWith("image/")) { - uploadFile(file, !handled); // Dont await these, any order is fine, only update on first one - handled = true; - } - } - - return handled; - }; - - node.pasteFile = function(file) { - if (file.type.startsWith("image/")) { - const is_pasted = (file.name === "image.png") && - (file.lastModified - Date.now() < 2000); - uploadFile(file, true, is_pasted); - return true; - } - return false; - } - - return { widget: uploadWidget }; - }, -}; diff --git a/web/scripts/workflows.js b/web/scripts/workflows.js deleted file mode 100644 index d38b6f5fc..000000000 --- a/web/scripts/workflows.js +++ /dev/null @@ -1,450 +0,0 @@ -// @ts-check - -import { api } from "./api.js"; -import { ChangeTracker } from "./changeTracker.js"; -import { ComfyAsyncDialog } from "./ui/components/asyncDialog.js"; -import { getStorageValue, setStorageValue } from "./utils.js"; - -function appendJsonExt(path) { - if (!path.toLowerCase().endsWith(".json")) { - path += ".json"; - } - return path; -} - -export function trimJsonExt(path) { - return path?.replace(/\.json$/, ""); -} - -export class ComfyWorkflowManager extends EventTarget { - /** @type {string | null} */ - #activePromptId = null; - #unsavedCount = 0; - #activeWorkflow; - - /** @type {Record} */ - workflowLookup = {}; - /** @type {Array} */ - workflows = []; - /** @type {Array} */ - openWorkflows = []; - /** @type {Record}>} */ - queuedPrompts = {}; - - get activeWorkflow() { - return this.#activeWorkflow ?? this.openWorkflows[0]; - } - - get activePromptId() { - return this.#activePromptId; - } - - get activePrompt() { - return this.queuedPrompts[this.#activePromptId]; - } - - /** - * @param {import("./app.js").ComfyApp} app - */ - constructor(app) { - super(); - this.app = app; - ChangeTracker.init(app); - - this.#bindExecutionEvents(); - } - - #bindExecutionEvents() { - // TODO: on reload, set active prompt based on the latest ws message - - const emit = () => this.dispatchEvent(new CustomEvent("execute", { detail: this.activePrompt })); - let executing = null; - api.addEventListener("execution_start", (e) => { - this.#activePromptId = e.detail.prompt_id; - - // This event can fire before the event is stored, so put a placeholder - this.queuedPrompts[this.#activePromptId] ??= { nodes: {} }; - emit(); - }); - api.addEventListener("execution_cached", (e) => { - if (!this.activePrompt) return; - for (const n of e.detail.nodes) { - this.activePrompt.nodes[n] = true; - } - emit(); - }); - api.addEventListener("executed", (e) => { - if (!this.activePrompt) return; - this.activePrompt.nodes[e.detail.node] = true; - emit(); - }); - api.addEventListener("executing", (e) => { - if (!this.activePrompt) return; - - if (executing) { - // Seems sometimes nodes that are cached fire executing but not executed - this.activePrompt.nodes[executing] = true; - } - executing = e.detail; - if (!executing) { - delete this.queuedPrompts[this.#activePromptId]; - this.#activePromptId = null; - } - emit(); - }); - } - - async loadWorkflows() { - try { - let favorites; - const resp = await api.getUserData("workflows/.index.json"); - let info; - if (resp.status === 200) { - info = await resp.json(); - favorites = new Set(info?.favorites ?? []); - } else { - favorites = new Set(); - } - - const workflows = (await api.listUserData("workflows", true, true)).map((w) => { - let workflow = this.workflowLookup[w[0]]; - if (!workflow) { - workflow = new ComfyWorkflow(this, w[0], w.slice(1), favorites.has(w[0])); - this.workflowLookup[workflow.path] = workflow; - } - return workflow; - }); - - this.workflows = workflows; - } catch (error) { - alert("Error loading workflows: " + (error.message ?? error)); - this.workflows = []; - } - } - - async saveWorkflowMetadata() { - await api.storeUserData("workflows/.index.json", { - favorites: [...this.workflows.filter((w) => w.isFavorite).map((w) => w.path)], - }); - } - - /** - * @param {string | ComfyWorkflow | null} workflow - */ - setWorkflow(workflow) { - if (workflow && typeof workflow === "string") { - // Selected by path, i.e. on reload of last workflow - const found = this.workflows.find((w) => w.path === workflow); - if (found) { - workflow = found; - workflow.unsaved = !workflow || getStorageValue("Comfy.PreviousWorkflowUnsaved") === "true"; - } - } - - if (!(workflow instanceof ComfyWorkflow)) { - // Still not found, either reloading a deleted workflow or blank - workflow = new ComfyWorkflow(this, workflow || "Unsaved Workflow" + (this.#unsavedCount++ ? ` (${this.#unsavedCount})` : "")); - } - - const index = this.openWorkflows.indexOf(workflow); - if (index === -1) { - // Opening a new workflow - this.openWorkflows.push(workflow); - } - - this.#activeWorkflow = workflow; - - setStorageValue("Comfy.PreviousWorkflow", this.activeWorkflow.path ?? ""); - this.dispatchEvent(new CustomEvent("changeWorkflow")); - } - - storePrompt({ nodes, id }) { - this.queuedPrompts[id] ??= {}; - this.queuedPrompts[id].nodes = { - ...nodes.reduce((p, n) => { - p[n] = false; - return p; - }, {}), - ...this.queuedPrompts[id].nodes, - }; - this.queuedPrompts[id].workflow = this.activeWorkflow; - } - - /** - * @param {ComfyWorkflow} workflow - */ - async closeWorkflow(workflow, warnIfUnsaved = true) { - if (!workflow.isOpen) { - return true; - } - if (workflow.unsaved && warnIfUnsaved) { - const res = await ComfyAsyncDialog.prompt({ - title: "Save Changes?", - message: `Do you want to save changes to "${workflow.path ?? workflow.name}" before closing?`, - actions: ["Yes", "No", "Cancel"], - }); - if (res === "Yes") { - const active = this.activeWorkflow; - if (active !== workflow) { - // We need to switch to the workflow to save it - await workflow.load(); - } - - if (!(await workflow.save())) { - // Save was canceled, restore the previous workflow - if (active !== workflow) { - await active.load(); - } - return; - } - } else if (res === "Cancel") { - return; - } - } - workflow.changeTracker = null; - this.openWorkflows.splice(this.openWorkflows.indexOf(workflow), 1); - if (this.openWorkflows.length) { - this.#activeWorkflow = this.openWorkflows[0]; - await this.#activeWorkflow.load(); - } else { - // Load default - await this.app.loadGraphData(); - } - } -} - -export class ComfyWorkflow { - #name; - #path; - #pathParts; - #isFavorite = false; - /** @type {ChangeTracker | null} */ - changeTracker = null; - unsaved = false; - - get name() { - return this.#name; - } - - get path() { - return this.#path; - } - - get pathParts() { - return this.#pathParts; - } - - get isFavorite() { - return this.#isFavorite; - } - - get isOpen() { - return !!this.changeTracker; - } - - /** - * @overload - * @param {ComfyWorkflowManager} manager - * @param {string} path - */ - /** - * @overload - * @param {ComfyWorkflowManager} manager - * @param {string} path - * @param {string[]} pathParts - * @param {boolean} isFavorite - */ - /** - * @param {ComfyWorkflowManager} manager - * @param {string} path - * @param {string[]} [pathParts] - * @param {boolean} [isFavorite] - */ - constructor(manager, path, pathParts, isFavorite) { - this.manager = manager; - if (pathParts) { - this.#updatePath(path, pathParts); - this.#isFavorite = isFavorite; - } else { - this.#name = path; - this.unsaved = true; - } - } - - /** - * @param {string} path - * @param {string[]} [pathParts] - */ - #updatePath(path, pathParts) { - this.#path = path; - - if (!pathParts) { - if (!path.includes("\\")) { - pathParts = path.split("/"); - } else { - pathParts = path.split("\\"); - } - } - - this.#pathParts = pathParts; - this.#name = trimJsonExt(pathParts[pathParts.length - 1]); - } - - async getWorkflowData() { - const resp = await api.getUserData("workflows/" + this.path); - if (resp.status !== 200) { - alert(`Error loading workflow file '${this.path}': ${resp.status} ${resp.statusText}`); - return; - } - return await resp.json(); - } - - load = async () => { - if (this.isOpen) { - await this.manager.app.loadGraphData(this.changeTracker.activeState, true, true, this); - } else { - const data = await this.getWorkflowData(); - if (!data) return; - await this.manager.app.loadGraphData(data, true, true, this); - } - }; - - async save(saveAs = false) { - if (!this.path || saveAs) { - return !!(await this.#save(null, false)); - } else { - return !!(await this.#save(this.path, true)); - } - } - - /** - * @param {boolean} value - */ - async favorite(value) { - try { - if (this.#isFavorite === value) return; - this.#isFavorite = value; - await this.manager.saveWorkflowMetadata(); - this.manager.dispatchEvent(new CustomEvent("favorite", { detail: this })); - } catch (error) { - alert("Error favoriting workflow " + this.path + "\n" + (error.message ?? error)); - } - } - - /** - * @param {string} path - */ - async rename(path) { - path = appendJsonExt(path); - let resp = await api.moveUserData("workflows/" + this.path, "workflows/" + path); - - if (resp.status === 409) { - if (!confirm(`Workflow '${path}' already exists, do you want to overwrite it?`)) return resp; - resp = await api.moveUserData("workflows/" + this.path, "workflows/" + path, { overwrite: true }); - } - - if (resp.status !== 200) { - alert(`Error renaming workflow file '${this.path}': ${resp.status} ${resp.statusText}`); - return; - } - - const isFav = this.isFavorite; - if (isFav) { - await this.favorite(false); - } - path = (await resp.json()).substring("workflows/".length); - this.#updatePath(path, null); - if (isFav) { - await this.favorite(true); - } - this.manager.dispatchEvent(new CustomEvent("rename", { detail: this })); - setStorageValue("Comfy.PreviousWorkflow", this.path ?? ""); - } - - async insert() { - const data = await this.getWorkflowData(); - if (!data) return; - - const old = localStorage.getItem("litegrapheditor_clipboard"); - const graph = new LGraph(data); - const canvas = new LGraphCanvas(null, graph, { skip_events: true, skip_render: true }); - canvas.selectNodes(); - canvas.copyToClipboard(); - this.manager.app.canvas.pasteFromClipboard(); - localStorage.setItem("litegrapheditor_clipboard", old); - } - - async delete() { - // TODO: fix delete of current workflow - should mark workflow as unsaved and when saving use old name by default - - try { - if (this.isFavorite) { - await this.favorite(false); - } - await api.deleteUserData("workflows/" + this.path); - this.unsaved = true; - this.#path = null; - this.#pathParts = null; - this.manager.workflows.splice(this.manager.workflows.indexOf(this), 1); - this.manager.dispatchEvent(new CustomEvent("delete", { detail: this })); - } catch (error) { - alert(`Error deleting workflow: ${error.message || error}`); - } - } - - track() { - if (this.changeTracker) { - this.changeTracker.restore(); - } else { - this.changeTracker = new ChangeTracker(this); - } - } - - /** - * @param {string|null} path - * @param {boolean} overwrite - */ - async #save(path, overwrite) { - if (!path) { - path = prompt("Save workflow as:", trimJsonExt(this.path) ?? this.name ?? "workflow"); - if (!path) return; - } - - path = appendJsonExt(path); - - const p = await this.manager.app.graphToPrompt(); - const json = JSON.stringify(p.workflow, null, 2); - let resp = await api.storeUserData("workflows/" + path, json, { stringify: false, throwOnError: false, overwrite }); - if (resp.status === 409) { - if (!confirm(`Workflow '${path}' already exists, do you want to overwrite it?`)) return; - resp = await api.storeUserData("workflows/" + path, json, { stringify: false }); - } - - if (resp.status !== 200) { - alert(`Error saving workflow '${this.path}': ${resp.status} ${resp.statusText}`); - return; - } - - path = (await resp.json()).substring("workflows/".length); - - if (!this.path) { - // Saved new workflow, patch this instance - this.#updatePath(path, null); - await this.manager.loadWorkflows(); - this.unsaved = false; - this.manager.dispatchEvent(new CustomEvent("rename", { detail: this })); - setStorageValue("Comfy.PreviousWorkflow", this.path ?? ""); - } else if (path !== this.path) { - // Saved as, open the new copy - await this.manager.loadWorkflows(); - const workflow = this.manager.workflowLookup[path]; - await workflow.load(); - } else { - // Normal save - this.unsaved = false; - this.manager.dispatchEvent(new CustomEvent("save", { detail: this })); - } - - return true; - } -} diff --git a/web/style.css b/web/style.css deleted file mode 100644 index 7774bfede..000000000 --- a/web/style.css +++ /dev/null @@ -1,664 +0,0 @@ -@import url("scripts/ui/menu/menu.css"); - -:root { - --fg-color: #000; - --bg-color: #fff; - --comfy-menu-bg: #353535; - --comfy-input-bg: #222; - --input-text: #ddd; - --descrip-text: #999; - --drag-text: #ccc; - --error-text: #ff4444; - --border-color: #4e4e4e; - --tr-even-bg-color: #222; - --tr-odd-bg-color: #353535; - --primary-bg: #236692; - --primary-fg: #ffffff; - --primary-hover-bg: #3485bb; - --primary-hover-fg: #ffffff; - --content-bg: #e0e0e0; - --content-fg: #000; - --content-hover-bg: #adadad; - --content-hover-fg: #000; -} - -@media (prefers-color-scheme: dark) { - :root { - --fg-color: #fff; - --bg-color: #202020; - --content-bg: #4e4e4e; - --content-fg: #fff; - --content-hover-bg: #222; - --content-hover-fg: #fff; - } -} - -body { - width: 100vw; - height: 100vh; - margin: 0; - overflow: hidden; - background-color: var(--bg-color); - color: var(--fg-color); - grid-template-columns: auto 1fr auto; - grid-template-rows: auto 1fr auto; - min-height: -webkit-fill-available; - max-height: -webkit-fill-available; - min-width: -webkit-fill-available; - max-width: -webkit-fill-available; -} - -.comfyui-body-top { - order: -5; - grid-column: 1/-1; - z-index: 10; - display: flex; - flex-direction: column; -} - -.comfyui-body-left { - order: -4; - z-index: 10; - display: flex; -} - -#graph-canvas { - width: 100%; - height: 100%; - order: -3; -} - -.comfyui-body-right { - order: -2; - z-index: 10; - display: flex; -} - -.comfyui-body-bottom { - order: -1; - grid-column: 1/-1; - z-index: 10; - display: flex; - flex-direction: column; -} - -.comfy-multiline-input { - background-color: var(--comfy-input-bg); - color: var(--input-text); - overflow: hidden; - overflow-y: auto; - padding: 2px; - resize: none; - border: none; - box-sizing: border-box; - font-size: 10px; -} - -.comfy-modal { - display: none; /* Hidden by default */ - position: fixed; /* Stay in place */ - z-index: 100; /* Sit on top */ - padding: 30px 30px 10px 30px; - background-color: var(--comfy-menu-bg); /* Modal background */ - color: var(--error-text); - box-shadow: 0 0 20px #888888; - border-radius: 10px; - top: 50%; - left: 50%; - max-width: 80vw; - max-height: 80vh; - transform: translate(-50%, -50%); - overflow: hidden; - justify-content: center; - font-family: monospace; - font-size: 15px; -} - -.comfy-modal-content { - display: flex; - flex-direction: column; -} - -.comfy-modal p { - overflow: auto; - white-space: pre-line; /* This will respect line breaks */ - margin-bottom: 20px; /* Add some margin between the text and the close button*/ -} - -.comfy-modal select, -.comfy-modal input[type=button], -.comfy-modal input[type=checkbox] { - margin: 3px 3px 3px 4px; -} - -.comfy-menu-hamburger { - position: fixed; - top: 10px; - z-index: 9999; - right: 10px; - width: 30px; - display: none; - gap: 8px; - flex-direction: column; - cursor: pointer; -} -.comfy-menu-hamburger div { - height: 3px; - width: 100%; - border-radius: 20px; - background-color: white; -} - -.comfy-menu { - font-size: 15px; - position: absolute; - top: 50%; - right: 0; - text-align: center; - z-index: 999; - width: 170px; - display: flex; - flex-direction: column; - align-items: center; - color: var(--descrip-text); - background-color: var(--comfy-menu-bg); - font-family: sans-serif; - padding: 10px; - border-radius: 0 8px 8px 8px; - box-shadow: 3px 3px 8px rgba(0, 0, 0, 0.4); -} - -.comfy-menu-header { - display: flex; -} - -.comfy-menu-actions { - display: flex; - gap: 3px; - align-items: center; - height: 20px; - position: relative; - top: -1px; - font-size: 22px; -} - -.comfy-menu .comfy-menu-actions button { - background-color: rgba(0, 0, 0, 0); - padding: 0; - border: none; - cursor: pointer; - font-size: inherit; -} - -.comfy-menu .comfy-menu-actions .comfy-settings-btn { - font-size: 0.6em; -} - -button.comfy-close-menu-btn { - font-size: 1em; - line-height: 12px; - color: #ccc; - position: relative; - top: -1px; -} - -.comfy-menu-queue-size { - flex: auto; -} - -.comfy-menu button, -.comfy-modal button { - font-size: 20px; -} - -.comfy-menu-btns { - margin-bottom: 10px; - width: 100%; -} - -.comfy-menu-btns button { - font-size: 10px; - width: 50%; - color: var(--descrip-text) !important; -} - -.comfy-menu > button { - width: 100%; -} - -.comfy-btn, -.comfy-menu > button, -.comfy-menu-btns button, -.comfy-menu .comfy-list button, -.comfy-modal button { - color: var(--input-text); - background-color: var(--comfy-input-bg); - border-radius: 8px; - border-color: var(--border-color); - border-style: solid; - margin-top: 2px; -} - -.comfy-btn:hover:not(:disabled), -.comfy-menu > button:hover, -.comfy-menu-btns button:hover, -.comfy-menu .comfy-list button:hover, -.comfy-modal button:hover, -.comfy-menu-actions button:hover { - filter: brightness(1.2); - will-change: transform; - cursor: pointer; -} - -span.drag-handle { - width: 10px; - height: 20px; - display: inline-block; - overflow: hidden; - line-height: 5px; - padding: 3px 4px; - cursor: move; - vertical-align: middle; - margin-top: -.4em; - margin-left: -.2em; - font-size: 12px; - font-family: sans-serif; - letter-spacing: 2px; - color: var(--drag-text); - text-shadow: 1px 0 1px black; -} - -span.drag-handle::after { - content: '.. .. ..'; -} - -.comfy-queue-btn { - width: 100%; -} - -.comfy-list { - color: var(--descrip-text); - background-color: var(--comfy-menu-bg); - margin-bottom: 10px; - border-color: var(--border-color); - border-style: solid; -} - -.comfy-list-items { - overflow-y: scroll; - max-height: 100px; - min-height: 25px; - background-color: var(--comfy-input-bg); - padding: 5px; -} - -.comfy-list h4 { - min-width: 160px; - margin: 0; - padding: 3px; - font-weight: normal; -} - -.comfy-list-items button { - font-size: 10px; -} - -.comfy-list-actions { - margin: 5px; - display: flex; - gap: 5px; - justify-content: center; -} - -.comfy-list-actions button { - font-size: 12px; -} - -button.comfy-queue-btn { - margin: 6px 0 !important; -} - -.comfy-modal.comfy-settings, -.comfy-modal.comfy-manage-templates { - text-align: center; - font-family: sans-serif; - color: var(--descrip-text); - z-index: 99; -} - -.comfy-modal.comfy-settings input[type="range"] { - vertical-align: middle; -} - -.comfy-modal.comfy-settings input[type="range"] + input[type="number"] { - width: 3.5em; -} - -.comfy-modal input, -.comfy-modal select { - color: var(--input-text); - background-color: var(--comfy-input-bg); - border-radius: 8px; - border-color: var(--border-color); - border-style: solid; - font-size: inherit; -} - -.comfy-tooltip-indicator { - text-decoration: underline; - text-decoration-style: dashed; -} - -@media only screen and (max-height: 850px) { - .comfy-menu { - top: 0 !important; - bottom: 0 !important; - left: auto !important; - right: 0 !important; - border-radius: 0; - } - - .comfy-menu span.drag-handle { - display: none; - } - - .comfy-menu-queue-size { - flex: unset; - } - - .comfy-menu-header { - justify-content: space-between; - } - .comfy-menu-actions { - gap: 10px; - font-size: 28px; - } -} - -/* Input popup */ - -.graphdialog { - min-height: 1em; - background-color: var(--comfy-menu-bg); -} - -.graphdialog .name { - font-size: 14px; - font-family: sans-serif; - color: var(--descrip-text); -} - -.graphdialog button { - margin-top: unset; - vertical-align: unset; - height: 1.6em; - padding-right: 8px; -} - -.graphdialog input, .graphdialog textarea, .graphdialog select { - background-color: var(--comfy-input-bg); - border: 2px solid; - border-color: var(--border-color); - color: var(--input-text); - border-radius: 12px 0 0 12px; -} - -/* Dialogs */ - -dialog { - box-shadow: 0 0 20px #888888; -} - -dialog::backdrop { - background: rgba(0, 0, 0, 0.5); -} - -.comfy-dialog.comfyui-dialog.comfy-modal { - top: 0; - left: 0; - right: 0; - bottom: 0; - transform: none; -} - -.comfy-dialog.comfy-modal { - font-family: Arial, sans-serif; - border-color: var(--bg-color); - box-shadow: none; - border: 2px solid var(--border-color); -} - -.comfy-dialog .comfy-modal-content { - flex-direction: row; - flex-wrap: wrap; - gap: 10px; - color: var(--fg-color); -} - -.comfy-dialog .comfy-modal-content h3 { - margin-top: 0; -} - -.comfy-dialog .comfy-modal-content > p { - width: 100%; -} - -.comfy-dialog .comfy-modal-content > .comfyui-button { - flex: 1; - justify-content: center; -} - -#comfy-settings-dialog { - padding: 0; - width: 41rem; -} - -#comfy-settings-dialog tr > td:first-child { - text-align: right; -} - -#comfy-settings-dialog tbody button, #comfy-settings-dialog table > button { - background-color: var(--bg-color); - border: 1px var(--border-color) solid; - border-radius: 0; - color: var(--input-text); - font-size: 1rem; - padding: 0.5rem; -} - -#comfy-settings-dialog button:hover { - background-color: var(--tr-odd-bg-color); -} - -/* General CSS for tables */ - -.comfy-table { - border-collapse: collapse; - color: var(--input-text); - font-family: Arial, sans-serif; - width: 100%; -} - -.comfy-table caption { - position: sticky; - top: 0; - background-color: var(--bg-color); - color: var(--input-text); - font-size: 1rem; - font-weight: bold; - padding: 8px; - text-align: center; - border-bottom: 1px solid var(--border-color); -} - -.comfy-table caption .comfy-btn { - position: absolute; - top: -2px; - right: 0; - bottom: 0; - cursor: pointer; - border: none; - height: 100%; - border-radius: 0; - aspect-ratio: 1/1; - user-select: none; - font-size: 20px; -} - -.comfy-table caption .comfy-btn:focus { - outline: none; -} - -.comfy-table tr:nth-child(even) { - background-color: var(--tr-even-bg-color); -} - -.comfy-table tr:nth-child(odd) { - background-color: var(--tr-odd-bg-color); -} - -.comfy-table td, -.comfy-table th { - border: 1px solid var(--border-color); - padding: 8px; -} - -/* Context menu */ - -.litegraph .dialog { - z-index: 1; - font-family: Arial, sans-serif; -} - -.litegraph .litemenu-entry.has_submenu { - position: relative; - padding-right: 20px; -} - -.litemenu-entry.has_submenu::after { - content: ">"; - position: absolute; - top: 0; - right: 2px; -} - -.litegraph.litecontextmenu, -.litegraph.litecontextmenu.dark { - z-index: 9999 !important; - background-color: var(--comfy-menu-bg) !important; - filter: brightness(95%); - will-change: transform; -} - -.litegraph.litecontextmenu .litemenu-entry:hover:not(.disabled):not(.separator) { - background-color: var(--comfy-menu-bg) !important; - filter: brightness(155%); - will-change: transform; - color: var(--input-text); -} - -.litegraph.litecontextmenu .litemenu-entry.submenu, -.litegraph.litecontextmenu.dark .litemenu-entry.submenu { - background-color: var(--comfy-menu-bg) !important; - color: var(--input-text); -} - -.litegraph.litecontextmenu input { - background-color: var(--comfy-input-bg) !important; - color: var(--input-text) !important; -} - -.comfy-context-menu-filter { - box-sizing: border-box; - border: 1px solid #999; - margin: 0 0 5px 5px; - width: calc(100% - 10px); -} - -.comfy-img-preview { - pointer-events: none; - overflow: hidden; - display: flex; - flex-wrap: wrap; - align-content: flex-start; - justify-content: center; -} - -.comfy-img-preview img { - object-fit: contain; - width: var(--comfy-img-preview-width); - height: var(--comfy-img-preview-height); -} - -.comfy-missing-nodes li button { - font-size: 12px; - margin-left: 5px; -} - -/* Search box */ - -.litegraph.litesearchbox { - z-index: 9999 !important; - background-color: var(--comfy-menu-bg) !important; - overflow: hidden; - display: block; -} - -.litegraph.litesearchbox input, -.litegraph.litesearchbox select { - background-color: var(--comfy-input-bg) !important; - color: var(--input-text); -} - -.litegraph.lite-search-item { - color: var(--input-text); - background-color: var(--comfy-input-bg); - filter: brightness(80%); - will-change: transform; - padding-left: 0.2em; -} - -.litegraph.lite-search-item.generic_type { - color: var(--input-text); - filter: brightness(50%); - will-change: transform; -} - -@media only screen and (max-width: 450px) { - #comfy-settings-dialog .comfy-table tbody { - display: grid; - } - #comfy-settings-dialog .comfy-table tr { - display: grid; - } - #comfy-settings-dialog tr > td:first-child { - text-align: center; - border-bottom: none; - padding-bottom: 0; - } - #comfy-settings-dialog tr > td:not(:first-child) { - text-align: center; - border-top: none; - } -} - -audio.comfy-audio.empty-audio-widget { - display: none; -} - -.comfy-graph-tooltip { - background: var(--comfy-input-bg); - border-radius: 5px; - box-shadow: 0 0 5px rgba(0, 0, 0, 0.4); - color: var(--input-text); - display: none; - font-family: sans-serif; - left: 0; - max-width: 30vw; - padding: 4px 8px; - position: absolute; - top: 0; - transform: translate(5px, calc(-100% - 5px)); - white-space: pre-wrap; - z-index: 99999; -} diff --git a/web/types/comfy.d.ts b/web/types/comfy.d.ts deleted file mode 100644 index 9a338b349..000000000 --- a/web/types/comfy.d.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { LGraphNode, IWidget } from "./litegraph"; -import { ComfyApp } from "../../scripts/app"; - -export interface ComfyExtension { - /** - * The name of the extension - */ - name: string; - /** - * Allows any initialisation, e.g. loading resources. Called after the canvas is created but before nodes are added - * @param app The ComfyUI app instance - */ - init?(app: ComfyApp): Promise; - /** - * Allows any additonal setup, called after the application is fully set up and running - * @param app The ComfyUI app instance - */ - setup?(app: ComfyApp): Promise; - /** - * Called before nodes are registered with the graph - * @param defs The collection of node definitions, add custom ones or edit existing ones - * @param app The ComfyUI app instance - */ - addCustomNodeDefs?(defs: Record, app: ComfyApp): Promise; - /** - * Allows the extension to add custom widgets - * @param app The ComfyUI app instance - * @returns An array of {[widget name]: widget data} - */ - getCustomWidgets?( - app: ComfyApp - ): Promise< - Record { widget?: IWidget; minWidth?: number; minHeight?: number }> - >; - /** - * Allows the extension to add additional handling to the node before it is registered with LGraph - * @param nodeType The node class (not an instance) - * @param nodeData The original node object info config object - * @param app The ComfyUI app instance - */ - beforeRegisterNodeDef?(nodeType: typeof LGraphNode, nodeData: ComfyObjectInfo, app: ComfyApp): Promise; - /** - * Allows the extension to register additional nodes with LGraph after standard nodes are added - * @param app The ComfyUI app instance - */ - registerCustomNodes?(app: ComfyApp): Promise; - /** - * Allows the extension to modify a node that has been reloaded onto the graph. - * If you break something in the backend and want to patch workflows in the frontend - * This is the place to do this - * @param node The node that has been loaded - * @param app The ComfyUI app instance - */ - loadedGraphNode?(node: LGraphNode, app: ComfyApp); - /** - * Allows the extension to run code after the constructor of the node - * @param node The node that has been created - * @param app The ComfyUI app instance - */ - nodeCreated?(node: LGraphNode, app: ComfyApp); -} - -export type ComfyObjectInfo = { - name: string; - display_name?: string; - description?: string; - category: string; - input?: { - required?: Record; - optional?: Record; - }; - output?: string[]; - output_name: string[]; -}; - -export type ComfyObjectInfoConfig = [string | any[]] | [string | any[], any]; diff --git a/web/types/litegraph.d.ts b/web/types/litegraph.d.ts deleted file mode 100644 index 6629e779f..000000000 --- a/web/types/litegraph.d.ts +++ /dev/null @@ -1,1506 +0,0 @@ -// Type definitions for litegraph.js 0.7.0 -// Project: litegraph.js -// Definitions by: NateScarlet - -export type Vector2 = [number, number]; -export type Vector4 = [number, number, number, number]; -export type widgetTypes = - | "number" - | "slider" - | "combo" - | "text" - | "toggle" - | "button"; -export type SlotShape = - | typeof LiteGraph.BOX_SHAPE - | typeof LiteGraph.CIRCLE_SHAPE - | typeof LiteGraph.ARROW_SHAPE - | typeof LiteGraph.SQUARE_SHAPE - | number; // For custom shapes - -/** https://github.com/jagenjo/litegraph.js/tree/master/guides#node-slots */ -export interface INodeSlot { - name: string; - type: string | -1; - label?: string; - dir?: - | typeof LiteGraph.UP - | typeof LiteGraph.RIGHT - | typeof LiteGraph.DOWN - | typeof LiteGraph.LEFT; - color_on?: string; - color_off?: string; - shape?: SlotShape; - locked?: boolean; - nameLocked?: boolean; -} - -export interface INodeInputSlot extends INodeSlot { - link: LLink["id"] | null; -} -export interface INodeOutputSlot extends INodeSlot { - links: LLink["id"][] | null; -} - -export type WidgetCallback = ( - this: T, - value: T["value"], - graphCanvas: LGraphCanvas, - node: LGraphNode, - pos: Vector2, - event?: MouseEvent -) => void; - -export interface IWidget { - name: string | null; - value: TValue; - options?: TOptions; - type?: widgetTypes; - y?: number; - property?: string; - last_y?: number; - clicked?: boolean; - marker?: boolean; - callback?: WidgetCallback; - /** Called by `LGraphCanvas.drawNodeWidgets` */ - draw?( - ctx: CanvasRenderingContext2D, - node: LGraphNode, - width: number, - posY: number, - height: number - ): void; - /** - * Called by `LGraphCanvas.processNodeWidgets` - * https://github.com/jagenjo/litegraph.js/issues/76 - */ - mouse?( - event: MouseEvent, - pos: Vector2, - node: LGraphNode - ): boolean; - /** Called by `LGraphNode.computeSize` */ - computeSize?(width: number): [number, number]; -} -export interface IButtonWidget extends IWidget { - type: "button"; -} -export interface IToggleWidget - extends IWidget { - type: "toggle"; -} -export interface ISliderWidget - extends IWidget { - type: "slider"; -} -export interface INumberWidget extends IWidget { - type: "number"; -} -export interface IComboWidget - extends IWidget< - string[], - { - values: - | string[] - | ((widget: IComboWidget, node: LGraphNode) => string[]); - } - > { - type: "combo"; -} - -export interface ITextWidget extends IWidget { - type: "text"; -} - -export interface IContextMenuItem { - content: string; - callback?: ContextMenuEventListener; - /** Used as innerHTML for extra child element */ - title?: string; - disabled?: boolean; - has_submenu?: boolean; - submenu?: { - options: ContextMenuItem[]; - } & IContextMenuOptions; - className?: string; -} -export interface IContextMenuOptions { - callback?: ContextMenuEventListener; - ignore_item_callbacks?: Boolean; - event?: MouseEvent | CustomEvent; - parentMenu?: ContextMenu; - autoopen?: boolean; - title?: string; - extra?: any; -} - -export type ContextMenuItem = IContextMenuItem | null; -export type ContextMenuEventListener = ( - value: ContextMenuItem, - options: IContextMenuOptions, - event: MouseEvent, - parentMenu: ContextMenu | undefined, - node: LGraphNode -) => boolean | void; - -export const LiteGraph: { - VERSION: number; - - CANVAS_GRID_SIZE: number; - - NODE_TITLE_HEIGHT: number; - NODE_TITLE_TEXT_Y: number; - NODE_SLOT_HEIGHT: number; - NODE_WIDGET_HEIGHT: number; - NODE_WIDTH: number; - NODE_MIN_WIDTH: number; - NODE_COLLAPSED_RADIUS: number; - NODE_COLLAPSED_WIDTH: number; - NODE_TITLE_COLOR: string; - NODE_TEXT_SIZE: number; - NODE_TEXT_COLOR: string; - NODE_SUBTEXT_SIZE: number; - NODE_DEFAULT_COLOR: string; - NODE_DEFAULT_BGCOLOR: string; - NODE_DEFAULT_BOXCOLOR: string; - NODE_DEFAULT_SHAPE: string; - DEFAULT_SHADOW_COLOR: string; - DEFAULT_GROUP_FONT: number; - - LINK_COLOR: string; - EVENT_LINK_COLOR: string; - CONNECTING_LINK_COLOR: string; - - MAX_NUMBER_OF_NODES: number; //avoid infinite loops - DEFAULT_POSITION: Vector2; //default node position - VALID_SHAPES: ["default", "box", "round", "card"]; //,"circle" - - //shapes are used for nodes but also for slots - BOX_SHAPE: 1; - ROUND_SHAPE: 2; - CIRCLE_SHAPE: 3; - CARD_SHAPE: 4; - ARROW_SHAPE: 5; - SQUARE_SHAPE: 6; - - //enums - INPUT: 1; - OUTPUT: 2; - - EVENT: -1; //for outputs - ACTION: -1; //for inputs - - ALWAYS: 0; - ON_EVENT: 1; - NEVER: 2; - ON_TRIGGER: 3; - - UP: 1; - DOWN: 2; - LEFT: 3; - RIGHT: 4; - CENTER: 5; - - STRAIGHT_LINK: 0; - LINEAR_LINK: 1; - SPLINE_LINK: 2; - - NORMAL_TITLE: 0; - NO_TITLE: 1; - TRANSPARENT_TITLE: 2; - AUTOHIDE_TITLE: 3; - - node_images_path: string; - - debug: boolean; - catch_exceptions: boolean; - throw_errors: boolean; - /** if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits */ - allow_scripts: boolean; - /** node types by string */ - registered_node_types: Record; - /** used for dropping files in the canvas */ - node_types_by_file_extension: Record; - /** node types by class name */ - Nodes: Record; - - /** used to add extra features to the search box */ - searchbox_extras: Record< - string, - { - data: { outputs: string[][]; title: string }; - desc: string; - type: string; - } - >; - - createNode(type: string): T; - /** Register a node class so it can be listed when the user wants to create a new one */ - registerNodeType(type: string, base: { new (): LGraphNode }): void; - /** removes a node type from the system */ - unregisterNodeType(type: string): void; - /** Removes all previously registered node's types. */ - clearRegisteredTypes(): void; - /** - * Create a new node type by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. - * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. - * @param name node name with namespace (p.e.: 'math/sum') - * @param func - * @param param_types an array containing the type of every parameter, otherwise parameters will accept any type - * @param return_type string with the return type, otherwise it will be generic - * @param properties properties to be configurable - */ - wrapFunctionAsNode( - name: string, - func: (...args: any[]) => any, - param_types?: string[], - return_type?: string, - properties?: object - ): void; - - /** - * Adds this method to all node types, existing and to be created - * (You can add it to LGraphNode.prototype but then existing node types wont have it) - */ - addNodeMethod(name: string, func: (...args: any[]) => any): void; - - /** - * Create a node of a given type with a name. The node is not attached to any graph yet. - * @param type full name of the node class. p.e. "math/sin" - * @param name a name to distinguish from other nodes - * @param options to set options - */ - createNode( - type: string, - title: string, - options: object - ): T; - - /** - * Returns a registered node type with a given name - * @param type full name of the node class. p.e. "math/sin" - */ - getNodeType(type: string): LGraphNodeConstructor; - - /** - * Returns a list of node types matching one category - * @method getNodeTypesInCategory - * @param {String} category category name - * @param {String} filter only nodes with ctor.filter equal can be shown - * @return {Array} array with all the node classes - */ - getNodeTypesInCategory( - category: string, - filter: string - ): LGraphNodeConstructor[]; - - /** - * Returns a list with all the node type categories - * @method getNodeTypesCategories - * @param {String} filter only nodes with ctor.filter equal can be shown - * @return {Array} array with all the names of the categories - */ - getNodeTypesCategories(filter: string): string[]; - - /** debug purposes: reloads all the js scripts that matches a wildcard */ - reloadNodes(folder_wildcard: string): void; - - getTime(): number; - LLink: typeof LLink; - LGraph: typeof LGraph; - DragAndScale: typeof DragAndScale; - compareObjects(a: object, b: object): boolean; - distance(a: Vector2, b: Vector2): number; - colorToString(c: string): string; - isInsideRectangle( - x: number, - y: number, - left: number, - top: number, - width: number, - height: number - ): boolean; - growBounding(bounding: Vector4, x: number, y: number): Vector4; - isInsideBounding(p: Vector2, bb: Vector4): boolean; - hex2num(hex: string): [number, number, number]; - num2hex(triplet: [number, number, number]): string; - ContextMenu: typeof ContextMenu; - extendClass(target: A, origin: B): A & B; - getParameterNames(func: string): string[]; -}; - -export type serializedLGraph< - TNode = ReturnType, - // https://github.com/jagenjo/litegraph.js/issues/74 - TLink = [number, number, number, number, number, string], - TGroup = ReturnType -> = { - last_node_id: LGraph["last_node_id"]; - last_link_id: LGraph["last_link_id"]; - nodes: TNode[]; - links: TLink[]; - groups: TGroup[]; - config: LGraph["config"]; - version: typeof LiteGraph.VERSION; -}; - -export declare class LGraph { - static supported_types: string[]; - static STATUS_STOPPED: 1; - static STATUS_RUNNING: 2; - - constructor(o?: object); - - filter: string; - catch_errors: boolean; - /** custom data */ - config: object; - elapsed_time: number; - fixedtime: number; - fixedtime_lapse: number; - globaltime: number; - inputs: any; - iteration: number; - last_link_id: number; - last_node_id: number; - last_update_time: number; - links: Record; - list_of_graphcanvas: LGraphCanvas[]; - outputs: any; - runningtime: number; - starttime: number; - status: typeof LGraph.STATUS_RUNNING | typeof LGraph.STATUS_STOPPED; - - private _nodes: LGraphNode[]; - private _groups: LGraphGroup[]; - private _nodes_by_id: Record; - /** nodes that are executable sorted in execution order */ - private _nodes_executable: - | (LGraphNode & { onExecute: NonNullable }[]) - | null; - /** nodes that contain onExecute */ - private _nodes_in_order: LGraphNode[]; - private _version: number; - - getSupportedTypes(): string[]; - /** Removes all nodes from this graph */ - clear(): void; - /** Attach Canvas to this graph */ - attachCanvas(graphCanvas: LGraphCanvas): void; - /** Detach Canvas to this graph */ - detachCanvas(graphCanvas: LGraphCanvas): void; - /** - * Starts running this graph every interval milliseconds. - * @param interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate - */ - start(interval?: number): void; - /** Stops the execution loop of the graph */ - stop(): void; - /** - * Run N steps (cycles) of the graph - * @param num number of steps to run, default is 1 - */ - runStep(num?: number, do_not_catch_errors?: boolean): void; - /** - * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than - * nodes with only inputs. - */ - updateExecutionOrder(): void; - /** This is more internal, it computes the executable nodes in order and returns it */ - computeExecutionOrder(only_onExecute: boolean, set_level: any): T; - /** - * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. - * It doesn't include the node itself - * @return an array with all the LGraphNodes that affect this node, in order of execution - */ - getAncestors(node: LGraphNode): LGraphNode[]; - /** - * Positions every node in a more readable manner - */ - arrange(margin?: number,layout?: string): void; - /** - * Returns the amount of time the graph has been running in milliseconds - * @return number of milliseconds the graph has been running - */ - getTime(): number; - - /** - * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant - * @return number of milliseconds the graph has been running - */ - getFixedTime(): number; - - /** - * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct - * if the nodes are using graphical actions - * @return number of milliseconds it took the last cycle - */ - getElapsedTime(): number; - /** - * Sends an event to all the nodes, useful to trigger stuff - * @param eventName the name of the event (function to be called) - * @param params parameters in array format - */ - sendEventToAllNodes(eventName: string, params: any[], mode?: any): void; - - sendActionToCanvas(action: any, params: any[]): void; - /** - * Adds a new node instance to this graph - * @param node the instance of the node - */ - add(node: LGraphNode, skip_compute_order?: boolean): void; - /** - * Called when a new node is added - * @param node the instance of the node - */ - onNodeAdded(node: LGraphNode): void; - /** Removes a node from the graph */ - remove(node: LGraphNode): void; - /** Returns a node by its id. */ - getNodeById(id: number): LGraphNode | undefined; - /** - * Returns a list of nodes that matches a class - * @param classObject the class itself (not an string) - * @return a list with all the nodes of this type - */ - findNodesByClass( - classObject: LGraphNodeConstructor - ): T[]; - /** - * Returns a list of nodes that matches a type - * @param type the name of the node type - * @return a list with all the nodes of this type - */ - findNodesByType(type: string): T[]; - /** - * Returns the first node that matches a name in its title - * @param title the name of the node to search - * @return the node or null - */ - findNodeByTitle(title: string): T | null; - /** - * Returns a list of nodes that matches a name - * @param title the name of the node to search - * @return a list with all the nodes with this name - */ - findNodesByTitle(title: string): T[]; - /** - * Returns the top-most node in this position of the canvas - * @param x the x coordinate in canvas space - * @param y the y coordinate in canvas space - * @param nodes_list a list with all the nodes to search from, by default is all the nodes in the graph - * @return the node at this position or null - */ - getNodeOnPos( - x: number, - y: number, - node_list?: LGraphNode[], - margin?: number - ): T | null; - /** - * Returns the top-most group in that position - * @param x the x coordinate in canvas space - * @param y the y coordinate in canvas space - * @return the group or null - */ - getGroupOnPos(x: number, y: number): LGraphGroup | null; - - onAction(action: any, param: any): void; - trigger(action: any, param: any): void; - /** Tell this graph it has a global graph input of this type */ - addInput(name: string, type: string, value?: any): void; - /** Assign a data to the global graph input */ - setInputData(name: string, data: any): void; - /** Returns the current value of a global graph input */ - getInputData(name: string): T; - /** Changes the name of a global graph input */ - renameInput(old_name: string, name: string): false | undefined; - /** Changes the type of a global graph input */ - changeInputType(name: string, type: string): false | undefined; - /** Removes a global graph input */ - removeInput(name: string): boolean; - /** Creates a global graph output */ - addOutput(name: string, type: string, value: any): void; - /** Assign a data to the global output */ - setOutputData(name: string, value: string): void; - /** Returns the current value of a global graph output */ - getOutputData(name: string): T; - - /** Renames a global graph output */ - renameOutput(old_name: string, name: string): false | undefined; - /** Changes the type of a global graph output */ - changeOutputType(name: string, type: string): false | undefined; - /** Removes a global graph output */ - removeOutput(name: string): boolean; - triggerInput(name: string, value: any): void; - setCallback(name: string, func: (...args: any[]) => any): void; - beforeChange(info?: LGraphNode): void; - afterChange(info?: LGraphNode): void; - connectionChange(node: LGraphNode): void; - /** returns if the graph is in live mode */ - isLive(): boolean; - /** clears the triggered slot animation in all links (stop visual animation) */ - clearTriggeredSlots(): void; - /* Called when something visually changed (not the graph!) */ - change(): void; - setDirtyCanvas(fg: boolean, bg: boolean): void; - /** Destroys a link */ - removeLink(link_id: number): void; - /** Creates a Object containing all the info about this graph, it can be serialized */ - serialize(): T; - /** - * Configure a graph from a JSON string - * @param data configure a graph from a JSON string - * @returns if there was any error parsing - */ - configure(data: object, keep_old?: boolean): boolean | undefined; - load(url: string): void; -} - -export type SerializedLLink = [number, string, number, number, number, number]; -export declare class LLink { - id: number; - type: string; - origin_id: number; - origin_slot: number; - target_id: number; - target_slot: number; - constructor( - id: number, - type: string, - origin_id: number, - origin_slot: number, - target_id: number, - target_slot: number - ); - configure(o: LLink | SerializedLLink): void; - serialize(): SerializedLLink; -} - -export type SerializedLGraphNode = { - id: T["id"]; - type: T["type"]; - pos: T["pos"]; - size: T["size"]; - flags: T["flags"]; - mode: T["mode"]; - inputs: T["inputs"]; - outputs: T["outputs"]; - title: T["title"]; - properties: T["properties"]; - widgets_values?: IWidget["value"][]; -}; - -/** https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#lgraphnode */ -export declare class LGraphNode { - static title_color: string; - static title: string; - static type: null | string; - static widgets_up: boolean; - constructor(title?: string); - - title: string; - type: null | string; - size: Vector2; - graph: null | LGraph; - graph_version: number; - pos: Vector2; - is_selected: boolean; - mouseOver: boolean; - - id: number; - - //inputs available: array of inputs - inputs: INodeInputSlot[]; - outputs: INodeOutputSlot[]; - connections: any[]; - - //local data - properties: Record; - properties_info: any[]; - - flags: Partial<{ - collapsed: boolean - }>; - - color: string; - bgcolor: string; - boxcolor: string; - shape: - | typeof LiteGraph.BOX_SHAPE - | typeof LiteGraph.ROUND_SHAPE - | typeof LiteGraph.CIRCLE_SHAPE - | typeof LiteGraph.CARD_SHAPE - | typeof LiteGraph.ARROW_SHAPE; - - serialize_widgets: boolean; - skip_list: boolean; - - /** Used in `LGraphCanvas.onMenuNodeMode` */ - mode?: - | typeof LiteGraph.ON_EVENT - | typeof LiteGraph.ON_TRIGGER - | typeof LiteGraph.NEVER - | typeof LiteGraph.ALWAYS; - - /** If set to true widgets do not start after the slots */ - widgets_up: boolean; - /** widgets start at y distance from the top of the node */ - widgets_start_y: number; - /** if you render outside the node, it will be clipped */ - clip_area: boolean; - /** if set to false it wont be resizable with the mouse */ - resizable: boolean; - /** slots are distributed horizontally */ - horizontal: boolean; - /** if true, the node will show the bgcolor as 'red' */ - has_errors?: boolean; - - /** configure a node from an object containing the serialized info */ - configure(info: SerializedLGraphNode): void; - /** serialize the content */ - serialize(): SerializedLGraphNode; - /** Creates a clone of this node */ - clone(): this; - /** serialize and stringify */ - toString(): string; - /** get the title string */ - getTitle(): string; - /** sets the value of a property */ - setProperty(name: string, value: any): void; - /** sets the output data */ - setOutputData(slot: number, data: any): void; - /** sets the output data */ - setOutputDataType(slot: number, type: string): void; - /** - * Retrieves the input data (data traveling through the connection) from one slot - * @param slot - * @param force_update if set to true it will force the connected node of this slot to output data into this link - * @return data or if it is not connected returns undefined - */ - getInputData(slot: number, force_update?: boolean): T; - /** - * Retrieves the input data type (in case this supports multiple input types) - * @param slot - * @return datatype in string format - */ - getInputDataType(slot: number): string; - /** - * Retrieves the input data from one slot using its name instead of slot number - * @param slot_name - * @param force_update if set to true it will force the connected node of this slot to output data into this link - * @return data or if it is not connected returns null - */ - getInputDataByName(slot_name: string, force_update?: boolean): T; - /** tells you if there is a connection in one input slot */ - isInputConnected(slot: number): boolean; - /** tells you info about an input connection (which node, type, etc) */ - getInputInfo( - slot: number - ): { link: number; name: string; type: string | 0 } | null; - /** returns the node connected in the input slot */ - getInputNode(slot: number): LGraphNode | null; - /** returns the value of an input with this name, otherwise checks if there is a property with that name */ - getInputOrProperty(name: string): T; - /** tells you the last output data that went in that slot */ - getOutputData(slot: number): T | null; - /** tells you info about an output connection (which node, type, etc) */ - getOutputInfo( - slot: number - ): { name: string; type: string; links: number[] } | null; - /** tells you if there is a connection in one output slot */ - isOutputConnected(slot: number): boolean; - /** tells you if there is any connection in the output slots */ - isAnyOutputConnected(): boolean; - /** retrieves all the nodes connected to this output slot */ - getOutputNodes(slot: number): LGraphNode[]; - /** Triggers an event in this node, this will trigger any output with the same name */ - trigger(action: string, param: any): void; - /** - * Triggers an slot event in this node - * @param slot the index of the output slot - * @param param - * @param link_id in case you want to trigger and specific output link in a slot - */ - triggerSlot(slot: number, param: any, link_id?: number): void; - /** - * clears the trigger slot animation - * @param slot the index of the output slot - * @param link_id in case you want to trigger and specific output link in a slot - */ - clearTriggeredSlot(slot: number, link_id?: number): void; - /** - * add a new property to this node - * @param name - * @param default_value - * @param type string defining the output type ("vec3","number",...) - * @param extra_info this can be used to have special properties of the property (like values, etc) - */ - addProperty( - name: string, - default_value: any, - type: string, - extra_info?: object - ): T; - /** - * add a new output slot to use in this node - * @param name - * @param type string defining the output type ("vec3","number",...) - * @param extra_info this can be used to have special properties of an output (label, special color, position, etc) - */ - addOutput( - name: string, - type: string | -1, - extra_info?: Partial - ): INodeOutputSlot; - /** - * add a new output slot to use in this node - * @param array of triplets like [[name,type,extra_info],[...]] - */ - addOutputs( - array: [string, string | -1, Partial | undefined][] - ): void; - /** remove an existing output slot */ - removeOutput(slot: number): void; - /** - * add a new input slot to use in this node - * @param name - * @param type string defining the input type ("vec3","number",...), it its a generic one use 0 - * @param extra_info this can be used to have special properties of an input (label, color, position, etc) - */ - addInput( - name: string, - type: string | -1, - extra_info?: Partial - ): INodeInputSlot; - /** - * add several new input slots in this node - * @param array of triplets like [[name,type,extra_info],[...]] - */ - addInputs( - array: [string, string | -1, Partial | undefined][] - ): void; - /** remove an existing input slot */ - removeInput(slot: number): void; - /** - * add an special connection to this node (used for special kinds of graphs) - * @param name - * @param type string defining the input type ("vec3","number",...) - * @param pos position of the connection inside the node - * @param direction if is input or output - */ - addConnection( - name: string, - type: string, - pos: Vector2, - direction: string - ): { - name: string; - type: string; - pos: Vector2; - direction: string; - links: null; - }; - setValue(v: any): void; - /** computes the size of a node according to its inputs and output slots */ - computeSize(): [number, number]; - /** - * https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#node-widgets - * @return created widget - */ - addWidget( - type: T["type"], - name: string, - value: T["value"], - callback?: WidgetCallback | string, - options?: T["options"] - ): T; - - addCustomWidget(customWidget: T): T; - - /** - * returns the bounding of the object, used for rendering purposes - * @return [x, y, width, height] - */ - getBounding(): Vector4; - /** checks if a point is inside the shape of a node */ - isPointInside( - x: number, - y: number, - margin?: number, - skipTitle?: boolean - ): boolean; - /** checks if a point is inside a node slot, and returns info about which slot */ - getSlotInPosition( - x: number, - y: number - ): { - input?: INodeInputSlot; - output?: INodeOutputSlot; - slot: number; - link_pos: Vector2; - }; - /** - * returns the input slot with a given name (used for dynamic slots), -1 if not found - * @param name the name of the slot - * @return the slot (-1 if not found) - */ - findInputSlot(name: string): number; - /** - * returns the output slot with a given name (used for dynamic slots), -1 if not found - * @param name the name of the slot - * @return the slot (-1 if not found) - */ - findOutputSlot(name: string): number; - /** - * connect this node output to the input of another node - * @param slot (could be the number of the slot or the string with the name of the slot) - * @param targetNode the target node - * @param targetSlot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) - * @return {Object} the link_info is created, otherwise null - */ - connect( - slot: number | string, - targetNode: LGraphNode, - targetSlot: number | string - ): T | null; - /** - * disconnect one output to an specific node - * @param slot (could be the number of the slot or the string with the name of the slot) - * @param target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] - * @return if it was disconnected successfully - */ - disconnectOutput(slot: number | string, targetNode?: LGraphNode): boolean; - /** - * disconnect one input - * @param slot (could be the number of the slot or the string with the name of the slot) - * @return if it was disconnected successfully - */ - disconnectInput(slot: number | string): boolean; - /** - * returns the center of a connection point in canvas coords - * @param is_input true if if a input slot, false if it is an output - * @param slot (could be the number of the slot or the string with the name of the slot) - * @param out a place to store the output, to free garbage - * @return the position - **/ - getConnectionPos( - is_input: boolean, - slot: number | string, - out?: Vector2 - ): Vector2; - /** Force align to grid */ - alignToGrid(): void; - /** Console output */ - trace(msg: string): void; - /** Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ - setDirtyCanvas(fg: boolean, bg: boolean): void; - loadImage(url: string): void; - /** Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ - captureInput(v: any): void; - /** Collapse the node to make it smaller on the canvas */ - collapse(force: boolean): void; - /** Forces the node to do not move or realign on Z */ - pin(v?: boolean): void; - localToScreen(x: number, y: number, graphCanvas: LGraphCanvas): Vector2; - - // https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-appearance - onDrawBackground?( - ctx: CanvasRenderingContext2D, - canvas: HTMLCanvasElement - ): void; - onDrawForeground?( - ctx: CanvasRenderingContext2D, - canvas: HTMLCanvasElement - ): void; - - // https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-behaviour - onMouseDown?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseMove?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseUp?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseEnter?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onMouseLeave?( - event: MouseEvent, - pos: Vector2, - graphCanvas: LGraphCanvas - ): void; - onKey?(event: KeyboardEvent, pos: Vector2, graphCanvas: LGraphCanvas): void; - - /** Called by `LGraphCanvas.selectNodes` */ - onSelected?(): void; - /** Called by `LGraphCanvas.deselectNode` */ - onDeselected?(): void; - /** Called by `LGraph.runStep` `LGraphNode.getInputData` */ - onExecute?(): void; - /** Called by `LGraph.serialize` */ - onSerialize?(o: SerializedLGraphNode): void; - /** Called by `LGraph.configure` */ - onConfigure?(o: SerializedLGraphNode): void; - /** - * when added to graph (warning: this is called BEFORE the node is configured when loading) - * Called by `LGraph.add` - */ - onAdded?(graph: LGraph): void; - /** - * when removed from graph - * Called by `LGraph.remove` `LGraph.clear` - */ - onRemoved?(): void; - /** - * if returns false the incoming connection will be canceled - * Called by `LGraph.connect` - * @param inputIndex target input slot number - * @param outputType type of output slot - * @param outputSlot output slot object - * @param outputNode node containing the output - * @param outputIndex index of output slot - */ - onConnectInput?( - inputIndex: number, - outputType: INodeOutputSlot["type"], - outputSlot: INodeOutputSlot, - outputNode: LGraphNode, - outputIndex: number - ): boolean; - /** - * if returns false the incoming connection will be canceled - * Called by `LGraph.connect` - * @param outputIndex target output slot number - * @param inputType type of input slot - * @param inputSlot input slot object - * @param inputNode node containing the input - * @param inputIndex index of input slot - */ - onConnectOutput?( - outputIndex: number, - inputType: INodeInputSlot["type"], - inputSlot: INodeInputSlot, - inputNode: LGraphNode, - inputIndex: number - ): boolean; - - /** - * Called just before connection (or disconnect - if input is linked). - * A convenient place to switch to another input, or create new one. - * This allow for ability to automatically add slots if needed - * @param inputIndex - * @return selected input slot index, can differ from parameter value - */ - onBeforeConnectInput?( - inputIndex: number - ): number; - - /** a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info or output_info ) */ - onConnectionsChange( - type: number, - slotIndex: number, - isConnected: boolean, - link: LLink, - ioSlot: (INodeOutputSlot | INodeInputSlot) - ): void; - - /** - * if returns false, will abort the `LGraphNode.setProperty` - * Called when a property is changed - * @param property - * @param value - * @param prevValue - */ - onPropertyChanged?(property: string, value: any, prevValue: any): void | boolean; - - /** Called by `LGraphCanvas.processContextMenu` */ - getMenuOptions?(graphCanvas: LGraphCanvas): ContextMenuItem[]; - getSlotMenuOptions?(slot: INodeSlot): ContextMenuItem[]; -} - -export type LGraphNodeConstructor = { - new (): T; -}; - -export type SerializedLGraphGroup = { - title: LGraphGroup["title"]; - bounding: LGraphGroup["_bounding"]; - color: LGraphGroup["color"]; - font: LGraphGroup["font"]; -}; -export declare class LGraphGroup { - title: string; - private _bounding: Vector4; - color: string; - font: string; - - configure(o: SerializedLGraphGroup): void; - serialize(): SerializedLGraphGroup; - move(deltaX: number, deltaY: number, ignoreNodes?: boolean): void; - recomputeInsideNodes(): void; - isPointInside: LGraphNode["isPointInside"]; - setDirtyCanvas: LGraphNode["setDirtyCanvas"]; -} - -export declare class DragAndScale { - constructor(element?: HTMLElement, skipEvents?: boolean); - offset: [number, number]; - scale: number; - max_scale: number; - min_scale: number; - onredraw: Function | null; - enabled: boolean; - last_mouse: Vector2; - element: HTMLElement | null; - visible_area: Vector4; - bindEvents(element: HTMLElement): void; - computeVisibleArea(): void; - onMouse(e: MouseEvent): void; - toCanvasContext(ctx: CanvasRenderingContext2D): void; - convertOffsetToCanvas(pos: Vector2): Vector2; - convertCanvasToOffset(pos: Vector2): Vector2; - mouseDrag(x: number, y: number): void; - changeScale(value: number, zooming_center?: Vector2): void; - changeDeltaScale(value: number, zooming_center?: Vector2): void; - reset(): void; -} - -/** - * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. - * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked - * - * @param canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) - * @param graph - * @param options { skip_rendering, autoresize } - */ -export declare class LGraphCanvas { - static node_colors: Record< - string, - { - color: string; - bgcolor: string; - groupcolor: string; - } - >; - static link_type_colors: Record; - static gradients: object; - static search_limit: number; - - static getFileExtension(url: string): string; - static decodeHTML(str: string): string; - - static onMenuCollapseAll(): void; - static onMenuNodeEdit(): void; - static onShowPropertyEditor( - item: any, - options: any, - e: any, - menu: any, - node: any - ): void; - /** Create menu for `Add Group` */ - static onGroupAdd: ContextMenuEventListener; - /** Create menu for `Add Node` */ - static onMenuAdd: ContextMenuEventListener; - static showMenuNodeOptionalInputs: ContextMenuEventListener; - static showMenuNodeOptionalOutputs: ContextMenuEventListener; - static onShowMenuNodeProperties: ContextMenuEventListener; - static onResizeNode: ContextMenuEventListener; - static onMenuNodeCollapse: ContextMenuEventListener; - static onMenuNodePin: ContextMenuEventListener; - static onMenuNodeMode: ContextMenuEventListener; - static onMenuNodeColors: ContextMenuEventListener; - static onMenuNodeShapes: ContextMenuEventListener; - static onMenuNodeRemove: ContextMenuEventListener; - static onMenuNodeClone: ContextMenuEventListener; - - constructor( - canvas: HTMLCanvasElement | string, - graph?: LGraph, - options?: { - skip_render?: boolean; - autoresize?: boolean; - } - ); - - static active_canvas: HTMLCanvasElement; - - allow_dragcanvas: boolean; - allow_dragnodes: boolean; - /** allow to control widgets, buttons, collapse, etc */ - allow_interaction: boolean; - /** allows to change a connection with having to redo it again */ - allow_reconnect_links: boolean; - /** allow selecting multi nodes without pressing extra keys */ - multi_select: boolean; - /** No effect */ - allow_searchbox: boolean; - always_render_background: boolean; - autoresize?: boolean; - background_image: string; - bgcanvas: HTMLCanvasElement; - bgctx: CanvasRenderingContext2D; - canvas: HTMLCanvasElement; - canvas_mouse: Vector2; - clear_background: boolean; - connecting_node: LGraphNode | null; - connections_width: number; - ctx: CanvasRenderingContext2D; - current_node: LGraphNode | null; - default_connection_color: { - input_off: string; - input_on: string; - output_off: string; - output_on: string; - }; - default_link_color: string; - dirty_area: Vector4 | null; - dirty_bgcanvas?: boolean; - dirty_canvas?: boolean; - drag_mode: boolean; - dragging_canvas: boolean; - dragging_rectangle: Vector4 | null; - ds: DragAndScale; - /** used for transition */ - editor_alpha: number; - filter: any; - fps: number; - frame: number; - graph: LGraph; - highlighted_links: Record; - highquality_render: boolean; - inner_text_font: string; - is_rendering: boolean; - last_draw_time: number; - last_mouse: Vector2; - /** - * Possible duplicated with `last_mouse` - * https://github.com/jagenjo/litegraph.js/issues/70 - */ - last_mouse_position: Vector2; - /** Timestamp of last mouse click, defaults to 0 */ - last_mouseclick: number; - links_render_mode: - | typeof LiteGraph.STRAIGHT_LINK - | typeof LiteGraph.LINEAR_LINK - | typeof LiteGraph.SPLINE_LINK; - live_mode: boolean; - node_capturing_input: LGraphNode | null; - node_dragged: LGraphNode | null; - node_in_panel: LGraphNode | null; - node_over: LGraphNode | null; - node_title_color: string; - node_widget: [LGraphNode, IWidget] | null; - /** Called by `LGraphCanvas.drawBackCanvas` */ - onDrawBackground: - | ((ctx: CanvasRenderingContext2D, visibleArea: Vector4) => void) - | null; - /** Called by `LGraphCanvas.drawFrontCanvas` */ - onDrawForeground: - | ((ctx: CanvasRenderingContext2D, visibleArea: Vector4) => void) - | null; - onDrawOverlay: ((ctx: CanvasRenderingContext2D) => void) | null; - /** Called by `LGraphCanvas.processMouseDown` */ - onMouse: ((event: MouseEvent) => boolean) | null; - /** Called by `LGraphCanvas.drawFrontCanvas` and `LGraphCanvas.drawLinkTooltip` */ - onDrawLinkTooltip: ((ctx: CanvasRenderingContext2D, link: LLink, _this: this) => void) | null; - /** Called by `LGraphCanvas.selectNodes` */ - onNodeMoved: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.processNodeSelected` */ - onNodeSelected: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.deselectNode` */ - onNodeDeselected: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.processNodeDblClicked` */ - onShowNodePanel: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.processNodeDblClicked` */ - onNodeDblClicked: ((node: LGraphNode) => void) | null; - /** Called by `LGraphCanvas.selectNodes` */ - onSelectionChange: ((nodes: Record) => void) | null; - /** Called by `LGraphCanvas.showSearchBox` */ - onSearchBox: - | (( - helper: Element, - value: string, - graphCanvas: LGraphCanvas - ) => string[]) - | null; - onSearchBoxSelection: - | ((name: string, event: MouseEvent, graphCanvas: LGraphCanvas) => void) - | null; - pause_rendering: boolean; - render_canvas_border: boolean; - render_collapsed_slots: boolean; - render_connection_arrows: boolean; - render_connections_border: boolean; - render_connections_shadows: boolean; - render_curved_connections: boolean; - render_execution_order: boolean; - render_only_selected: boolean; - render_shadows: boolean; - render_title_colored: boolean; - round_radius: number; - selected_group: null | LGraphGroup; - selected_group_resizing: boolean; - selected_nodes: Record; - show_info: boolean; - title_text_font: string; - /** set to true to render title bar with gradients */ - use_gradients: boolean; - visible_area: DragAndScale["visible_area"]; - visible_links: LLink[]; - visible_nodes: LGraphNode[]; - zoom_modify_alpha: boolean; - - /** clears all the data inside */ - clear(): void; - /** assigns a graph, you can reassign graphs to the same canvas */ - setGraph(graph: LGraph, skipClear?: boolean): void; - /** opens a graph contained inside a node in the current graph */ - openSubgraph(graph: LGraph): void; - /** closes a subgraph contained inside a node */ - closeSubgraph(): void; - /** assigns a canvas */ - setCanvas(canvas: HTMLCanvasElement, skipEvents?: boolean): void; - /** binds mouse, keyboard, touch and drag events to the canvas */ - bindEvents(): void; - /** unbinds mouse events from the canvas */ - unbindEvents(): void; - - /** - * this function allows to render the canvas using WebGL instead of Canvas2D - * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL - **/ - enableWebGL(): void; - - /** - * marks as dirty the canvas, this way it will be rendered again - * @param fg if the foreground canvas is dirty (the one containing the nodes) - * @param bg if the background canvas is dirty (the one containing the wires) - */ - setDirty(fg: boolean, bg: boolean): void; - - /** - * Used to attach the canvas in a popup - * @return the window where the canvas is attached (the DOM root node) - */ - getCanvasWindow(): Window; - /** starts rendering the content of the canvas when needed */ - startRendering(): void; - /** stops rendering the content of the canvas (to save resources) */ - stopRendering(): void; - - processMouseDown(e: MouseEvent): boolean | undefined; - processMouseMove(e: MouseEvent): boolean | undefined; - processMouseUp(e: MouseEvent): boolean | undefined; - processMouseWheel(e: MouseEvent): boolean | undefined; - - /** returns true if a position (in graph space) is on top of a node little corner box */ - isOverNodeBox(node: LGraphNode, canvasX: number, canvasY: number): boolean; - /** returns true if a position (in graph space) is on top of a node input slot */ - isOverNodeInput( - node: LGraphNode, - canvasX: number, - canvasY: number, - slotPos: Vector2 - ): boolean; - - /** process a key event */ - processKey(e: KeyboardEvent): boolean | undefined; - - copyToClipboard(): void; - pasteFromClipboard(): void; - processDrop(e: DragEvent): void; - checkDropItem(e: DragEvent): void; - processNodeDblClicked(n: LGraphNode): void; - processNodeSelected(n: LGraphNode, e: MouseEvent): void; - processNodeDeselected(node: LGraphNode): void; - - /** selects a given node (or adds it to the current selection) */ - selectNode(node: LGraphNode, add?: boolean): void; - /** selects several nodes (or adds them to the current selection) */ - selectNodes(nodes?: LGraphNode[], add?: boolean): void; - /** removes a node from the current selection */ - deselectNode(node: LGraphNode): void; - /** removes all nodes from the current selection */ - deselectAllNodes(): void; - /** deletes all nodes in the current selection from the graph */ - deleteSelectedNodes(): void; - - /** centers the camera on a given node */ - centerOnNode(node: LGraphNode): void; - /** changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom */ - setZoom(value: number, center: Vector2): void; - /** brings a node to front (above all other nodes) */ - bringToFront(node: LGraphNode): void; - /** sends a node to the back (below all other nodes) */ - sendToBack(node: LGraphNode): void; - /** checks which nodes are visible (inside the camera area) */ - computeVisibleNodes(nodes: LGraphNode[]): LGraphNode[]; - /** renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) */ - draw(forceFG?: boolean, forceBG?: boolean): void; - /** draws the front canvas (the one containing all the nodes) */ - drawFrontCanvas(): void; - /** draws some useful stats in the corner of the canvas */ - renderInfo(ctx: CanvasRenderingContext2D, x: number, y: number): void; - /** draws the back canvas (the one containing the background and the connections) */ - drawBackCanvas(): void; - /** draws the given node inside the canvas */ - drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void; - /** draws graphic for node's slot */ - drawSlotGraphic(ctx: CanvasRenderingContext2D, pos: number[], shape: SlotShape, horizontal: boolean): void; - /** draws the shape of the given node in the canvas */ - drawNodeShape( - node: LGraphNode, - ctx: CanvasRenderingContext2D, - size: [number, number], - fgColor: string, - bgColor: string, - selected: boolean, - mouseOver: boolean - ): void; - /** draws every connection visible in the canvas */ - drawConnections(ctx: CanvasRenderingContext2D): void; - /** - * draws a link between two points - * @param a start pos - * @param b end pos - * @param link the link object with all the link info - * @param skipBorder ignore the shadow of the link - * @param flow show flow animation (for events) - * @param color the color for the link - * @param startDir the direction enum - * @param endDir the direction enum - * @param numSublines number of sublines (useful to represent vec3 or rgb) - **/ - renderLink( - a: Vector2, - b: Vector2, - link: object, - skipBorder: boolean, - flow: boolean, - color?: string, - startDir?: number, - endDir?: number, - numSublines?: number - ): void; - - computeConnectionPoint( - a: Vector2, - b: Vector2, - t: number, - startDir?: number, - endDir?: number - ): void; - - drawExecutionOrder(ctx: CanvasRenderingContext2D): void; - /** draws the widgets stored inside a node */ - drawNodeWidgets( - node: LGraphNode, - posY: number, - ctx: CanvasRenderingContext2D, - activeWidget: object - ): void; - /** process an event on widgets */ - processNodeWidgets( - node: LGraphNode, - pos: Vector2, - event: Event, - activeWidget: object - ): void; - /** draws every group area in the background */ - drawGroups(canvas: any, ctx: CanvasRenderingContext2D): void; - adjustNodesSize(): void; - /** resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode */ - resize(width?: number, height?: number): void; - /** - * switches to live mode (node shapes are not rendered, only the content) - * this feature was designed when graphs where meant to create user interfaces - **/ - switchLiveMode(transition?: boolean): void; - onNodeSelectionChange(): void; - touchHandler(event: TouchEvent): void; - - showLinkMenu(link: LLink, e: any): false; - prompt( - title: string, - value: any, - callback: Function, - event: any - ): HTMLDivElement; - showSearchBox(event?: MouseEvent): void; - showEditPropertyValue(node: LGraphNode, property: any, options: any): void; - createDialog( - html: string, - options?: { position?: Vector2; event?: MouseEvent } - ): void; - - convertOffsetToCanvas: DragAndScale["convertOffsetToCanvas"]; - convertCanvasToOffset: DragAndScale["convertCanvasToOffset"]; - /** converts event coordinates from canvas2D to graph coordinates */ - convertEventToCanvasOffset(e: MouseEvent): Vector2; - /** adds some useful properties to a mouse event, like the position in graph coordinates */ - adjustMouseEvent(e: MouseEvent): void; - - getCanvasMenuOptions(): ContextMenuItem[]; - getNodeMenuOptions(node: LGraphNode): ContextMenuItem[]; - getGroupMenuOptions(): ContextMenuItem[]; - /** Called by `getCanvasMenuOptions`, replace default options */ - getMenuOptions?(): ContextMenuItem[]; - /** Called by `getCanvasMenuOptions`, append to default options */ - getExtraMenuOptions?(): ContextMenuItem[]; - /** Called when mouse right click */ - processContextMenu(node: LGraphNode, event: Event): void; -} - -declare class ContextMenu { - static trigger( - element: HTMLElement, - event_name: string, - params: any, - origin: any - ): void; - static isCursorOverElement(event: MouseEvent, element: HTMLElement): void; - static closeAllContextMenus(window: Window): void; - constructor(values: ContextMenuItem[], options?: IContextMenuOptions, window?: Window); - options: IContextMenuOptions; - parentMenu?: ContextMenu; - lock: boolean; - current_submenu?: ContextMenu; - addItem( - name: string, - value: ContextMenuItem, - options?: IContextMenuOptions - ): void; - close(e?: MouseEvent, ignore_parent_menu?: boolean): void; - getTopMenu(): void; - getFirstEvent(): void; -} - -declare global { - interface CanvasRenderingContext2D { - /** like rect but rounded corners */ - roundRect( - x: number, - y: number, - width: number, - height: number, - radius: number, - radiusLow: number - ): void; - } - - interface Math { - clamp(v: number, min: number, max: number): number; - } -} diff --git a/web/user.css b/web/user.css deleted file mode 100644 index 8b1af3868..000000000 --- a/web/user.css +++ /dev/null @@ -1 +0,0 @@ -/* Put custom styles here */ \ No newline at end of file