├── .github └── workflows │ └── publish.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __init__.py ├── nodes ├── execution_time.py ├── log_console.py ├── upload_anything.py └── url_download.py ├── pyproject.toml ├── requirements.txt └── web ├── executionTime.js ├── images └── terminal@2x.png ├── logConsole.css ├── logConsole.js ├── reroute.js ├── uploadAnything.js └── vendor ├── interact.min.js ├── xterm-addon-fit.min.js ├── xterm.min.css └── xterm.min.js /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Comfy registry 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "pyproject.toml" 9 | 10 | jobs: 11 | publish-node: 12 | name: Publish Custom Node to registry 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Check out code 16 | uses: actions/checkout@v4 17 | - name: Publish Custom Node 18 | uses: Comfy-Org/publish-node-action@main 19 | with: 20 | ## Add your own personal access token to your Github Repository secrets and reference it here. 21 | personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | .idea/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.5.1 2 | --- 3 | 4 | ### BUG Fix 5 | 6 | 1. `getSlotMenuOptions.apply` incorrectly applying arguments when right clicking on slots. 7 | 8 | https://github.com/ty0x2333/ComfyUI-Dev-Utils/issues/18 9 | 10 | Thanks [Trung0246](https://github.com/Trung0246) 11 | 12 | 0.5.0 13 | --- 14 | 15 | ### Enhancement 16 | 17 | 1. Add VRAM Used to the ExecutionTime Node. 18 | 19 | PR: https://github.com/ty0x2333/ComfyUI-Dev-Utils/pull/20 20 | 21 | Thanks [drake7707](https://github.com/drake7707) . 22 | 23 | 2. Add a Max statistics row to the ExecutionTime Node to easily view the longest execution time of a node and the VRAM 24 | requirements of the workflow. 25 | 26 | 0.4.3 27 | --- 28 | 29 | ### BUG Fix 30 | 31 | 1. Fix BUG: The ExecutionTime Node is not working with the latest version of ComfyUI. 32 | PR: https://github.com/ty0x2333/ComfyUI-Dev-Utils/pull/16 33 | 34 | Thanks [hugovntr](https://github.com/hugovntr) . 35 | 36 | ComfyUI commit: https://github.com/comfyanonymous/ComfyUI/tree/5cfe38f41c7091b0fd954877d9d7427a8b438b1a 37 | 38 | 0.4.2 39 | --- 40 | 41 | ### Enhancement 42 | 43 | 1. Ignore the ExecutionTime Node when saving the API. 44 | 45 | 0.4.1 46 | --- 47 | 48 | ### New Feature 49 | 50 | 1. Support [Comfy-Org/comfy-cli](https://github.com/Comfy-Org/comfy-cli) 51 | 52 | 0.4.0 53 | --- 54 | 55 | ### Enhancement 56 | 57 | 1. Save LogConsole panel "Collapsed" state 58 | 59 | ### BUG Fix 60 | 61 | 1. Remove "prompt_id" judgment from ExecutionTime. (Compatible with `ComfyUI-Workflow-Component`) 62 | 63 | > ComfyUI-Workflow-Component will repeatedly call "execution_start" 64 | with different "prompt_id" in the component. 65 | 66 | 0.3.0 67 | --- 68 | 69 | ### New Feature 70 | 71 | 1. Add Log Console 72 | 73 | 2024-04-28 07 42 37 74 | 75 | 0.2.0 76 | --- 77 | 78 | ### New Feature 79 | 80 | 1. Support export Execution Time CSV File. 81 | 82 | ### Enhancement 83 | 84 | 1. Optimize the style of Execution Time Node to make it better Resize. 85 | 2. Support Execution Time Node auto-resize 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ty0x2333 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ComfyUI-Dev-Utils 2 | === 3 | [![GitHub Tag](https://img.shields.io/github/v/tag/ty0x2333/ComfyUI-Dev-Utils)](https://github.com/ty0x2333/ComfyUI-Dev-Utils/tags) 4 | 5 | Installation 6 | --- 7 | 8 | #### ComfyUI-Manager 9 | 10 | Guide: [ComfyUI-Manager How-to-use](https://github.com/ltdrdata/ComfyUI-Manager#how-to-use) 11 | 12 | #### Manual 13 | 14 | In your ComfyUI directory: 15 | 16 | ```shell 17 | $ cd custom_nodes 18 | $ git clone https://github.com/ty0x2333/ComfyUI-Dev-Utils.git 19 | $ cd ComfyUI-Dev-Utils 20 | $ pip install -r requirements.txt 21 | ``` 22 | 23 | **Finally, restart ComfyUI** 24 | 25 | Features 26 | --- 27 | 28 | 1. Execution Time Analysis Tool 29 | - When running, a Badge will be added to the upper left corner of the Node to display the execution time of the 30 | node. 31 |
32 | Preview 33 | 34 |
35 | - Add `Execution Time` Node to display the execution time of each node in a table. At the same time, the current 36 | execution time and the last execution time, as well as their differences, will be displayed. 37 |
38 | Preview 39 | 40 | execution-time-node 41 | 42 |
43 | - Add a "Clear Execution Cache" button to the sidebar menu. Click it to clear the current cache(unload models and 44 | free memory). 45 |
46 | Preview 47 | 48 |
49 | 50 |
51 | Usage Example (Video) 52 | 53 | [Execution Time Analysis Demo](https://github.com/ty0x2333/ComfyUI-Dev-Utils/assets/7489176/1345f5db-c7b8-482c-9b71-de2da4d9ca09) 54 | 55 |
56 | 57 | 2. Log Console 58 | 59 | Provide a Console panel to display **Python logs** (**not** Javascript console.log). 60 | 61 | `LogConsole` automatically captures the output of `print`, `logging`, `stdout` and `stderr`. Then send it to the web page via SSE. 62 | 63 | 2024-04-28 07 42 37 64 | 65 | 66 | `LogConsole` Feautes: 67 | 68 | - **based on SSE, not Websocket. It will not affect the performance of ComfyUI's core and other functions.** 69 | - Support text color. Differentiate error logs by color. 70 | - Lazy startup, only starts capturing logs when needed. 71 | - Supports completely disabling LogConsole. 72 | 73 |
74 |
75 | Usage Example (Video) 76 | 77 | [LogConsole Demo](https://github.com/ty0x2333/ComfyUI-Dev-Utils/assets/7489176/f8295843-80ae-43e5-9702-3fd6c1962519) 78 | 79 |
80 | 81 | 4. Reroute Enhancement 82 | - Add "Reroute" option to node slot menu. 83 | 84 |
85 | Preview 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
BeforeAfter
96 |
97 | 98 | - Optimized for deleting Reroute Node. 99 | 100 |
101 | Preview 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 |
BeforeAfter
112 |
113 | 114 | 5. `UrlDownload` Node 115 | 116 | Download file from remote url and get file path 117 | 118 | 6. `UploadAnything` Node 119 | 120 | Upload any file and get file path 121 | 122 | Reference 123 | --- 124 | 125 | - [ltdrdata/ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) 126 | - [Kosinkadink/ComfyUI-VideoHelperSuite](https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite) 127 | - [chrisgoringe/cg-quicknodes](https://github.com/chrisgoringe/cg-quicknodes) 128 | - [tzwm/comfyui-profiler](https://github.com/tzwm/comfyui-profiler) 129 | - [xtermjs/xterm.js](https://github.com/xtermjs/xterm.js) 130 | 131 | License 132 | --- 133 | ComfyUI-Dev-Utils is available under the MIT license. See the LICENSE file for more info. 134 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .nodes.url_download import UrlDownload 2 | from .nodes.upload_anything import UploadAnything 3 | from .nodes.execution_time import ExecutionTime 4 | from .nodes.log_console import * 5 | 6 | # A dictionary that contains all nodes you want to export with their names 7 | # NOTE: names should be globally unique 8 | NODE_CLASS_MAPPINGS = { 9 | "TY_UrlDownload": UrlDownload, 10 | "TY_UploadAnything": UploadAnything, 11 | "TY_ExecutionTime": ExecutionTime 12 | } 13 | 14 | # A dictionary that contains the friendly/humanly readable titles for the nodes 15 | NODE_DISPLAY_NAME_MAPPINGS = { 16 | "TY_UrlDownload": "Url Download", 17 | "TY_UploadAnything": "Upload Anything", 18 | "TY_ExecutionTime": "Execution Time" 19 | } 20 | 21 | WEB_DIRECTORY = "./web" 22 | 23 | __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] 24 | -------------------------------------------------------------------------------- /nodes/execution_time.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import torch 4 | 5 | import execution 6 | import server 7 | # import model_management 8 | 9 | 10 | class ExecutionTime: 11 | CATEGORY = "TyDev-Utils/Debug" 12 | 13 | @classmethod 14 | def INPUT_TYPES(s): 15 | return {"required": {}} 16 | 17 | RETURN_TYPES = () 18 | RETURN_NAMES = () 19 | FUNCTION = "process" 20 | 21 | def process(self): 22 | return () 23 | 24 | 25 | CURRENT_START_EXECUTION_DATA = None 26 | 27 | 28 | # def get_free_vram(): 29 | # dev = model_management.get_torch_device() 30 | # if hasattr(dev, 'type') and (dev.type == 'cpu' or dev.type == 'mps'): 31 | # return 0 32 | # else: 33 | # return model_management.get_free_memory(dev) 34 | 35 | 36 | def get_peak_memory(): 37 | if not torch.cuda.is_available(): 38 | return 0 39 | device = torch.device('cuda') 40 | return torch.cuda.max_memory_allocated(device) 41 | 42 | 43 | def reset_peak_memory_record(): 44 | if not torch.cuda.is_available(): 45 | return 46 | device = torch.device('cuda') 47 | torch.cuda.reset_max_memory_allocated(device) 48 | 49 | 50 | def handle_execute(class_type, last_node_id, prompt_id, server, unique_id): 51 | if not CURRENT_START_EXECUTION_DATA: 52 | return 53 | start_time = CURRENT_START_EXECUTION_DATA['nodes_start_perf_time'].get(unique_id) 54 | start_vram = CURRENT_START_EXECUTION_DATA['nodes_start_vram'].get(unique_id) 55 | if start_time: 56 | end_time = time.perf_counter() 57 | execution_time = end_time - start_time 58 | 59 | end_vram = get_peak_memory() 60 | vram_used = end_vram - start_vram 61 | print(f"end_vram - start_vram: {end_vram} - {start_vram} = {vram_used}") 62 | if server.client_id is not None and last_node_id != server.last_node_id: 63 | server.send_sync( 64 | "TyDev-Utils.ExecutionTime.executed", 65 | {"node": unique_id, "prompt_id": prompt_id, "execution_time": int(execution_time * 1000), 66 | "vram_used": vram_used}, 67 | server.client_id 68 | ) 69 | print(f"#{unique_id} [{class_type}]: {execution_time:.2f}s - vram {vram_used}b") 70 | 71 | 72 | try: 73 | origin_execute = execution.execute 74 | 75 | 76 | def swizzle_execute(server, dynprompt, caches, current_item, extra_data, executed, prompt_id, execution_list, 77 | pending_subgraph_results): 78 | unique_id = current_item 79 | class_type = dynprompt.get_node(unique_id)['class_type'] 80 | last_node_id = server.last_node_id 81 | result = origin_execute(server, dynprompt, caches, current_item, extra_data, executed, prompt_id, 82 | execution_list, 83 | pending_subgraph_results) 84 | handle_execute(class_type, last_node_id, prompt_id, server, unique_id) 85 | return result 86 | 87 | 88 | execution.execute = swizzle_execute 89 | except Exception as e: 90 | pass 91 | 92 | # region: Deprecated 93 | try: 94 | # The execute method in the old version of ComfyUI is now deprecated. 95 | origin_recursive_execute = execution.recursive_execute 96 | 97 | 98 | def swizzle_origin_recursive_execute(server, prompt, outputs, current_item, extra_data, executed, prompt_id, 99 | outputs_ui, 100 | object_storage): 101 | unique_id = current_item 102 | class_type = prompt[unique_id]['class_type'] 103 | last_node_id = server.last_node_id 104 | result = origin_recursive_execute(server, prompt, outputs, current_item, extra_data, executed, prompt_id, 105 | outputs_ui, 106 | object_storage) 107 | handle_execute(class_type, last_node_id, prompt_id, server, unique_id) 108 | return result 109 | 110 | 111 | execution.recursive_execute = swizzle_origin_recursive_execute 112 | except Exception as e: 113 | pass 114 | # endregion 115 | 116 | origin_func = server.PromptServer.send_sync 117 | 118 | 119 | def swizzle_send_sync(self, event, data, sid=None): 120 | # print(f"swizzle_send_sync, event: {event}, data: {data}") 121 | global CURRENT_START_EXECUTION_DATA 122 | if event == "execution_start": 123 | CURRENT_START_EXECUTION_DATA = dict( 124 | start_perf_time=time.perf_counter(), 125 | nodes_start_perf_time={}, 126 | nodes_start_vram={} 127 | ) 128 | 129 | origin_func(self, event=event, data=data, sid=sid) 130 | 131 | if event == "executing" and data and CURRENT_START_EXECUTION_DATA: 132 | if data.get("node") is None: 133 | if sid is not None: 134 | start_perf_time = CURRENT_START_EXECUTION_DATA.get('start_perf_time') 135 | new_data = data.copy() 136 | if start_perf_time is not None: 137 | execution_time = time.perf_counter() - start_perf_time 138 | new_data['execution_time'] = int(execution_time * 1000) 139 | origin_func( 140 | self, 141 | event="TyDev-Utils.ExecutionTime.execution_end", 142 | data=new_data, 143 | sid=sid 144 | ) 145 | else: 146 | node_id = data.get("node") 147 | CURRENT_START_EXECUTION_DATA['nodes_start_perf_time'][node_id] = time.perf_counter() 148 | reset_peak_memory_record() 149 | CURRENT_START_EXECUTION_DATA['nodes_start_vram'][node_id] = get_peak_memory() 150 | 151 | 152 | server.PromptServer.send_sync = swizzle_send_sync 153 | -------------------------------------------------------------------------------- /nodes/log_console.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import io 5 | import logging 6 | import sys 7 | from typing import Optional, List, Any 8 | 9 | from aiohttp import web 10 | from aiohttp_sse import sse_response, EventSourceResponse 11 | 12 | from server import PromptServer 13 | 14 | 15 | class SSEHandler: 16 | 17 | def __init__(self, client_id: Optional[str], console_id: Optional[str], response: EventSourceResponse): 18 | self.client_id = client_id 19 | self.console_id = console_id 20 | self.response = response 21 | 22 | @property 23 | def is_connected(self) -> bool: 24 | return self.response.is_connected() 25 | 26 | async def send(self, record: str): 27 | await self.response.send(data=record) 28 | 29 | 30 | log_queue = asyncio.Queue() 31 | 32 | 33 | class LogCatcher: 34 | RED = "\x1b[31;20m" 35 | RESET = "\x1b[0m" 36 | 37 | def __init__(self, obj: Any, attr_name: str, error: bool): 38 | self.obj = obj 39 | self.attr_name = attr_name 40 | self.error = error 41 | self.origin_value = None 42 | 43 | def start(self): 44 | self.origin_value = getattr(self.obj, self.attr_name) 45 | setattr(self.obj, self.attr_name, self) 46 | 47 | def stop(self): 48 | setattr(self.obj, self.attr_name, self.origin_value) 49 | self.origin_value = None 50 | 51 | def write(self, value): 52 | if value: 53 | if self.error: 54 | put_value = self.RED + value + self.RESET 55 | else: 56 | put_value = value 57 | log_queue.put_nowait(put_value) 58 | if self.origin_value: 59 | self.origin_value.write(value) 60 | 61 | def __getattr__(self, item): 62 | return getattr(self.origin_value, item) 63 | 64 | 65 | LOG_CATCHERS: Optional[List[LogCatcher]] = None 66 | 67 | 68 | class LogListener: 69 | _sentinel = None 70 | 71 | def __init__(self, queue: asyncio.Queue): 72 | self.queue = queue 73 | self.handlers: List[SSEHandler] = [] 74 | self._task: Optional[asyncio.Task] = None 75 | 76 | @property 77 | def is_started(self) -> bool: 78 | return self._task is not None 79 | 80 | def start_if_needed(self): 81 | if self.is_started: 82 | return 83 | try: 84 | loop = asyncio.get_event_loop() 85 | except: # noqa 86 | loop = asyncio.new_event_loop() 87 | 88 | self._task = loop.create_task(self._monitor()) 89 | 90 | def append_handler(self, handler: SSEHandler): 91 | if not handler.is_connected: 92 | return 93 | print(f"[LogConsole] client [{handler.client_id}], console [{handler.console_id}], connected") 94 | self.handlers.append(handler) 95 | 96 | def __remove_disconnected_handler(self, handler: SSEHandler): 97 | print(f"[LogConsole] client [{handler.client_id}], console [{handler.console_id}], disconnected") 98 | self.handlers.remove(handler) 99 | 100 | async def handle(self, record): 101 | for handler in self.handlers[:]: 102 | if not handler.is_connected: 103 | self.__remove_disconnected_handler(handler) 104 | continue 105 | 106 | try: 107 | await handler.send(record) 108 | except ConnectionResetError: 109 | self.__remove_disconnected_handler(handler) 110 | 111 | async def _monitor(self): 112 | q = self.queue 113 | while True: 114 | try: 115 | record = await self.queue.get() 116 | await self.handle(record) 117 | q.task_done() 118 | except Exception as e: 119 | print(f"QueueListener._monitor fail: {e}") 120 | 121 | def stop(self): 122 | if not self._task: 123 | return 124 | self._task.cancel() 125 | self._task = None 126 | 127 | 128 | log_listener = LogListener(queue=log_queue) 129 | 130 | 131 | def start_log_catchers_if_needed(): 132 | global LOG_CATCHERS 133 | if LOG_CATCHERS is not None: 134 | return 135 | print("Start Log Catchers...") 136 | LOG_CATCHERS = [] 137 | stdout_log_catcher = LogCatcher(sys, 'stdout', error=False) 138 | LOG_CATCHERS.append(stdout_log_catcher) 139 | stderr_log_catcher = LogCatcher(sys, 'stderr', error=True) 140 | LOG_CATCHERS.append(stderr_log_catcher) 141 | 142 | for handler in logging.root.handlers: 143 | if not isinstance(handler, logging.StreamHandler) or not handler.stream: 144 | continue 145 | 146 | if handler.stream.__class__.__name__ == 'ComfyUIManagerLogger': 147 | if getattr(handler.stream, 'is_stdout'): 148 | error = False 149 | else: 150 | error = True 151 | LOG_CATCHERS.append(LogCatcher(handler, 'stream', error=error)) 152 | continue 153 | 154 | if isinstance(handler.stream, io.TextIOWrapper): 155 | if handler.stream.name == '': 156 | LOG_CATCHERS.append(LogCatcher(handler, 'stream', error=False)) 157 | elif handler.stream.name == '': 158 | LOG_CATCHERS.append(LogCatcher(handler, 'stream', error=True)) 159 | 160 | for catcher in LOG_CATCHERS: 161 | catcher.start() 162 | 163 | 164 | def stop_log_catchers_if_needed(): 165 | global LOG_CATCHERS 166 | if LOG_CATCHERS is None: 167 | return 168 | print("Stop Log Catchers...") 169 | for catcher in LOG_CATCHERS: 170 | catcher.stop() 171 | LOG_CATCHERS = None 172 | 173 | 174 | @PromptServer.instance.routes.get("/ty-dev-utils/log") # noqa 175 | async def log_stream(request: web.Request) -> web.StreamResponse: 176 | client_id = request.query.get('client_id') 177 | console_id = request.query.get('console_id') 178 | log_listener.start_if_needed() 179 | start_log_catchers_if_needed() 180 | 181 | async with sse_response(request) as resp: 182 | log_listener.append_handler(SSEHandler(response=resp, client_id=client_id, console_id=console_id)) 183 | while resp.is_connected(): 184 | await asyncio.sleep(1) 185 | 186 | return resp 187 | 188 | 189 | @PromptServer.instance.routes.post("/ty-dev-utils/disable-log") # noqa 190 | async def disable_log_stream(request: web.Request) -> web.StreamResponse: 191 | client_id = request.query.get('client_id') 192 | console_id = request.query.get('console_id') 193 | print(f"Disable Log Console. client id: {client_id}, console id: {console_id}") 194 | log_listener.stop() 195 | stop_log_catchers_if_needed() 196 | 197 | return web.json_response({'code': 0}) 198 | -------------------------------------------------------------------------------- /nodes/upload_anything.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import os 3 | 4 | import folder_paths 5 | 6 | 7 | # modified from https://stackoverflow.com/questions/22058048/hashing-a-file-in-python 8 | def calculate_file_hash(filename: str, hash_every_n: int = 1): 9 | # Larger video files were taking >.5 seconds to hash even when cached, 10 | # so instead the modified time from the filesystem is used as a hash 11 | h = hashlib.sha256() 12 | h.update(filename.encode()) 13 | h.update(str(os.path.getmtime(filename)).encode()) 14 | return h.hexdigest() 15 | 16 | 17 | # Reference: https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite/blob/main/videohelpersuite/load_video_nodes.py 18 | class UploadAnything: 19 | @classmethod 20 | def INPUT_TYPES(s): 21 | input_dir = folder_paths.get_input_directory() 22 | files = [] 23 | for f in os.listdir(input_dir): 24 | if os.path.isfile(os.path.join(input_dir, f)): 25 | files.append(f) 26 | return { 27 | "required": { 28 | "file": (sorted(files),), 29 | }, 30 | } 31 | 32 | CATEGORY = "TyDev-Utils/Utils" 33 | 34 | RETURN_TYPES = ("STRING",) 35 | RETURN_NAMES = ("file_path",) 36 | 37 | FUNCTION = "upload" 38 | 39 | def upload(self, file): 40 | file_path = folder_paths.get_annotated_filepath(file) 41 | return (file_path,) 42 | 43 | @classmethod 44 | def IS_CHANGED(s, file, **kwargs): 45 | file_path = folder_paths.get_annotated_filepath(file) 46 | return calculate_file_hash(file_path) 47 | 48 | @classmethod 49 | def VALIDATE_INPUTS(s, file, **kwargs): 50 | if not folder_paths.exists_annotated_filepath(file): 51 | return "Invalid file: {}".format(file) 52 | return True 53 | -------------------------------------------------------------------------------- /nodes/url_download.py: -------------------------------------------------------------------------------- 1 | import itertools as IT 2 | import os 3 | import re 4 | import tempfile 5 | import urllib.parse 6 | from pathlib import Path 7 | 8 | import requests 9 | from tqdm import tqdm 10 | 11 | import comfy 12 | import folder_paths 13 | 14 | 15 | class UrlDownload: 16 | @classmethod 17 | def INPUT_TYPES(s): 18 | return { 19 | "required": { 20 | "url": ("STRING", {"default": ""}), 21 | "filename_prefix": ("STRING", {"default": "TyDev"}), 22 | }, 23 | "optional": { 24 | "save_file": ("BOOLEAN", {"default": True}), 25 | } 26 | } 27 | 28 | RETURN_TYPES = ( 29 | "STRING", 30 | ) 31 | 32 | RETURN_NAMES = ("file_path",) 33 | 34 | FUNCTION = "doit" 35 | 36 | CATEGORY = "TyDev-Utils/Utils" 37 | 38 | @staticmethod 39 | def uniquify(path, sep='-'): 40 | """https://stackoverflow.com/a/13852851""" 41 | 42 | def name_sequence(): 43 | count = IT.count() 44 | yield '' 45 | while True: 46 | yield '{s}{n:d}'.format(s=sep, n=next(count)) 47 | 48 | orig = tempfile._name_sequence 49 | with tempfile._once_lock: 50 | tempfile._name_sequence = name_sequence() 51 | path = os.path.normpath(path) 52 | dirname, basename = os.path.split(path) 53 | filename, ext = os.path.splitext(basename) 54 | fd, filename = tempfile.mkstemp(dir=dirname, prefix=filename, suffix=ext) 55 | tempfile._name_sequence = orig 56 | return filename 57 | 58 | def doit(self, url: str, filename_prefix: str, save_file: bool = True): 59 | if save_file: 60 | output_dir = folder_paths.get_output_directory() 61 | else: 62 | output_dir = folder_paths.get_temp_directory() 63 | 64 | parse_result = urllib.parse.urlparse(url) 65 | url_path = Path(parse_result.path) 66 | extension = Path(parse_result.path).suffix.removeprefix('.') 67 | extension = re.sub('!.*$', '', extension) 68 | 69 | full_output_folder, filename, _, _, _ = folder_paths.get_save_image_path(filename_prefix, output_dir) 70 | 71 | output_filename = f"{filename}-{url_path.stem}" 72 | if extension: 73 | output_filename += f".{extension}" 74 | 75 | output_file_path = os.path.join(full_output_folder, output_filename) 76 | output_file_path = self.uniquify(output_file_path) 77 | 78 | block_size = 4096 # 4 KB 79 | with requests.get(url, stream=True) as r: 80 | r.raise_for_status() 81 | total = int(r.headers.get('content-length', 0)) 82 | pbar = comfy.utils.ProgressBar(total) 83 | current = 0 84 | with tqdm.wrapattr( 85 | open(output_file_path, 'wb'), 86 | "write", 87 | miniters=1, 88 | desc=url.split('/')[-1], 89 | total=total 90 | ) as fout: 91 | for chunk in r.iter_content(chunk_size=block_size): 92 | fout.write(chunk) 93 | current += len(chunk) 94 | pbar.update_absolute(value=current) 95 | 96 | return (output_file_path,) 97 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "comfyui-dev-utils" 3 | description = "Execution Time Analysis, Reroute Enhancement, Node collection for developers." 4 | version = "0.5.1" 5 | license = { text = "MIT License" } 6 | dependencies = ["aiohttp-sse"] 7 | 8 | [project.urls] 9 | Repository = "https://github.com/ty0x2333/ComfyUI-Dev-Utils" 10 | # Used by Comfy Registry https://comfyregistry.org 11 | 12 | [tool.comfy] 13 | PublisherId = "ty0x2333" 14 | DisplayName = "ComfyUI-Dev-Utils" 15 | Icon = "" 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp-sse -------------------------------------------------------------------------------- /web/executionTime.js: -------------------------------------------------------------------------------- 1 | import {api} from "../../../scripts/api.js"; 2 | import {app} from "../../../scripts/app.js"; 3 | import {$el} from "../../scripts/ui.js"; 4 | 5 | // region: Refresh Timer 6 | let refreshTimer = null; 7 | 8 | function stopRefreshTimer() { 9 | if (!refreshTimer) { 10 | return; 11 | } 12 | clearInterval(refreshTimer); 13 | refreshTimer = null; 14 | } 15 | 16 | function startRefreshTimer() { 17 | stopRefreshTimer(); 18 | refreshTimer = setInterval(function () { 19 | app.graph.setDirtyCanvas(true, false); 20 | }, 100); 21 | } 22 | 23 | 24 | // endregion 25 | 26 | function formatExecutionTime(time) { 27 | return `${(time / 1000.0).toFixed(2)}s` 28 | } 29 | 30 | // Reference: https://gist.github.com/zentala/1e6f72438796d74531803cc3833c039c 31 | function formatBytes(bytes, decimals) { 32 | if (bytes === 0) { 33 | return '0 B' 34 | } 35 | const k = 1024, 36 | dm = decimals || 2, 37 | sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 38 | i = Math.floor(Math.log(bytes) / Math.log(k)); 39 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; 40 | } 41 | 42 | 43 | // Reference: https://github.com/ltdrdata/ComfyUI-Manager/blob/main/js/comfyui-manager.js 44 | function drawBadge(node, orig, restArgs) { 45 | let ctx = restArgs[0]; 46 | const r = orig?.apply?.(node, restArgs); 47 | 48 | if (!node.flags.collapsed && node.constructor.title_mode != LiteGraph.NO_TITLE) { 49 | let text = ""; 50 | if (node.ty_et_execution_time !== undefined) { 51 | text = formatExecutionTime(node.ty_et_execution_time) + " - vram " + formatBytes(node.ty_et_vram_used, 2); 52 | } else if (node.ty_et_start_time !== undefined) { 53 | text = formatExecutionTime(LiteGraph.getTime() - node.ty_et_start_time); 54 | } 55 | if (!text) { 56 | return 57 | } 58 | let fgColor = "white"; 59 | let bgColor = "#0F1F0F"; 60 | let visible = true; 61 | 62 | ctx.save(); 63 | ctx.font = "12px sans-serif"; 64 | const textSize = ctx.measureText(text); 65 | ctx.fillStyle = bgColor; 66 | ctx.beginPath(); 67 | const paddingHorizontal = 6; 68 | ctx.roundRect(0, -LiteGraph.NODE_TITLE_HEIGHT - 20, textSize.width + paddingHorizontal * 2, 20, 5); 69 | ctx.fill(); 70 | 71 | ctx.fillStyle = fgColor; 72 | ctx.fillText(text, paddingHorizontal, -LiteGraph.NODE_TITLE_HEIGHT - paddingHorizontal); 73 | ctx.restore(); 74 | } 75 | return r; 76 | } 77 | 78 | // Reference: https://github.com/ltdrdata/ComfyUI-Manager/blob/main/js/common.js 79 | async function unloadModelsAndFreeMemory() { 80 | let res = await api.fetchApi(`/free`, { 81 | method: 'POST', 82 | headers: {'Content-Type': 'application/json'}, 83 | body: '{"unload_models": true, "free_memory": true}' 84 | }); 85 | 86 | if (res.status === 200) { 87 | app.ui.dialog.show('Unload models and free memory success.') 88 | } else { 89 | app.ui.dialog.show('[ERROR] Unload models and free memory fail.') 90 | } 91 | app.ui.dialog.element.style.zIndex = 10010; 92 | } 93 | 94 | function setupClearExecutionCacheMenu() { 95 | const menu = document.querySelector(".comfy-menu"); 96 | const freeButton = document.createElement("button"); 97 | freeButton.textContent = "Clear Execution Cache"; 98 | freeButton.onclick = async () => { 99 | await unloadModelsAndFreeMemory(); 100 | }; 101 | 102 | menu.append(freeButton); 103 | } 104 | 105 | 106 | let lastRunningDate = null; 107 | let runningData = null; 108 | 109 | 110 | // https://stackoverflow.com/a/56370447 111 | function exportTable(table, separator = ',') { 112 | // Select rows from table_id 113 | var rows = table.querySelectorAll('tr'); 114 | // Construct csv 115 | var csv = []; 116 | for (var i = 0; i < rows.length; i++) { 117 | var row = [], cols = rows[i].querySelectorAll('td, th'); 118 | for (var j = 0; j < cols.length; j++) { 119 | // Clean innertext to remove multiple spaces and jumpline (break csv) 120 | var data = cols[j].innerText.replace(/(\r\n|\n|\r)/gm, '').replace(/(\s\s)/gm, ' ') 121 | // Escape double-quote with double-double-quote (see https://stackoverflow.com/questions/17808511/properly-escape-a-double-quote-in-csv) 122 | data = data.replace(/"/g, '""'); 123 | // Push escaped string 124 | row.push('"' + data + '"'); 125 | } 126 | csv.push(row.join(separator)); 127 | } 128 | var csv_string = csv.join('\n'); 129 | // Download it 130 | var filename = 'execution_time' + new Date().toLocaleDateString() + '.csv'; 131 | var link = document.createElement('a'); 132 | link.style.display = 'none'; 133 | link.setAttribute('target', '_blank'); 134 | link.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv_string)); 135 | link.setAttribute('download', filename); 136 | document.body.appendChild(link); 137 | link.click(); 138 | document.body.removeChild(link); 139 | } 140 | 141 | function buildTableHtml() { 142 | const tableBody = $el("tbody") 143 | const tableFooter = $el("tfoot", {style: {"background": "var(--comfy-input-bg)"}}) 144 | const headerThStyle = {"white-space": "nowrap"} 145 | const table = $el("table", { 146 | textAlign: "right", 147 | border: "1px solid var(--border-color)", 148 | style: {"border": "none", "border-spacing": "0", "font-size": "14px", "width": "100%"} 149 | }, [ 150 | $el("thead", {style: {"background": "var(--comfy-input-bg)"}}, [ 151 | $el("tr", [ 152 | $el("th", {style: headerThStyle, "textContent": "Node Id"}), 153 | $el("th", {style: headerThStyle, "textContent": "Node Title"}), 154 | $el("th", {style: headerThStyle, "textContent": "Current Time"}), 155 | $el("th", {style: headerThStyle, "textContent": "Per Time"}), 156 | $el("th", {style: headerThStyle, "textContent": "Cur / Pre Time Diff"}), 157 | $el("th", {style: headerThStyle, "textContent": "VRAM Used"}) 158 | ]) 159 | ]), 160 | tableBody, 161 | tableFooter 162 | ]); 163 | if (runningData?.nodes_execution_time === undefined) { 164 | return table; 165 | } 166 | 167 | function diff(current, pre) { 168 | let diffText; 169 | let diffColor; 170 | if (pre) { 171 | const diffTime = current - pre; 172 | const diffPercentText = `${(diffTime * 100 / pre).toFixed(2)}%`; 173 | if (diffTime > 0) { 174 | diffColor = 'red'; 175 | diffText = `+${formatExecutionTime(diffTime)} / +${diffPercentText}`; 176 | } else if (diffPercentText === '0.00%') { 177 | diffColor = 'white'; 178 | diffText = formatExecutionTime(diffTime); 179 | } else { 180 | diffColor = 'green'; 181 | diffText = `${formatExecutionTime(diffTime)} / ${diffPercentText}`; 182 | } 183 | } 184 | return [diffColor, diffText] 185 | } 186 | 187 | let max_execution_time = null 188 | let max_vram_used = null 189 | 190 | runningData.nodes_execution_time.forEach(function (item) { 191 | const nodeId = item.node; 192 | const node = app.graph.getNodeById(nodeId) 193 | const title = node?.title ?? nodeId 194 | const preExecutionTime = lastRunningDate?.nodes_execution_time?.find(x => x.node === nodeId)?.execution_time 195 | 196 | const [diffColor, diffText] = diff(item.execution_time, preExecutionTime); 197 | 198 | if (max_execution_time == null || item.execution_time > max_execution_time) { 199 | max_execution_time = item.execution_time 200 | } 201 | 202 | if (max_vram_used == null || item.vram_used > max_vram_used) { 203 | max_vram_used = item.vram_used 204 | } 205 | 206 | tableBody.append($el("tr", { 207 | style: {"cursor": "pointer"}, 208 | onclick: () => { 209 | if (node) { 210 | app.canvas.selectNode(node, false); 211 | } 212 | } 213 | }, [ 214 | $el("td", {style: {"textAlign": "right"}, "textContent": nodeId}), 215 | $el("td", {style: {"textAlign": "right"}, "textContent": title}), 216 | $el("td", {style: {"textAlign": "right"}, "textContent": formatExecutionTime(item.execution_time)}), 217 | $el("td", { 218 | style: {"textAlign": "right"}, 219 | "textContent": preExecutionTime !== undefined ? formatExecutionTime(preExecutionTime) : undefined 220 | }), 221 | $el("td", { 222 | style: { 223 | "textAlign": "right", 224 | "color": diffColor 225 | }, 226 | "textContent": diffText 227 | }), 228 | $el("td", {style: {"textAlign": "right"}, "textContent": formatBytes(item.vram_used, 2)}), 229 | ])) 230 | }); 231 | if (runningData.total_execution_time !== null) { 232 | const [diffColor, diffText] = diff(runningData.total_execution_time, lastRunningDate?.total_execution_time); 233 | 234 | tableFooter.append($el("tr", [ 235 | $el("td", {style: {"textAlign": "right"}, "textContent": 'Max'}), 236 | $el("td", {style: {"textAlign": "right"}, "textContent": ''}), 237 | $el("td", { 238 | style: {"textAlign": "right"}, 239 | "textContent": max_execution_time != null ? formatExecutionTime(max_execution_time) : '' 240 | }), 241 | $el("td", { 242 | style: {"textAlign": "right"}, 243 | "textContent": '' 244 | }), 245 | $el("td", { 246 | style: { 247 | "textAlign": "right", 248 | "color": diffColor 249 | }, 250 | "textContent": '' 251 | }), 252 | $el("td", { 253 | style: {"textAlign": "right"}, 254 | "textContent": max_vram_used != null ? formatBytes(max_vram_used, 2) : '' 255 | }), 256 | ])) 257 | 258 | tableFooter.append($el("tr", [ 259 | $el("td", {style: {"textAlign": "right"}, "textContent": 'Total'}), 260 | $el("td", {style: {"textAlign": "right"}, "textContent": ''}), 261 | $el("td", { 262 | style: {"textAlign": "right"}, 263 | "textContent": formatExecutionTime(runningData.total_execution_time) 264 | }), 265 | $el("td", { 266 | style: {"textAlign": "right"}, 267 | "textContent": lastRunningDate?.total_execution_time ? formatExecutionTime(lastRunningDate?.total_execution_time) : undefined 268 | }), 269 | $el("td", { 270 | style: { 271 | "textAlign": "right", 272 | "color": diffColor 273 | }, 274 | "textContent": diffText 275 | }), 276 | $el("td", {style: {"textAlign": "right"}, "textContent": ""}), 277 | ])) 278 | } 279 | return table; 280 | } 281 | 282 | function refreshTable() { 283 | app.graph._nodes.forEach(function (node) { 284 | if (node.comfyClass === "TY_ExecutionTime" && node.widgets) { 285 | const tableWidget = node.widgets.find((w) => w.name === "Table"); 286 | if (!tableWidget) { 287 | return; 288 | } 289 | tableWidget.inputEl.replaceChild(buildTableHtml(), tableWidget.inputEl.firstChild); 290 | const computeSize = node.computeSize(); 291 | const newSize = [Math.max(node.size[0], computeSize[0]), Math.max(node.size[1], computeSize[1])]; 292 | node.setSize(newSize); 293 | app.graph.setDirtyCanvas(true); 294 | } 295 | }); 296 | } 297 | 298 | app.registerExtension({ 299 | name: "TyDev-Utils.ExecutionTime", 300 | async setup() { 301 | setupClearExecutionCacheMenu(); 302 | api.addEventListener("executing", ({detail}) => { 303 | const nodeId = detail; 304 | if (!nodeId) { // Finish 305 | return 306 | } 307 | const node = app.graph.getNodeById(nodeId) 308 | if (node) { 309 | node.ty_et_start_time = LiteGraph.getTime(); 310 | } 311 | }); 312 | 313 | api.addEventListener("TyDev-Utils.ExecutionTime.executed", ({detail}) => { 314 | const node = app.graph.getNodeById(detail.node) 315 | if (node) { 316 | node.ty_et_execution_time = detail.execution_time; 317 | node.ty_et_vram_used = detail.vram_used; 318 | } 319 | const index = runningData.nodes_execution_time.findIndex(x => x.node === detail.node); 320 | const data = { 321 | node: detail.node, 322 | execution_time: detail.execution_time, 323 | vram_used: detail.vram_used 324 | }; 325 | if (index > 0) { 326 | runningData.nodes_execution_time[index] = data 327 | } else { 328 | runningData.nodes_execution_time.push(data) 329 | } 330 | refreshTable(); 331 | }); 332 | 333 | api.addEventListener("execution_start", ({detail}) => { 334 | if (runningData && runningData.total_execution_time == null) { 335 | return; 336 | } 337 | lastRunningDate = runningData; 338 | app.graph._nodes.forEach(function (node) { 339 | delete node.ty_et_start_time 340 | delete node.ty_et_execution_time 341 | delete node.ty_et_vram_used 342 | }); 343 | runningData = { 344 | nodes_execution_time: [], 345 | total_execution_time: null 346 | }; 347 | startRefreshTimer(); 348 | }); 349 | 350 | api.addEventListener("TyDev-Utils.ExecutionTime.execution_end", ({detail}) => { 351 | stopRefreshTimer(); 352 | runningData.total_execution_time = detail.execution_time; 353 | refreshTable(); 354 | }) 355 | }, 356 | async nodeCreated(node, app) { 357 | if (!node.ty_et_swizzled) { 358 | let orig = node.onDrawForeground; 359 | if (!orig) { 360 | orig = node.__proto__.onDrawForeground; 361 | } 362 | 363 | node.onDrawForeground = function (ctx) { 364 | drawBadge(node, orig, arguments) 365 | }; 366 | node.ty_et_swizzled = true; 367 | } 368 | }, 369 | async loadedGraphNode(node, app) { 370 | if (!node.ty_et_swizzled) { 371 | const orig = node.onDrawForeground; 372 | node.onDrawForeground = function (ctx) { 373 | drawBadge(node, orig, arguments) 374 | }; 375 | node.ty_et_swizzled = true; 376 | } 377 | }, 378 | async beforeRegisterNodeDef(nodeType, nodeData, app) { 379 | if (nodeType.comfyClass === "TY_ExecutionTime") { 380 | const originComputeSize = nodeType.prototype.computeSize || LGraphNode.prototype.computeSize; 381 | nodeType.prototype.computeSize = function () { 382 | const originSize = originComputeSize.apply(this, arguments); 383 | if (this.flags?.collapsed || !this.widgets) { 384 | return originSize; 385 | } 386 | const tableWidget = this.widgets.find((w) => w.name === "Table"); 387 | if (!tableWidget) { 388 | return originSize; 389 | } 390 | const tableElem = tableWidget.inputEl.firstChild; 391 | const tableHeight = tableElem.getBoundingClientRect().height; 392 | const thHeight = tableElem.tHead.getBoundingClientRect().height; 393 | const thUnscaledHeight = 24; 394 | const tableUnscaledHeight = thUnscaledHeight * tableHeight / thHeight; 395 | const autoResizeMaxHeight = 300; 396 | return [Math.max(originSize[0], 600), originSize[1] + Math.min(tableUnscaledHeight, autoResizeMaxHeight) - LiteGraph.NODE_WIDGET_HEIGHT]; 397 | } 398 | 399 | const nodeCreated = nodeType.prototype.onNodeCreated; 400 | nodeType.prototype.onNodeCreated = function () { 401 | nodeCreated?.apply(this, arguments); 402 | 403 | const tableWidget = { 404 | type: "HTML", 405 | name: "Table", 406 | draw: function (ctx, node, widgetWidth, y, widgetHeight) { 407 | const marginHorizontal = 14; 408 | const marginTop = 0; 409 | const marginBottom = 14; 410 | const elRect = ctx.canvas.getBoundingClientRect(); 411 | const transform = new DOMMatrix() 412 | .scaleSelf(elRect.width / ctx.canvas.width, elRect.height / ctx.canvas.height) 413 | .multiplySelf(ctx.getTransform()) 414 | .translateSelf(marginHorizontal, marginTop + y); 415 | 416 | const x = Math.max(0, Math.round(ctx.getTransform().a * (node.size[0] - this.inputEl.scrollWidth - 2 * marginHorizontal) / 2)); 417 | Object.assign( 418 | this.inputEl.style, 419 | { 420 | transformOrigin: '0 0', 421 | transform: transform, 422 | left: `${x}px`, 423 | top: `0px`, 424 | position: "absolute", 425 | width: `${widgetWidth - marginHorizontal * 2}px`, 426 | height: `${node.size[1] - (marginTop + marginBottom) - y}px`, 427 | overflow: `auto`, 428 | } 429 | ); 430 | }, 431 | }; 432 | tableWidget.inputEl = $el("div"); 433 | 434 | document.body.appendChild(tableWidget.inputEl); 435 | 436 | this.addWidget("button", "Export CSV", "display: none", () => { 437 | exportTable(tableWidget.inputEl.firstChild) 438 | }); 439 | this.addCustomWidget(tableWidget); 440 | 441 | this.onRemoved = function () { 442 | tableWidget.inputEl.remove(); 443 | }; 444 | this.serialize_widgets = false; 445 | this.isVirtualNode = true; 446 | 447 | const tableElem = buildTableHtml(); 448 | 449 | tableWidget.inputEl.appendChild(tableElem) 450 | 451 | this.setSize(this.computeSize()); 452 | } 453 | } 454 | } 455 | }); -------------------------------------------------------------------------------- /web/images/terminal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ty0x2333/ComfyUI-Dev-Utils/0dac07c2b99589ef0d03696595b37b4fdcd819b3/web/images/terminal@2x.png -------------------------------------------------------------------------------- /web/logConsole.css: -------------------------------------------------------------------------------- 1 | .tydev-utils-log-console-container { 2 | position: absolute; 3 | left: 0; 4 | right: 0; 5 | bottom: 0; 6 | z-index: 1000; 7 | height: 240px; 8 | background: rgba(0, 0, 0, 0.8); 9 | } 10 | 11 | .tydev-utils-log-console-control { 12 | position: relative; 13 | padding: 0 16px; 14 | height: 20px; 15 | background: var(--comfy-menu-bg); 16 | display: flex; 17 | flex-direction: row; 18 | justify-content: space-between; 19 | cursor: row-resize; 20 | 21 | button { 22 | color: var(--descrip-text); 23 | background: transparent; 24 | border: none; 25 | font-family: inherit; 26 | font-size: 22px; 27 | } 28 | 29 | button:hover { 30 | cursor: pointer; 31 | color: white; 32 | 33 | .arrow-icon { 34 | border-color: white; 35 | } 36 | } 37 | } 38 | 39 | .expand-button { 40 | display: flex; 41 | flex-direction: row; 42 | justify-content: center; 43 | width: 60px; 44 | position: absolute; 45 | left: calc(50% - 30px); 46 | cursor: pointer; 47 | } 48 | 49 | .arrow { 50 | width: 40px; 51 | height: 100%; 52 | display: flex; 53 | flex-direction: row; 54 | justify-content: center; 55 | position: relative; 56 | } 57 | 58 | i.arrow-icon { 59 | width: 4px; 60 | height: 4px; 61 | border: solid var(--descrip-text); 62 | border-width: 0 3px 3px 0; 63 | display: inline-block; 64 | padding: 3px; 65 | } 66 | 67 | .arrow-up { 68 | top: 6px; 69 | 70 | .arrow-icon { 71 | transform: rotate(-135deg); 72 | -webkit-transform: rotate(-135deg); 73 | } 74 | } 75 | 76 | .arrow-down { 77 | .arrow-icon { 78 | transform: rotate(45deg); 79 | -webkit-transform: rotate(45deg); 80 | } 81 | } 82 | 83 | #tydev-utils-log-console { 84 | position: absolute; 85 | top: 20px; 86 | bottom: 8px; 87 | left: 4px; 88 | right: 4px; 89 | } 90 | 91 | .tydev-utils-log-console-menu-container { 92 | display: flex; 93 | flex-direction: row; 94 | } 95 | 96 | #tydev-utils-log-console-state { 97 | font-size: 12px; 98 | line-height: 20px; 99 | } 100 | 101 | .xterm-viewport { 102 | background: transparent !important; 103 | } 104 | 105 | .xterm-viewport::-webkit-scrollbar { 106 | background: transparent; 107 | } 108 | 109 | .xterm-viewport::-webkit-scrollbar * { 110 | background: transparent; 111 | } 112 | 113 | .xterm-viewport::-webkit-scrollbar-thumb { 114 | background: var(--descrip-text) !important; 115 | } -------------------------------------------------------------------------------- /web/logConsole.js: -------------------------------------------------------------------------------- 1 | import {app} from "../../../scripts/app.js"; 2 | import {api} from "../../../scripts/api.js"; 3 | import {$el} from "../../../scripts/ui.js"; 4 | 5 | function addScript(src) { 6 | $el("script", { 7 | parent: document.head, 8 | src: src 9 | }); 10 | } 11 | 12 | function addCss(src) { 13 | $el("link", { 14 | parent: document.head, 15 | rel: "stylesheet", 16 | type: "text/css", 17 | href: src 18 | }); 19 | } 20 | 21 | const getValue = (key, defaultValue) => { 22 | const storageKey = localStorage.getItem("TyDev-Utils.LogConsole." + key); 23 | if (storageKey && !isNaN(+storageKey)) { 24 | return storageKey; 25 | } 26 | return defaultValue; 27 | }; 28 | 29 | const setValue = (key, value) => { 30 | localStorage.setItem("TyDev-Utils.LogConsole." + key, value); 31 | }; 32 | 33 | let logConsoleEnabled = true; 34 | 35 | 36 | // https://stackoverflow.com/a/8809472 37 | function generateUUID() { // Public Domain/MIT 38 | var d = new Date().getTime();//Timestamp 39 | var d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || 0;//Time in microseconds since page-load or 0 if unsupported 40 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 41 | var r = Math.random() * 16;//random number between 0 and 16 42 | if (d > 0) {//Use timestamp until depleted 43 | r = (d + r) % 16 | 0; 44 | d = Math.floor(d / 16); 45 | } else {//Use microseconds since page-load if supported 46 | r = (d2 + r) % 16 | 0; 47 | d2 = Math.floor(d2 / 16); 48 | } 49 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 50 | }); 51 | } 52 | 53 | let consoleId = generateUUID() 54 | 55 | app.registerExtension({ 56 | name: "TyDev-Utils.LogConsole", 57 | eventSource: null, 58 | terminal: null, 59 | 60 | async setup() { 61 | const showButton = $el("button.comfy-settings-btn", { 62 | style: { 63 | right: "16px", 64 | cursor: "pointer" 65 | }, 66 | }, [ 67 | $el("img", { 68 | src: new URL("images/terminal@2x.png", import.meta.url).toString(), 69 | style: { 70 | marginTop: "2px", 71 | width: "14px", 72 | height: "14px" 73 | } 74 | }) 75 | ]); 76 | 77 | document.querySelector(".comfy-settings-btn").after(showButton); 78 | 79 | addScript(new URL("vendor/xterm.min.js", import.meta.url).toString()); 80 | addCss(new URL("vendor/xterm.min.css", import.meta.url).toString()); 81 | addScript(new URL("vendor/xterm-addon-fit.min.js", import.meta.url).toString()); 82 | addScript(new URL("vendor/interact.min.js", import.meta.url).toString()); 83 | 84 | addCss(new URL("logConsole.css", import.meta.url).toString()); 85 | 86 | const consoleElem = $el('div'); 87 | consoleElem.id = 'tydev-utils-log-console'; 88 | 89 | const collapseButton = $el("button.expand-button", [ 90 | $el("div.arrow.arrow-down", [$el("i.arrow-icon")]) 91 | ]); 92 | const closeButton = $el("button.comfy-close-menu-btn", {textContent: 'x'}); 93 | const clearButton = $el("button", { 94 | textContent: '🗑️', 95 | style: { 96 | fontSize: '14px' 97 | }, 98 | onclick: () => { 99 | this.terminal?.clear(); 100 | } 101 | }); 102 | const consoleMenuContainer = $el("div.tydev-utils-log-console-menu-container", [ 103 | clearButton, 104 | closeButton 105 | ]) 106 | const stateElem = $el("strong"); 107 | stateElem.id = "tydev-utils-log-console-state"; 108 | const consoleIdElem = $el("div", { 109 | textContent: `ID: ${consoleId}`, 110 | style: { 111 | fontSize: '12px', 112 | marginLeft: '8px', 113 | lineHeight: '20px', 114 | color: 'var(--descrip-text)' 115 | } 116 | }); 117 | const consoleStateElem = $el("div", {style: {display: 'flex', flexDirection: 'row'}}, [ 118 | stateElem, 119 | consoleIdElem 120 | ]); 121 | const headerElem = $el("div.tydev-utils-log-console-control", [ 122 | consoleStateElem, 123 | collapseButton, 124 | consoleMenuContainer 125 | ]); 126 | const containerElem = $el("div.tydev-utils-log-console-container", [ 127 | headerElem, 128 | consoleElem 129 | ]); 130 | document.body.append(containerElem); 131 | 132 | const defaultHeight = 240; 133 | const controlHeight = 20; 134 | 135 | const setPanelCollapsed = (collapsed) => { 136 | if (collapsed) { 137 | collapseButton.firstChild.className = "arrow arrow-up" 138 | } else { 139 | collapseButton.firstChild.className = "arrow arrow-down" 140 | } 141 | containerElem.style.height = `${collapsed ? controlHeight : defaultHeight}px` 142 | containerElem.setAttribute('data-y', collapsed ? defaultHeight - controlHeight : 0); 143 | setValue("Collapsed", collapsed ? '1' : '0') 144 | }; 145 | 146 | collapseButton.onclick = () => { 147 | const toCollapsed = !collapseButton.firstChild.className.includes("arrow-up"); 148 | setPanelCollapsed(toCollapsed); 149 | }; 150 | 151 | interact(containerElem) 152 | .resizable({ 153 | // resize from all edges and corners 154 | edges: {left: false, right: false, bottom: false, top: true}, 155 | 156 | listeners: { 157 | move(event) { 158 | var target = event.target 159 | // var x = (parseFloat(target.getAttribute('data-x')) || 0) 160 | var y = parseFloat(target.getAttribute('data-y')) || 0 161 | 162 | // update the element's style 163 | // target.style.width = event.rect.width + 'px' 164 | target.style.height = event.rect.height + 'px' 165 | 166 | // translate when resizing from top or left edges 167 | // x += event.deltaRect.left 168 | y += event.deltaRect.top 169 | 170 | // target.style.transform = 'translate(' + x + 'px,' + y + 'px)' 171 | 172 | // target.setAttribute('data-x', x) 173 | target.setAttribute('data-y', y) 174 | event.preventDefault(); 175 | // target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height) 176 | } 177 | }, 178 | modifiers: [ 179 | // keep the edges inside the parent 180 | interact.modifiers.restrictEdges({ 181 | outer: 'parent' 182 | }), 183 | 184 | // minimum size 185 | interact.modifiers.restrictSize({ 186 | min: {height: controlHeight} 187 | }) 188 | ], 189 | 190 | inertia: true 191 | }) 192 | 193 | const collapsed = getValue('Collapsed', '0') === '1'; 194 | setPanelCollapsed(collapsed); 195 | 196 | const setConsoleVisible = (visible) => { 197 | setValue('Visible', visible ? '1' : '0'); 198 | containerElem.hidden = !visible || !logConsoleEnabled; 199 | showButton.hidden = visible || !logConsoleEnabled; 200 | } 201 | 202 | this.setupTerminal(containerElem, consoleElem); 203 | 204 | showButton.onclick = () => { 205 | setConsoleVisible(!(getValue('Visible', '1') === '1')); 206 | } 207 | closeButton.onclick = () => { 208 | setConsoleVisible(false); 209 | } 210 | 211 | api.addEventListener("reconnected", () => { 212 | if (this.eventSource) { 213 | this.startSSE(); 214 | } 215 | }); 216 | 217 | const onEnabledChange = (value) => { 218 | logConsoleEnabled = value; 219 | if (value) { 220 | this.startSSE(); 221 | } else { 222 | this.stopSSE(); 223 | } 224 | const visible = getValue('Visible', '1') === '1'; 225 | setConsoleVisible(visible); 226 | 227 | if (!value) { 228 | api.fetchApi(`/ty-dev-utils/disable-log?console_id=${consoleId}&client_id=${api.clientId}`, { 229 | method: "POST" 230 | }); 231 | } 232 | } 233 | 234 | logConsoleEnabled = app.ui.settings.addSetting({ 235 | id: "TyDev-Utils.LogConsole.Enabled", 236 | name: "TyDev LogConsole Enabled", 237 | type: "boolean", 238 | defaultValue: true, 239 | onChange: onEnabledChange 240 | }); 241 | }, 242 | setupTerminal(containerElem, consoleElem) { 243 | if (this.terminal) { 244 | return; 245 | } 246 | this.terminal = new Terminal({ 247 | convertEol: true, 248 | }); 249 | const terminal = this.terminal; 250 | this.terminal.attachCustomKeyEventHandler((e) => { 251 | if (e.ctrlKey && e.keyCode === 76) { 252 | // Ctrl + L 253 | terminal.clear(); 254 | return false; 255 | } 256 | }); 257 | const fitAddon = new FitAddon.FitAddon(); 258 | this.terminal.loadAddon(fitAddon); 259 | this.terminal.open(consoleElem); 260 | fitAddon.fit(); 261 | 262 | const resizeObserver = new ResizeObserver(function (entries) { 263 | try { 264 | fitAddon && fitAddon.fit(); 265 | } catch (err) { 266 | console.log(err); 267 | } 268 | }); 269 | 270 | resizeObserver.observe(containerElem); 271 | }, 272 | startSSE() { 273 | if ([EventSource.OPEN, EventSource.CONNECTING].includes(this.eventSource?.readyState)) { 274 | return 275 | } 276 | const logSSEUrl = api.apiURL(`/ty-dev-utils/log?console_id=${consoleId}&client_id=${api.clientId}`); 277 | this.eventSource = new EventSource(logSSEUrl); 278 | this.eventSource.onopen = () => { 279 | // console.log('EventSource connected') 280 | this.setSSEState(this.eventSource.readyState); 281 | }; 282 | 283 | this.eventSource.onerror = (error) => { 284 | // console.error('EventSource failed', error) 285 | this.eventSource.close(); 286 | this.setSSEState(this.eventSource.readyState); 287 | }; 288 | 289 | const messageHandler = (event) => { 290 | this.terminal?.write(event.data); 291 | // console.log(event.data); 292 | } 293 | 294 | this.eventSource.addEventListener("message", messageHandler); 295 | }, 296 | stopSSE() { 297 | this.eventSource?.close(); 298 | this.setSSEState(EventSource.CLOSED); 299 | this.eventSource = null; 300 | }, 301 | setSSEState(state) { 302 | const stateElem = document.getElementById("tydev-utils-log-console-state"); 303 | if (stateElem) { 304 | let stateTextColor; 305 | let stateText; 306 | if (state === EventSource.OPEN) { 307 | stateTextColor = "green"; 308 | stateText = "CONNECTED"; 309 | } else if (state === EventSource.CONNECTING) { 310 | stateTextColor = "gold"; 311 | stateText = "CONNECTING"; 312 | } else if (state === EventSource.CLOSED) { 313 | stateTextColor = 'red'; 314 | stateText = "CLOSED"; 315 | } 316 | stateElem.style.color = stateTextColor; 317 | stateElem.innerText = stateText; 318 | } 319 | } 320 | }); 321 | -------------------------------------------------------------------------------- /web/reroute.js: -------------------------------------------------------------------------------- 1 | import {app} from "../../../scripts/app.js"; 2 | 3 | 4 | app.registerExtension({ 5 | name: "TyDev-Utils.Reroute", 6 | addRerouteMenu(nodeType) { 7 | const getSlotMenuOptions = nodeType.prototype.getSlotMenuOptions; 8 | nodeType.prototype.getSlotMenuOptions = function (slot) { 9 | 10 | // region Copy From litegraph.core.js 11 | var menuInfo = []; 12 | if (getSlotMenuOptions) { 13 | menuInfo = getSlotMenuOptions.apply(this, [slot]); 14 | } else { 15 | if ( 16 | slot && 17 | slot.output && 18 | slot.output.links && 19 | slot.output.links.length 20 | ) { 21 | menuInfo.push({content: "Disconnect Links", slot: slot}); 22 | } 23 | var _slot = slot.input || slot.output; 24 | if (_slot.removable) { 25 | menuInfo.push( 26 | _slot.locked 27 | ? "Cannot remove" 28 | : {content: "Remove Slot", slot: slot} 29 | ); 30 | } 31 | if (!_slot.nameLocked) { 32 | menuInfo.push({content: "Rename Slot", slot: slot}); 33 | } 34 | } 35 | // endregion 36 | 37 | if ( 38 | slot && 39 | slot.output && 40 | slot.output.links && 41 | slot.output.links.length 42 | ) { 43 | menuInfo.push({ 44 | content: 'Reroute', 45 | slot: slot, 46 | callback: (value, options, event, parentMenu, node) => { 47 | const slot = value.slot; 48 | node.graph.beforeChange(); 49 | if ( 50 | slot && 51 | slot.output && 52 | slot.output.links && 53 | slot.output.links.length 54 | ) { 55 | const rerouteNode = LiteGraph.createNode("Reroute"); 56 | app.graph.add(rerouteNode); 57 | rerouteNode.pos = [ 58 | node.pos[0] + node.size[0] + 40, 59 | node.pos[1] 60 | ]; 61 | app.canvas.selectNode(rerouteNode, false); 62 | const linkInfos = []; 63 | for (var i = 0, l = slot.output.links.length; i < l; i++) { 64 | var linkId = slot.output.links[i]; 65 | var linkInfo = node.graph.links[linkId]; 66 | if (!linkInfo) { 67 | continue; 68 | } 69 | linkInfos.push(linkInfo); 70 | } 71 | linkInfos.forEach(function (linkInfo) { 72 | var targetNode = node.graph.getNodeById(linkInfo.target_id); 73 | rerouteNode.connect(0, targetNode, linkInfo.target_slot); 74 | }) 75 | node.connect(slot.slot, rerouteNode, 0); 76 | } 77 | node.graph.afterChange(); 78 | node.setDirtyCanvas(true, true); 79 | } 80 | }) 81 | } 82 | 83 | return menuInfo; 84 | }; 85 | }, 86 | setup() { 87 | for (const nodeType of Object.values(LiteGraph.registered_node_types)) { 88 | this.addRerouteMenu(nodeType); 89 | } 90 | }, 91 | }); 92 | 93 | const deleteSelectedNodes = LGraphCanvas.prototype.deleteSelectedNodes; 94 | LGraphCanvas.prototype.deleteSelectedNodes = function () { 95 | 96 | this.graph.beforeChange(); 97 | 98 | for (var i in this.selected_nodes) { 99 | var node = this.selected_nodes[i]; 100 | if (node.type != "Reroute") { 101 | continue; 102 | } 103 | if (node.block_delete) { 104 | continue; 105 | } 106 | if ( 107 | node.inputs && node.inputs.length && node.outputs && node.outputs.length && 108 | LiteGraph.isValidConnection(node.inputs[0].type, node.outputs[0].type) && 109 | node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length 110 | ) { 111 | var input_link = node.graph.links[node.inputs[0].link]; 112 | var output_links = node.outputs[0].links.map(function (link_id) { 113 | return node.graph.links[link_id] 114 | }) 115 | var input_node = node.getInputNode(0); 116 | var output_nodes = node.getOutputNodes(0); 117 | 118 | if (input_node && output_nodes) { 119 | for (var outputIndex = 0; outputIndex < output_nodes.length; ++outputIndex) { 120 | const output_node = output_nodes[outputIndex]; 121 | const output_link = output_links[outputIndex]; 122 | input_node.connect(input_link.origin_slot, output_node, output_link.target_slot); 123 | } 124 | } 125 | } 126 | this.graph.remove(node); 127 | if (this.onNodeDeselected) { 128 | this.onNodeDeselected(node); 129 | } 130 | delete this.selected_nodes[i]; 131 | } 132 | 133 | // this.selected_nodes = {}; 134 | this.current_node = null; 135 | this.highlighted_links = {}; 136 | this.setDirty(true); 137 | this.graph.afterChange(); 138 | 139 | deleteSelectedNodes.apply(this); 140 | } -------------------------------------------------------------------------------- /web/uploadAnything.js: -------------------------------------------------------------------------------- 1 | import {app} from "../../scripts/app.js"; 2 | import {api} from '../../../scripts/api.js' 3 | 4 | async function uploadFile(file) { 5 | try { 6 | const body = new FormData(); 7 | const i = file.webkitRelativePath.lastIndexOf('/'); 8 | const subfolder = file.webkitRelativePath.slice(0, i + 1) 9 | const new_file = new File([file], file.name, { 10 | type: file.type, 11 | lastModified: file.lastModified, 12 | }); 13 | body.append("image", new_file); 14 | if (i > 0) { 15 | body.append("subfolder", subfolder); 16 | } 17 | const resp = await api.fetchApi("/upload/image", { 18 | method: "POST", 19 | body, 20 | }); 21 | 22 | if (resp.status === 200) { 23 | return resp.status 24 | } else { 25 | alert(resp.status + " - " + resp.statusText); 26 | } 27 | } catch (error) { 28 | alert(error); 29 | } 30 | } 31 | 32 | function addUploadWidget(nodeType, nodeData, widgetName) { 33 | const onNodeCreated = nodeType.prototype.onNodeCreated; 34 | nodeType.prototype.onNodeCreated = function () { 35 | const pathWidget = this.widgets.find((w) => w.name === widgetName); 36 | const fileInput = document.createElement("input"); 37 | const onRemoved = this.onRemoved; 38 | this.onRemoved = () => { 39 | fileInput?.remove(); 40 | onRemoved?.apply(this, arguments); 41 | } 42 | Object.assign(fileInput, { 43 | type: "file", 44 | accept: "*", 45 | style: "display: none", 46 | onchange: async () => { 47 | if (fileInput.files.length) { 48 | if (await uploadFile(fileInput.files[0]) != 200) { 49 | //upload failed and file can not be added to options 50 | return; 51 | } 52 | const filename = fileInput.files[0].name; 53 | pathWidget.options.values.push(filename); 54 | pathWidget.value = filename; 55 | if (pathWidget.callback) { 56 | pathWidget.callback(filename) 57 | } 58 | app.graph.setDirtyCanvas(true); 59 | } 60 | }, 61 | }); 62 | document.body.append(fileInput); 63 | let uploadWidget = this.addWidget("button", "choose file to upload", "image", () => { 64 | //clear the active click event 65 | app.canvas.node_widget = null 66 | 67 | fileInput.click(); 68 | }); 69 | uploadWidget.options.serialize = false; 70 | 71 | onNodeCreated?.apply(this, arguments); 72 | } 73 | } 74 | 75 | app.registerExtension({ 76 | name: "TyDev-Utils.UploadAnything", 77 | async beforeRegisterNodeDef(nodeType, nodeData, app) { 78 | if (nodeData?.name === 'UploadAnything') { 79 | addUploadWidget(nodeType, nodeData, 'file') 80 | } 81 | }, 82 | }); 83 | -------------------------------------------------------------------------------- /web/vendor/interact.min.js: -------------------------------------------------------------------------------- 1 | /* interact.js 1.10.27 | https://raw.github.com/taye/interact.js/main/LICENSE */ 2 | 3 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).interact=e()}(this,(function(){"use strict";function t(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,r)}return n}function e(e){for(var n=1;n2&&void 0!==arguments[2]?arguments[2]:function(t){return!0},r=arguments.length>3?arguments[3]:void 0;if(r=r||{},w.string(t)&&-1!==t.search(" ")&&(t=J(t)),w.array(t))return t.forEach((function(t){return $(t,e,n,r)})),r;if(w.object(t)&&(e=t,t=""),w.func(e)&&n(t))r[t]=r[t]||[],r[t].push(e);else if(w.array(e))for(var i=0,o=e;i1?lt(e):e[0];ot(r,t.page),function(t,e){e=e||{},I.isOperaMobile&&rt(t)?it("screen",t,e):it("client",t,e)}(r,t.client),t.timeStamp=n}function ct(t){var e=[];return w.array(t)?(e[0]=t[0],e[1]=t[1]):"touchend"===t.type?1===t.touches.length?(e[0]=t.touches[0],e[1]=t.changedTouches[0]):0===t.touches.length&&(e[0]=t.changedTouches[0],e[1]=t.changedTouches[1]):(e[0]=t.touches[0],e[1]=t.touches[1]),e}function lt(t){for(var e={pageX:0,pageY:0,clientX:0,clientY:0,screenX:0,screenY:0},n=0;n=(parseInt(y(r).getComputedStyle(r).zIndex,10)||0)&&(e=o);else e=o}else e=o}return e}(a);return r.activeDrops[h]||null}function St(t,e,n){var r=t.dropState,i={enter:null,leave:null,activate:null,deactivate:null,move:null,drop:null};return"dragstart"===n.type&&(i.activate=new xt(r,n,"dropactivate"),i.activate.target=null,i.activate.dropzone=null),"dragend"===n.type&&(i.deactivate=new xt(r,n,"dropdeactivate"),i.deactivate.target=null,i.deactivate.dropzone=null),r.rejected||(r.cur.element!==r.prev.element&&(r.prev.dropzone&&(i.leave=new xt(r,n,"dragleave"),n.dragLeave=i.leave.target=r.prev.element,n.prevDropzone=i.leave.dropzone=r.prev.dropzone),r.cur.dropzone&&(i.enter=new xt(r,n,"dragenter"),n.dragEnter=r.cur.element,n.dropzone=r.cur.dropzone)),"dragend"===n.type&&r.cur.dropzone&&(i.drop=new xt(r,n,"drop"),n.dropzone=r.cur.dropzone,n.relatedTarget=r.cur.element),"dragmove"===n.type&&r.cur.dropzone&&(i.move=new xt(r,n,"dropmove"),n.dropzone=r.cur.dropzone)),i}function _t(t,e){var n=t.dropState,r=n.activeDrops,i=n.cur,o=n.prev;e.leave&&o.dropzone.fire(e.leave),e.enter&&i.dropzone.fire(e.enter),e.move&&i.dropzone.fire(e.move),e.drop&&i.dropzone.fire(e.drop),e.deactivate&&wt(r,e.deactivate),n.prev.dropzone=i.dropzone,n.prev.element=i.element}function Pt(t,e){var n=t.interaction,r=t.iEvent,i=t.event;if("dragmove"===r.type||"dragend"===r.type){var o=n.dropState;e.dynamicDrop&&(o.activeDrops=Et(e,n.element));var a=r,s=Tt(n,a,i);o.rejected=o.rejected&&!!s&&s.dropzone===o.cur.dropzone&&s.element===o.cur.element,o.cur.dropzone=s&&s.dropzone,o.cur.element=s&&s.element,o.events=St(n,0,a)}}var Ot={id:"actions/drop",install:function(t){var e=t.actions,n=t.interactStatic,r=t.Interactable,i=t.defaults;t.usePlugin(_),r.prototype.dropzone=function(t){return function(t,e){if(w.object(e)){if(t.options.drop.enabled=!1!==e.enabled,e.listeners){var n=$(e.listeners),r=Object.keys(n).reduce((function(t,e){return t[/^(enter|leave)/.test(e)?"drag".concat(e):/^(activate|deactivate|move)/.test(e)?"drop".concat(e):e]=n[e],t}),{}),i=t.options.drop.listeners;i&&t.off(i),t.on(r),t.options.drop.listeners=r}return w.func(e.ondrop)&&t.on("drop",e.ondrop),w.func(e.ondropactivate)&&t.on("dropactivate",e.ondropactivate),w.func(e.ondropdeactivate)&&t.on("dropdeactivate",e.ondropdeactivate),w.func(e.ondragenter)&&t.on("dragenter",e.ondragenter),w.func(e.ondragleave)&&t.on("dragleave",e.ondragleave),w.func(e.ondropmove)&&t.on("dropmove",e.ondropmove),/^(pointer|center)$/.test(e.overlap)?t.options.drop.overlap=e.overlap:w.number(e.overlap)&&(t.options.drop.overlap=Math.max(Math.min(1,e.overlap),0)),"accept"in e&&(t.options.drop.accept=e.accept),"checker"in e&&(t.options.drop.checker=e.checker),t}if(w.bool(e))return t.options.drop.enabled=e,t;return t.options.drop}(this,t)},r.prototype.dropCheck=function(t,e,n,r,i,o){return function(t,e,n,r,i,o,a){var s=!1;if(!(a=a||t.getRect(o)))return!!t.options.drop.checker&&t.options.drop.checker(e,n,s,t,o,r,i);var c=t.options.drop.overlap;if("pointer"===c){var l=K(r,i,"drag"),u=ot(e);u.x+=l.x,u.y+=l.y;var p=u.x>a.left&&u.xa.top&&u.y=a.left&&h<=a.right&&v>=a.top&&v<=a.bottom}if(d&&w.number(c)){s=Math.max(0,Math.min(a.right,d.right)-Math.max(a.left,d.left))*Math.max(0,Math.min(a.bottom,d.bottom)-Math.max(a.top,d.top))/(d.width*d.height)>=c}t.options.drop.checker&&(s=t.options.drop.checker(e,n,s,t,o,r,i));return s}(this,t,e,n,r,i,o)},n.dynamicDrop=function(e){return w.bool(e)?(t.dynamicDrop=e,n):t.dynamicDrop},V(e.phaselessTypes,{dragenter:!0,dragleave:!0,dropactivate:!0,dropdeactivate:!0,dropmove:!0,drop:!0}),e.methodDict.drop="dropzone",t.dynamicDrop=!1,i.actions.drop=Ot.defaults},listeners:{"interactions:before-action-start":function(t){var e=t.interaction;"drag"===e.prepared.name&&(e.dropState={cur:{dropzone:null,element:null},prev:{dropzone:null,element:null},rejected:null,events:null,activeDrops:[]})},"interactions:after-action-start":function(t,e){var n=t.interaction,r=(t.event,t.iEvent);if("drag"===n.prepared.name){var i=n.dropState;i.activeDrops=[],i.events={},i.activeDrops=Et(e,n.element),i.events=St(n,0,r),i.events.activate&&(wt(i.activeDrops,i.events.activate),e.fire("actions/drop:start",{interaction:n,dragEvent:r}))}},"interactions:action-move":Pt,"interactions:after-action-move":function(t,e){var n=t.interaction,r=t.iEvent;if("drag"===n.prepared.name){var i=n.dropState;_t(n,i.events),e.fire("actions/drop:move",{interaction:n,dragEvent:r}),i.events={}}},"interactions:action-end":function(t,e){if("drag"===t.interaction.prepared.name){var n=t.interaction,r=t.iEvent;Pt(t,e),_t(n,n.dropState.events),e.fire("actions/drop:end",{interaction:n,dragEvent:r})}},"interactions:stop":function(t){var e=t.interaction;if("drag"===e.prepared.name){var n=e.dropState;n&&(n.activeDrops=null,n.events=null,n.cur.dropzone=null,n.cur.element=null,n.prev.dropzone=null,n.prev.element=null,n.rejected=!1)}}},getActiveDrops:Et,getDrop:Tt,getDropEvents:St,fireDropEvents:_t,filterEventType:function(t){return 0===t.search("drag")||0===t.search("drop")},defaults:{enabled:!1,accept:null,overlap:"pointer"}},kt=Ot;function Dt(t){var e=t.interaction,n=t.iEvent,r=t.phase;if("gesture"===e.prepared.name){var i=e.pointers.map((function(t){return t.pointer})),o="start"===r,a="end"===r,s=e.interactable.options.deltaSource;if(n.touches=[i[0],i[1]],o)n.distance=pt(i,s),n.box=ut(i),n.scale=1,n.ds=0,n.angle=ft(i,s),n.da=0,e.gesture.startDistance=n.distance,e.gesture.startAngle=n.angle;else if(a||e.pointers.length<2){var c=e.prevEvent;n.distance=c.distance,n.box=c.box,n.scale=c.scale,n.ds=0,n.angle=c.angle,n.da=0}else n.distance=pt(i,s),n.box=ut(i),n.scale=n.distance/e.gesture.startDistance,n.angle=ft(i,s),n.ds=n.scale-e.gesture.scale,n.da=n.angle-e.gesture.angle;e.gesture.distance=n.distance,e.gesture.angle=n.angle,w.number(n.scale)&&n.scale!==1/0&&!isNaN(n.scale)&&(e.gesture.scale=n.scale)}}var It={id:"actions/gesture",before:["actions/drag","actions/resize"],install:function(t){var e=t.actions,n=t.Interactable,r=t.defaults;n.prototype.gesturable=function(t){return w.object(t)?(this.options.gesture.enabled=!1!==t.enabled,this.setPerAction("gesture",t),this.setOnEvents("gesture",t),this):w.bool(t)?(this.options.gesture.enabled=t,this):this.options.gesture},e.map.gesture=It,e.methodDict.gesture="gesturable",r.actions.gesture=It.defaults},listeners:{"interactions:action-start":Dt,"interactions:action-move":Dt,"interactions:action-end":Dt,"interactions:new":function(t){t.interaction.gesture={angle:0,distance:0,scale:1,startAngle:0,startDistance:0}},"auto-start:check":function(t){if(!(t.interaction.pointers.length<2)){var e=t.interactable.options.gesture;if(e&&e.enabled)return t.action={name:"gesture"},!1}}},defaults:{},getCursor:function(){return""},filterEventType:function(t){return 0===t.search("gesture")}},Mt=It;function zt(t,e,n,r,i,o,a){if(!e)return!1;if(!0===e){var s=w.number(o.width)?o.width:o.right-o.left,c=w.number(o.height)?o.height:o.bottom-o.top;if(a=Math.min(a,Math.abs(("left"===t||"right"===t?s:c)/2)),s<0&&("left"===t?t="right":"right"===t&&(t="left")),c<0&&("top"===t?t="bottom":"bottom"===t&&(t="top")),"left"===t){var l=s>=0?o.left:o.right;return n.x=0?o.top:o.bottom;return n.y(s>=0?o.right:o.left)-a;if("bottom"===t)return n.y>(c>=0?o.bottom:o.top)-a}return!!w.element(r)&&(w.element(e)?e===r:F(r,e,i))}function At(t){var e=t.iEvent,n=t.interaction;if("resize"===n.prepared.name&&n.resizeAxes){var r=e;n.interactable.options.resize.square?("y"===n.resizeAxes?r.delta.x=r.delta.y:r.delta.y=r.delta.x,r.axes="xy"):(r.axes=n.resizeAxes,"x"===n.resizeAxes?r.delta.y=0:"y"===n.resizeAxes&&(r.delta.x=0))}}var Rt,Ct,jt={id:"actions/resize",before:["actions/drag"],install:function(t){var e=t.actions,n=t.browser,r=t.Interactable,i=t.defaults;jt.cursors=function(t){return t.isIe9?{x:"e-resize",y:"s-resize",xy:"se-resize",top:"n-resize",left:"w-resize",bottom:"s-resize",right:"e-resize",topleft:"se-resize",bottomright:"se-resize",topright:"ne-resize",bottomleft:"ne-resize"}:{x:"ew-resize",y:"ns-resize",xy:"nwse-resize",top:"ns-resize",left:"ew-resize",bottom:"ns-resize",right:"ew-resize",topleft:"nwse-resize",bottomright:"nwse-resize",topright:"nesw-resize",bottomleft:"nesw-resize"}}(n),jt.defaultMargin=n.supportsTouch||n.supportsPointerEvent?20:10,r.prototype.resizable=function(e){return function(t,e,n){if(w.object(e))return t.options.resize.enabled=!1!==e.enabled,t.setPerAction("resize",e),t.setOnEvents("resize",e),w.string(e.axis)&&/^x$|^y$|^xy$/.test(e.axis)?t.options.resize.axis=e.axis:null===e.axis&&(t.options.resize.axis=n.defaults.actions.resize.axis),w.bool(e.preserveAspectRatio)?t.options.resize.preserveAspectRatio=e.preserveAspectRatio:w.bool(e.square)&&(t.options.resize.square=e.square),t;if(w.bool(e))return t.options.resize.enabled=e,t;return t.options.resize}(this,e,t)},e.map.resize=jt,e.methodDict.resize="resizable",i.actions.resize=jt.defaults},listeners:{"interactions:new":function(t){t.interaction.resizeAxes="xy"},"interactions:action-start":function(t){!function(t){var e=t.iEvent,n=t.interaction;if("resize"===n.prepared.name&&n.prepared.edges){var r=e,i=n.rect;n._rects={start:V({},i),corrected:V({},i),previous:V({},i),delta:{left:0,right:0,width:0,top:0,bottom:0,height:0}},r.edges=n.prepared.edges,r.rect=n._rects.corrected,r.deltaRect=n._rects.delta}}(t),At(t)},"interactions:action-move":function(t){!function(t){var e=t.iEvent,n=t.interaction;if("resize"===n.prepared.name&&n.prepared.edges){var r=e,i=n.interactable.options.resize.invert,o="reposition"===i||"negate"===i,a=n.rect,s=n._rects,c=s.start,l=s.corrected,u=s.delta,p=s.previous;if(V(p,l),o){if(V(l,a),"reposition"===i){if(l.top>l.bottom){var f=l.top;l.top=l.bottom,l.bottom=f}if(l.left>l.right){var d=l.left;l.left=l.right,l.right=d}}}else l.top=Math.min(a.top,c.bottom),l.bottom=Math.max(a.bottom,c.top),l.left=Math.min(a.left,c.right),l.right=Math.max(a.right,c.left);for(var h in l.width=l.right-l.left,l.height=l.bottom-l.top,l)u[h]=l[h]-p[h];r.edges=n.prepared.edges,r.rect=l,r.deltaRect=u}}(t),At(t)},"interactions:action-end":function(t){var e=t.iEvent,n=t.interaction;if("resize"===n.prepared.name&&n.prepared.edges){var r=e;r.edges=n.prepared.edges,r.rect=n._rects.corrected,r.deltaRect=n._rects.delta}},"auto-start:check":function(t){var e=t.interaction,n=t.interactable,r=t.element,i=t.rect,o=t.buttons;if(i){var a=V({},e.coords.cur.page),s=n.options.resize;if(s&&s.enabled&&(!e.pointerIsDown||!/mouse|pointer/.test(e.pointerType)||0!=(o&s.mouseButtons))){if(w.object(s.edges)){var c={left:!1,right:!1,top:!1,bottom:!1};for(var l in c)c[l]=zt(l,s.edges[l],a,e._latestPointer.eventTarget,r,i,s.margin||jt.defaultMargin);c.left=c.left&&!c.right,c.top=c.top&&!c.bottom,(c.left||c.right||c.top||c.bottom)&&(t.action={name:"resize",edges:c})}else{var u="y"!==s.axis&&a.x>i.right-jt.defaultMargin,p="x"!==s.axis&&a.y>i.bottom-jt.defaultMargin;(u||p)&&(t.action={name:"resize",axes:(u?"x":"")+(p?"y":"")})}return!t.action&&void 0}}}},defaults:{square:!1,preserveAspectRatio:!1,axis:"xy",margin:NaN,edges:null,invert:"none"},cursors:null,getCursor:function(t){var e=t.edges,n=t.axis,r=t.name,i=jt.cursors,o=null;if(n)o=i[r+n];else if(e){for(var a="",s=0,c=["top","bottom","left","right"];s=1){var l={x:qt.x*c,y:qt.y*c};if(l.x||l.y){var u=Vt(o);w.window(o)?o.scrollBy(l.x,l.y):o&&(o.scrollLeft+=l.x,o.scrollTop+=l.y);var p=Vt(o),f={x:p.x-u.x,y:p.y-u.y};(f.x||f.y)&&e.fire({type:"autoscroll",target:n,interactable:e,delta:f,interaction:t,container:o})}qt.prevTime=a}qt.isScrolling&&(Lt.cancel(qt.i),qt.i=Lt.request(qt.scroll))},check:function(t,e){var n;return null==(n=t.options[e].autoScroll)?void 0:n.enabled},onInteractionMove:function(t){var e=t.interaction,n=t.pointer;if(e.interacting()&&qt.check(e.interactable,e.prepared.name))if(e.simulation)qt.x=qt.y=0;else{var r,i,o,a,s=e.interactable,c=e.element,l=e.prepared.name,u=s.options[l].autoScroll,p=Bt(u.container,s,c);if(w.window(p))a=n.clientXp.innerWidth-qt.margin,o=n.clientY>p.innerHeight-qt.margin;else{var f=Y(p);a=n.clientXf.right-qt.margin,o=n.clientY>f.bottom-qt.margin}qt.x=i?1:a?-1:0,qt.y=o?1:r?-1:0,qt.isScrolling||(qt.margin=u.margin,qt.speed=u.speed,qt.start(e))}}};function Bt(t,e,n){return(w.string(t)?W(t,e,n):t)||y(n)}function Vt(t){return w.window(t)&&(t=window.document.body),{x:t.scrollLeft,y:t.scrollTop}}var Wt={id:"auto-scroll",install:function(t){var e=t.defaults,n=t.actions;t.autoScroll=qt,qt.now=function(){return t.now()},n.phaselessTypes.autoscroll=!0,e.perAction.autoScroll=qt.defaults},listeners:{"interactions:new":function(t){t.interaction.autoScroll=null},"interactions:destroy":function(t){t.interaction.autoScroll=null,qt.stop(),qt.interaction&&(qt.interaction=null)},"interactions:stop":qt.stop,"interactions:action-move":function(t){return qt.onInteractionMove(t)}}},Gt=Wt;function Nt(t,e){var n=!1;return function(){return n||(g.console.warn(e),n=!0),t.apply(this,arguments)}}function Ut(t,e){return t.name=e.name,t.axis=e.axis,t.edges=e.edges,t}function Ht(t){return w.bool(t)?(this.options.styleCursor=t,this):null===t?(delete this.options.styleCursor,this):this.options.styleCursor}function Kt(t){return w.func(t)?(this.options.actionChecker=t,this):null===t?(delete this.options.actionChecker,this):this.options.actionChecker}var $t={id:"auto-start/interactableMethods",install:function(t){var e=t.Interactable;e.prototype.getAction=function(e,n,r,i){var o=function(t,e,n,r,i){var o=t.getRect(r),a=e.buttons||{0:1,1:4,3:8,4:16}[e.button],s={action:null,interactable:t,interaction:n,element:r,rect:o,buttons:a};return i.fire("auto-start:check",s),s.action}(this,n,r,i,t);return this.options.actionChecker?this.options.actionChecker(e,n,o,this,i,r):o},e.prototype.ignoreFrom=Nt((function(t){return this._backCompatOption("ignoreFrom",t)}),"Interactable.ignoreFrom() has been deprecated. Use Interactble.draggable({ignoreFrom: newValue})."),e.prototype.allowFrom=Nt((function(t){return this._backCompatOption("allowFrom",t)}),"Interactable.allowFrom() has been deprecated. Use Interactble.draggable({allowFrom: newValue})."),e.prototype.actionChecker=Kt,e.prototype.styleCursor=Ht}};function Jt(t,e,n,r,i){return e.testIgnoreAllow(e.options[t.name],n,r)&&e.options[t.name].enabled&&ee(e,n,t,i)?t:null}function Qt(t,e,n,r,i,o,a){for(var s=0,c=r.length;s=s)return!1;if(d.interactable===t){if((l+=h===n.name?1:0)>=o)return!1;if(d.element===e&&(u++,h===n.name&&u>=a))return!1}}}return s>0}function ne(t,e){return w.number(t)?(e.autoStart.maxInteractions=t,this):e.autoStart.maxInteractions}function re(t,e,n){var r=n.autoStart.cursorElement;r&&r!==t&&(r.style.cursor=""),t.ownerDocument.documentElement.style.cursor=e,t.style.cursor=e,n.autoStart.cursorElement=e?t:null}function ie(t,e){var n=t.interactable,r=t.element,i=t.prepared;if("mouse"===t.pointerType&&n&&n.options.styleCursor){var o="";if(i.name){var a=n.options[i.name].cursorChecker;o=w.func(a)?a(i,n,r,t._interacting):e.actions.map[i.name].getCursor(i)}re(t.element,o||"",e)}else e.autoStart.cursorElement&&re(e.autoStart.cursorElement,"",e)}var oe={id:"auto-start/base",before:["actions"],install:function(t){var e=t.interactStatic,n=t.defaults;t.usePlugin($t),n.base.actionChecker=null,n.base.styleCursor=!0,V(n.perAction,{manualStart:!1,max:1/0,maxPerElement:1,allowFrom:null,ignoreFrom:null,mouseButtons:1}),e.maxInteractions=function(e){return ne(e,t)},t.autoStart={maxInteractions:1/0,withinInteractionLimit:ee,cursorElement:null}},listeners:{"interactions:down":function(t,e){var n=t.interaction,r=t.pointer,i=t.event,o=t.eventTarget;n.interacting()||te(n,Zt(n,r,i,o,e),e)},"interactions:move":function(t,e){!function(t,e){var n=t.interaction,r=t.pointer,i=t.event,o=t.eventTarget;"mouse"!==n.pointerType||n.pointerIsDown||n.interacting()||te(n,Zt(n,r,i,o,e),e)}(t,e),function(t,e){var n=t.interaction;if(n.pointerIsDown&&!n.interacting()&&n.pointerWasMoved&&n.prepared.name){e.fire("autoStart:before-start",t);var r=n.interactable,i=n.prepared.name;i&&r&&(r.options[i].manualStart||!ee(r,n.element,n.prepared,e)?n.stop():(n.start(n.prepared,r,n.element),ie(n,e)))}}(t,e)},"interactions:stop":function(t,e){var n=t.interaction,r=n.interactable;r&&r.options.styleCursor&&re(n.element,"",e)}},maxInteractions:ne,withinInteractionLimit:ee,validateAction:Jt},ae=oe;var se={id:"auto-start/dragAxis",listeners:{"autoStart:before-start":function(t,e){var n=t.interaction,r=t.eventTarget,i=t.dx,o=t.dy;if("drag"===n.prepared.name){var a=Math.abs(i),s=Math.abs(o),c=n.interactable.options.drag,l=c.startAxis,u=a>s?"x":a0&&(e.autoStartHoldTimer=setTimeout((function(){e.start(e.prepared,e.interactable,e.element)}),n))},"interactions:move":function(t){var e=t.interaction,n=t.duplicate;e.autoStartHoldTimer&&e.pointerWasMoved&&!n&&(clearTimeout(e.autoStartHoldTimer),e.autoStartHoldTimer=null)},"autoStart:before-start":function(t){var e=t.interaction;ce(e)>0&&(e.prepared.name=null)}},getHoldDuration:ce},ue=le,pe={id:"auto-start",install:function(t){t.usePlugin(ae),t.usePlugin(ue),t.usePlugin(se)}},fe=function(t){return/^(always|never|auto)$/.test(t)?(this.options.preventDefault=t,this):w.bool(t)?(this.options.preventDefault=t?"always":"never",this):this.options.preventDefault};function de(t){var e=t.interaction,n=t.event;e.interactable&&e.interactable.checkAndPreventDefault(n)}var he={id:"core/interactablePreventDefault",install:function(t){var e=t.Interactable;e.prototype.preventDefault=fe,e.prototype.checkAndPreventDefault=function(e){return function(t,e,n){var r=t.options.preventDefault;if("never"!==r)if("always"!==r){if(e.events.supportsPassive&&/^touch(start|move)$/.test(n.type)){var i=y(n.target).document,o=e.getDocOptions(i);if(!o||!o.events||!1!==o.events.passive)return}/^(mouse|pointer|touch)*(down|start)/i.test(n.type)||w.element(n.target)&&R(n.target,"input,select,textarea,[contenteditable=true],[contenteditable=true] *")||n.preventDefault()}else n.preventDefault()}(this,t,e)},t.interactions.docEvents.push({type:"dragstart",listener:function(e){for(var n=0,r=t.interactions.list;n150)return null;var e=180*Math.atan2(t.prevEvent.velocityY,t.prevEvent.velocityX)/Math.PI;e<0&&(e+=360);var n=112.5<=e&&e<247.5,r=202.5<=e&&e<337.5;return{up:r,down:!r&&22.5<=e&&e<157.5,left:n,right:!n&&(292.5<=e||e<67.5),angle:e,speed:t.prevEvent.speed,velocity:{x:t.prevEvent.velocityX,y:t.prevEvent.velocityY}}}},{key:"preventDefault",value:function(){}},{key:"stopImmediatePropagation",value:function(){this.immediatePropagationStopped=this.propagationStopped=!0}},{key:"stopPropagation",value:function(){this.propagationStopped=!0}}]),n}(vt);Object.defineProperties(Se.prototype,{pageX:{get:function(){return this.page.x},set:function(t){this.page.x=t}},pageY:{get:function(){return this.page.y},set:function(t){this.page.y=t}},clientX:{get:function(){return this.client.x},set:function(t){this.client.x=t}},clientY:{get:function(){return this.client.y},set:function(t){this.client.y=t}},dx:{get:function(){return this.delta.x},set:function(t){this.delta.x=t}},dy:{get:function(){return this.delta.y},set:function(t){this.delta.y=t}},velocityX:{get:function(){return this.velocity.x},set:function(t){this.velocity.x=t}},velocityY:{get:function(){return this.velocity.y},set:function(t){this.velocity.y=t}}});var _e=o((function t(e,n,i,o,a){r(this,t),this.id=void 0,this.pointer=void 0,this.event=void 0,this.downTime=void 0,this.downTarget=void 0,this.id=e,this.pointer=n,this.event=i,this.downTime=o,this.downTarget=a})),Pe=function(t){return t.interactable="",t.element="",t.prepared="",t.pointerIsDown="",t.pointerWasMoved="",t._proxy="",t}({}),Oe=function(t){return t.start="",t.move="",t.end="",t.stop="",t.interacting="",t}({}),ke=0,De=function(){function t(e){var n=this,i=e.pointerType,o=e.scopeFire;r(this,t),this.interactable=null,this.element=null,this.rect=null,this._rects=void 0,this.edges=null,this._scopeFire=void 0,this.prepared={name:null,axis:null,edges:null},this.pointerType=void 0,this.pointers=[],this.downEvent=null,this.downPointer={},this._latestPointer={pointer:null,event:null,eventTarget:null},this.prevEvent=null,this.pointerIsDown=!1,this.pointerWasMoved=!1,this._interacting=!1,this._ending=!1,this._stopped=!0,this._proxy=void 0,this.simulation=null,this.doMove=Nt((function(t){this.move(t)}),"The interaction.doMove() method has been renamed to interaction.move()"),this.coords={start:{page:{x:0,y:0},client:{x:0,y:0},timeStamp:0},prev:{page:{x:0,y:0},client:{x:0,y:0},timeStamp:0},cur:{page:{x:0,y:0},client:{x:0,y:0},timeStamp:0},delta:{page:{x:0,y:0},client:{x:0,y:0},timeStamp:0},velocity:{page:{x:0,y:0},client:{x:0,y:0},timeStamp:0}},this._id=ke++,this._scopeFire=o,this.pointerType=i;var a=this;this._proxy={};var s=function(t){Object.defineProperty(n._proxy,t,{get:function(){return a[t]}})};for(var c in Pe)s(c);var l=function(t){Object.defineProperty(n._proxy,t,{value:function(){return a[t].apply(a,arguments)}})};for(var u in Oe)l(u);this._scopeFire("interactions:new",{interaction:this})}return o(t,[{key:"pointerMoveTolerance",get:function(){return 1}},{key:"pointerDown",value:function(t,e,n){var r=this.updatePointer(t,e,n,!0),i=this.pointers[r];this._scopeFire("interactions:down",{pointer:t,event:e,eventTarget:n,pointerIndex:r,pointerInfo:i,type:"down",interaction:this})}},{key:"start",value:function(t,e,n){return!(this.interacting()||!this.pointerIsDown||this.pointers.length<("gesture"===t.name?2:1)||!e.options[t.name].enabled)&&(Ut(this.prepared,t),this.interactable=e,this.element=n,this.rect=e.getRect(n),this.edges=this.prepared.edges?V({},this.prepared.edges):{left:!0,right:!0,top:!0,bottom:!0},this._stopped=!1,this._interacting=this._doPhase({interaction:this,event:this.downEvent,phase:"start"})&&!this._stopped,this._interacting)}},{key:"pointerMove",value:function(t,e,n){this.simulation||this.modification&&this.modification.endResult||this.updatePointer(t,e,n,!1);var r,i,o=this.coords.cur.page.x===this.coords.prev.page.x&&this.coords.cur.page.y===this.coords.prev.page.y&&this.coords.cur.client.x===this.coords.prev.client.x&&this.coords.cur.client.y===this.coords.prev.client.y;this.pointerIsDown&&!this.pointerWasMoved&&(r=this.coords.cur.client.x-this.coords.start.client.x,i=this.coords.cur.client.y-this.coords.start.client.y,this.pointerWasMoved=Q(r,i)>this.pointerMoveTolerance);var a,s,c,l=this.getPointerIndex(t),u={pointer:t,pointerIndex:l,pointerInfo:this.pointers[l],event:e,type:"move",eventTarget:n,dx:r,dy:i,duplicate:o,interaction:this};o||(a=this.coords.velocity,s=this.coords.delta,c=Math.max(s.timeStamp/1e3,.001),a.page.x=s.page.x/c,a.page.y=s.page.y/c,a.client.x=s.client.x/c,a.client.y=s.client.y/c,a.timeStamp=c),this._scopeFire("interactions:move",u),o||this.simulation||(this.interacting()&&(u.type=null,this.move(u)),this.pointerWasMoved&&et(this.coords.prev,this.coords.cur))}},{key:"move",value:function(t){t&&t.event||nt(this.coords.delta),(t=V({pointer:this._latestPointer.pointer,event:this._latestPointer.event,eventTarget:this._latestPointer.eventTarget,interaction:this},t||{})).phase="move",this._doPhase(t)}},{key:"pointerUp",value:function(t,e,n,r){var i=this.getPointerIndex(t);-1===i&&(i=this.updatePointer(t,e,n,!1));var o=/cancel$/i.test(e.type)?"cancel":"up";this._scopeFire("interactions:".concat(o),{pointer:t,pointerIndex:i,pointerInfo:this.pointers[i],event:e,eventTarget:n,type:o,curEventTarget:r,interaction:this}),this.simulation||this.end(e),this.removePointer(t,e)}},{key:"documentBlur",value:function(t){this.end(t),this._scopeFire("interactions:blur",{event:t,type:"blur",interaction:this})}},{key:"end",value:function(t){var e;this._ending=!0,t=t||this._latestPointer.event,this.interacting()&&(e=this._doPhase({event:t,interaction:this,phase:"end"})),this._ending=!1,!0===e&&this.stop()}},{key:"currentAction",value:function(){return this._interacting?this.prepared.name:null}},{key:"interacting",value:function(){return this._interacting}},{key:"stop",value:function(){this._scopeFire("interactions:stop",{interaction:this}),this.interactable=this.element=null,this._interacting=!1,this._stopped=!0,this.prepared.name=this.prevEvent=null}},{key:"getPointerIndex",value:function(t){var e=at(t);return"mouse"===this.pointerType||"pen"===this.pointerType?this.pointers.length-1:yt(this.pointers,(function(t){return t.id===e}))}},{key:"getPointerInfo",value:function(t){return this.pointers[this.getPointerIndex(t)]}},{key:"updatePointer",value:function(t,e,n,r){var i,o,a,s=at(t),c=this.getPointerIndex(t),l=this.pointers[c];return r=!1!==r&&(r||/(down|start)$/i.test(e.type)),l?l.pointer=t:(l=new _e(s,t,e,null,null),c=this.pointers.length,this.pointers.push(l)),st(this.coords.cur,this.pointers.map((function(t){return t.pointer})),this._now()),i=this.coords.delta,o=this.coords.prev,a=this.coords.cur,i.page.x=a.page.x-o.page.x,i.page.y=a.page.y-o.page.y,i.client.x=a.client.x-o.client.x,i.client.y=a.client.y-o.client.y,i.timeStamp=a.timeStamp-o.timeStamp,r&&(this.pointerIsDown=!0,l.downTime=this.coords.cur.timeStamp,l.downTarget=n,tt(this.downPointer,t),this.interacting()||(et(this.coords.start,this.coords.cur),et(this.coords.prev,this.coords.cur),this.downEvent=e,this.pointerWasMoved=!1)),this._updateLatestPointer(t,e,n),this._scopeFire("interactions:update-pointer",{pointer:t,event:e,eventTarget:n,down:r,pointerInfo:l,pointerIndex:c,interaction:this}),c}},{key:"removePointer",value:function(t,e){var n=this.getPointerIndex(t);if(-1!==n){var r=this.pointers[n];this._scopeFire("interactions:remove-pointer",{pointer:t,event:e,eventTarget:null,pointerIndex:n,pointerInfo:r,interaction:this}),this.pointers.splice(n,1),this.pointerIsDown=!1}}},{key:"_updateLatestPointer",value:function(t,e,n){this._latestPointer.pointer=t,this._latestPointer.event=e,this._latestPointer.eventTarget=n}},{key:"destroy",value:function(){this._latestPointer.pointer=null,this._latestPointer.event=null,this._latestPointer.eventTarget=null}},{key:"_createPreparedEvent",value:function(t,e,n,r){return new Se(this,t,this.prepared.name,e,this.element,n,r)}},{key:"_fireEvent",value:function(t){var e;null==(e=this.interactable)||e.fire(t),(!this.prevEvent||t.timeStamp>=this.prevEvent.timeStamp)&&(this.prevEvent=t)}},{key:"_doPhase",value:function(t){var e=t.event,n=t.phase,r=t.preEnd,i=t.type,o=this.rect;if(o&&"move"===n&&(H(this.edges,o,this.coords.delta[this.interactable.options.deltaSource]),o.width=o.right-o.left,o.height=o.bottom-o.top),!1===this._scopeFire("interactions:before-action-".concat(n),t))return!1;var a=t.iEvent=this._createPreparedEvent(e,n,r,i);return this._scopeFire("interactions:action-".concat(n),t),"start"===n&&(this.prevEvent=a),this._fireEvent(a),this._scopeFire("interactions:after-action-".concat(n),t),!0}},{key:"_now",value:function(){return Date.now()}}]),t}();function Ie(t){Me(t.interaction)}function Me(t){if(!function(t){return!(!t.offset.pending.x&&!t.offset.pending.y)}(t))return!1;var e=t.offset.pending;return Ae(t.coords.cur,e),Ae(t.coords.delta,e),H(t.edges,t.rect,e),e.x=0,e.y=0,!0}function ze(t){var e=t.x,n=t.y;this.offset.pending.x+=e,this.offset.pending.y+=n,this.offset.total.x+=e,this.offset.total.y+=n}function Ae(t,e){var n=t.page,r=t.client,i=e.x,o=e.y;n.x+=i,n.y+=o,r.x+=i,r.y+=o}Oe.offsetBy="";var Re={id:"offset",before:["modifiers","pointer-events","actions","inertia"],install:function(t){t.Interaction.prototype.offsetBy=ze},listeners:{"interactions:new":function(t){t.interaction.offset={total:{x:0,y:0},pending:{x:0,y:0}}},"interactions:update-pointer":function(t){return function(t){t.pointerIsDown&&(Ae(t.coords.cur,t.offset.total),t.offset.pending.x=0,t.offset.pending.y=0)}(t.interaction)},"interactions:before-action-start":Ie,"interactions:before-action-move":Ie,"interactions:before-action-end":function(t){var e=t.interaction;if(Me(e))return e.move({offset:!0}),e.end(),!1},"interactions:stop":function(t){var e=t.interaction;e.offset.total.x=0,e.offset.total.y=0,e.offset.pending.x=0,e.offset.pending.y=0}}},Ce=Re;var je=function(){function t(e){r(this,t),this.active=!1,this.isModified=!1,this.smoothEnd=!1,this.allowResume=!1,this.modification=void 0,this.modifierCount=0,this.modifierArg=void 0,this.startCoords=void 0,this.t0=0,this.v0=0,this.te=0,this.targetOffset=void 0,this.modifiedOffset=void 0,this.currentOffset=void 0,this.lambda_v0=0,this.one_ve_v0=0,this.timeout=void 0,this.interaction=void 0,this.interaction=e}return o(t,[{key:"start",value:function(t){var e=this.interaction,n=Fe(e);if(!n||!n.enabled)return!1;var r=e.coords.velocity.client,i=Q(r.x,r.y),o=this.modification||(this.modification=new me(e));if(o.copyFrom(e.modification),this.t0=e._now(),this.allowResume=n.allowResume,this.v0=i,this.currentOffset={x:0,y:0},this.startCoords=e.coords.cur.page,this.modifierArg=o.fillArg({pageCoords:this.startCoords,preEnd:!0,phase:"inertiastart"}),this.t0-e.coords.cur.timeStamp<50&&i>n.minSpeed&&i>n.endSpeed)this.startInertia();else{if(o.result=o.setAll(this.modifierArg),!o.result.changed)return!1;this.startSmoothEnd()}return e.modification.result.rect=null,e.offsetBy(this.targetOffset),e._doPhase({interaction:e,event:t,phase:"inertiastart"}),e.offsetBy({x:-this.targetOffset.x,y:-this.targetOffset.y}),e.modification.result.rect=null,this.active=!0,e.simulation=this,!0}},{key:"startInertia",value:function(){var t=this,e=this.interaction.coords.velocity.client,n=Fe(this.interaction),r=n.resistance,i=-Math.log(n.endSpeed/this.v0)/r;this.targetOffset={x:(e.x-i)/r,y:(e.y-i)/r},this.te=i,this.lambda_v0=r/this.v0,this.one_ve_v0=1-n.endSpeed/this.v0;var o=this.modification,a=this.modifierArg;a.pageCoords={x:this.startCoords.x+this.targetOffset.x,y:this.startCoords.y+this.targetOffset.y},o.result=o.setAll(a),o.result.changed&&(this.isModified=!0,this.modifiedOffset={x:this.targetOffset.x+o.result.delta.x,y:this.targetOffset.y+o.result.delta.y}),this.onNextFrame((function(){return t.inertiaTick()}))}},{key:"startSmoothEnd",value:function(){var t=this;this.smoothEnd=!0,this.isModified=!0,this.targetOffset={x:this.modification.result.delta.x,y:this.modification.result.delta.y},this.onNextFrame((function(){return t.smoothEndTick()}))}},{key:"onNextFrame",value:function(t){var e=this;this.timeout=Lt.request((function(){e.active&&t()}))}},{key:"inertiaTick",value:function(){var t,e,n,r,i,o,a,s=this,c=this.interaction,l=Fe(c).resistance,u=(c._now()-this.t0)/1e3;if(u=0;a--){var d=p[a];if(d.selector===t&&d.context===e){for(var h=d.listeners,v=h.length-1;v>=0;v--){var g=h[v];if(g.func===i&&Ne(g.options,u)){h.splice(v,1),h.length||(p.splice(a,1),s(e,n,c),s(e,n,l,!0)),f=!0;break}}if(f)break}}},delegateListener:c,delegateUseCapture:l,delegatedEvents:r,documents:i,targets:n,supportsOptions:!1,supportsPassive:!1};function a(t,e,r,i){if(t.addEventListener){var a=Ge(i),s=bt(n,(function(e){return e.eventTarget===t}));s||(s={eventTarget:t,events:{}},n.push(s)),s.events[e]||(s.events[e]=[]),bt(s.events[e],(function(t){return t.func===r&&Ne(t.options,a)}))||(t.addEventListener(e,r,o.supportsOptions?a:a.capture),s.events[e].push({func:r,options:a}))}}function s(t,e,r,i){if(t.addEventListener&&t.removeEventListener){var a=yt(n,(function(e){return e.eventTarget===t})),c=n[a];if(c&&c.events)if("all"!==e){var l=!1,u=c.events[e];if(u){if("all"===r){for(var p=u.length-1;p>=0;p--){var f=u[p];s(t,e,f.func,f.options)}return}for(var d=Ge(i),h=0;h=2)continue;if(!i.interacting()&&e===i.pointerType)return i}return null}};function Ke(t,e){return t.pointers.some((function(t){return t.id===e}))}var $e=He,Je=["pointerDown","pointerMove","pointerUp","updatePointer","removePointer","windowBlur"];function Qe(t,e){return function(n){var r=e.interactions.list,i=dt(n),o=ht(n),a=o[0],s=o[1],c=[];if(/^touch/.test(n.type)){e.prevTouchTime=e.now();for(var l=0,u=n.changedTouches;l=0;r--){var i=e.interactions.list[r];i.interactable===n&&(i.stop(),e.fire("interactions:destroy",{interaction:i}),i.destroy(),e.interactions.list.length>2&&e.interactions.list.splice(r,1))}}},onDocSignal:tn,doOnInteractions:Qe,methodNames:Je},nn=en,rn=function(t){return t[t.On=0]="On",t[t.Off=1]="Off",t}(rn||{}),on=function(){function t(e,n,i,o){r(this,t),this.target=void 0,this.options=void 0,this._actions=void 0,this.events=new Ve,this._context=void 0,this._win=void 0,this._doc=void 0,this._scopeEvents=void 0,this._actions=n.actions,this.target=e,this._context=n.context||i,this._win=y(B(e)?this._context:e),this._doc=this._win.document,this._scopeEvents=o,this.set(n)}return o(t,[{key:"_defaults",get:function(){return{base:{},perAction:{},actions:{}}}},{key:"setOnEvents",value:function(t,e){return w.func(e.onstart)&&this.on("".concat(t,"start"),e.onstart),w.func(e.onmove)&&this.on("".concat(t,"move"),e.onmove),w.func(e.onend)&&this.on("".concat(t,"end"),e.onend),w.func(e.oninertiastart)&&this.on("".concat(t,"inertiastart"),e.oninertiastart),this}},{key:"updatePerActionListeners",value:function(t,e,n){var r,i=this,o=null==(r=this._actions.map[t])?void 0:r.filterEventType,a=function(t){return(null==o||o(t))&&ve(t,i._actions)};(w.array(e)||w.object(e))&&this._onOff(rn.Off,t,e,void 0,a),(w.array(n)||w.object(n))&&this._onOff(rn.On,t,n,void 0,a)}},{key:"setPerAction",value:function(t,e){var n=this._defaults;for(var r in e){var i=r,o=this.options[t],a=e[i];"listeners"===i&&this.updatePerActionListeners(t,o.listeners,a),w.array(a)?o[i]=mt(a):w.plainObject(a)?(o[i]=V(o[i]||{},ge(a)),w.object(n.perAction[i])&&"enabled"in n.perAction[i]&&(o[i].enabled=!1!==a.enabled)):w.bool(a)&&w.object(n.perAction[i])?o[i].enabled=a:o[i]=a}}},{key:"getRect",value:function(t){return t=t||(w.element(this.target)?this.target:null),w.string(this.target)&&(t=t||this._context.querySelector(this.target)),L(t)}},{key:"rectChecker",value:function(t){var e=this;return w.func(t)?(this.getRect=function(n){var r=V({},t.apply(e,n));return"width"in r||(r.width=r.right-r.left,r.height=r.bottom-r.top),r},this):null===t?(delete this.getRect,this):this.getRect}},{key:"_backCompatOption",value:function(t,e){if(B(e)||w.object(e)){for(var n in this.options[t]=e,this._actions.map)this.options[n][t]=e;return this}return this.options[t]}},{key:"origin",value:function(t){return this._backCompatOption("origin",t)}},{key:"deltaSource",value:function(t){return"page"===t||"client"===t?(this.options.deltaSource=t,this):this.options.deltaSource}},{key:"getAllElements",value:function(){var t=this.target;return w.string(t)?Array.from(this._context.querySelectorAll(t)):w.func(t)&&t.getAllElements?t.getAllElements():w.element(t)?[t]:[]}},{key:"context",value:function(){return this._context}},{key:"inContext",value:function(t){return this._context===t.ownerDocument||M(this._context,t)}},{key:"testIgnoreAllow",value:function(t,e,n){return!this.testIgnore(t.ignoreFrom,e,n)&&this.testAllow(t.allowFrom,e,n)}},{key:"testAllow",value:function(t,e,n){return!t||!!w.element(n)&&(w.string(t)?F(n,t,e):!!w.element(t)&&M(t,n))}},{key:"testIgnore",value:function(t,e,n){return!(!t||!w.element(n))&&(w.string(t)?F(n,t,e):!!w.element(t)&&M(t,n))}},{key:"fire",value:function(t){return this.events.fire(t),this}},{key:"_onOff",value:function(t,e,n,r,i){w.object(e)&&!w.array(e)&&(r=n,n=null);var o=$(e,n,i);for(var a in o){"wheel"===a&&(a=I.wheelEvent);for(var s=0,c=o[a];s=0;n--){var r=e[n],i=r.selector,o=r.context,a=r.listeners;i===this.target&&o===this._context&&e.splice(n,1);for(var s=a.length-1;s>=0;s--)this._scopeEvents.removeDelegate(this.target,this._context,t,a[s][0],a[s][1])}else this._scopeEvents.remove(this.target,"all")}}]),t}(),an=function(){function t(e){var n=this;r(this,t),this.list=[],this.selectorMap={},this.scope=void 0,this.scope=e,e.addListeners({"interactable:unset":function(t){var e=t.interactable,r=e.target,i=w.string(r)?n.selectorMap[r]:r[n.scope.id],o=yt(i,(function(t){return t===e}));i.splice(o,1)}})}return o(t,[{key:"new",value:function(t,e){e=V(e||{},{actions:this.scope.actions});var n=new this.scope.Interactable(t,e,this.scope.document,this.scope.events);return this.scope.addDocument(n._doc),this.list.push(n),w.string(t)?(this.selectorMap[t]||(this.selectorMap[t]=[]),this.selectorMap[t].push(n)):(n.target[this.scope.id]||Object.defineProperty(t,this.scope.id,{value:[],configurable:!0}),t[this.scope.id].push(n)),this.scope.fire("interactable:new",{target:t,options:e,interactable:n,win:this.scope._win}),n}},{key:"getExisting",value:function(t,e){var n=e&&e.context||this.scope.document,r=w.string(t),i=r?this.selectorMap[t]:t[this.scope.id];if(i)return bt(i,(function(e){return e._context===n&&(r||e.inContext(t))}))}},{key:"forEachMatch",value:function(t,e){for(var n=0,r=this.list;nMath.abs(u.y),l.coords,l.rect),V(i,l.coords));return l.eventProps},defaults:{ratio:"preserve",equalDelta:!1,modifiers:[],enabled:!1}};function gn(t,e,n){var r=t.startCoords,i=t.edgeSign;e?n.y=r.y+(n.x-r.x)*i.y:n.x=r.x+(n.y-r.y)*i.x}function mn(t,e,n,r){var i=t.startRect,o=t.startCoords,a=t.ratio,s=t.edgeSign;if(e){var c=r.width/a;n.y=o.y+(c-i.height)*s.y}else{var l=r.height*a;n.x=o.x+(l-i.width)*s.x}}var yn=be(vn,"aspectRatio"),bn=function(){};bn._defaults={};var xn=bn;function wn(t,e,n){return w.func(t)?G(t,e.interactable,e.element,[n.x,n.y,e]):G(t,e.interactable,e.element)}var En={start:function(t){var e=t.rect,n=t.startOffset,r=t.state,i=t.interaction,o=t.pageCoords,a=r.options,s=a.elementRect,c=V({left:0,top:0,right:0,bottom:0},a.offset||{});if(e&&s){var l=wn(a.restriction,i,o);if(l){var u=l.right-l.left-e.width,p=l.bottom-l.top-e.height;u<0&&(c.left+=u,c.right+=u),p<0&&(c.top+=p,c.bottom+=p)}c.left+=n.left-e.width*s.left,c.top+=n.top-e.height*s.top,c.right+=n.right-e.width*(1-s.right),c.bottom+=n.bottom-e.height*(1-s.bottom)}r.offset=c},set:function(t){var e=t.coords,n=t.interaction,r=t.state,i=r.options,o=r.offset,a=wn(i.restriction,n,e);if(a){var s=function(t){return!t||"left"in t&&"top"in t||((t=V({},t)).left=t.x||0,t.top=t.y||0,t.right=t.right||t.left+t.width,t.bottom=t.bottom||t.top+t.height),t}(a);e.x=Math.max(Math.min(s.right-o.right,e.x),s.left+o.left),e.y=Math.max(Math.min(s.bottom-o.bottom,e.y),s.top+o.top)}},defaults:{restriction:null,elementRect:null,offset:null,endOnly:!1,enabled:!1}},Tn=be(En,"restrict"),Sn={top:1/0,left:1/0,bottom:-1/0,right:-1/0},_n={top:-1/0,left:-1/0,bottom:1/0,right:1/0};function Pn(t,e){for(var n=0,r=["top","left","bottom","right"];n(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})())); 8 | //# sourceMappingURL=xterm-addon-fit.js.map -------------------------------------------------------------------------------- /web/vendor/xterm.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Minified by jsDelivr using clean-css v5.3.2. 3 | * Original file: /npm/xterm@5.3.0/css/xterm.css 4 | * 5 | * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files 6 | */ 7 | .xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:0}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm .xterm-cursor-pointer,.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative} 8 | /*# sourceMappingURL=/sm/c5efcc609fa43782768884f031126e0ac5752fe8f1f6c9d1eed7903b1bba39c1.map */ --------------------------------------------------------------------------------