├── .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 |
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 | [](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 |
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 |
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 | Before |
89 | After |
90 |
91 |
92 |  |
93 |  |
94 |
95 |
96 |
97 |
98 | - Optimized for deleting Reroute Node.
99 |
100 |
101 | Preview
102 |
103 |
104 | Before |
105 | After |
106 |
107 |
108 |  |
109 |  |
110 |
111 |
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 */
--------------------------------------------------------------------------------