Initial Commit
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'WASasquatch' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} ## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
+157
-207
@@ -1,207 +1,157 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
#poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
#pdm.lock
|
||||
#pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
#pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Cursor
|
||||
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
||||
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
||||
# refer to https://docs.cursor.com/context/ignore-files
|
||||
.cursorignore
|
||||
.cursorindexingignore
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
*.pyc
|
||||
|
||||
# Ignore Windows ADS quarantine metadata files
|
||||
*:Zone.Identifier
|
||||
*:zone.identifier
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
*.pyc
|
||||
|
||||
# Ignore Windows ADS quarantine metadata files
|
||||
*:Zone.Identifier
|
||||
*:zone.identifier
|
||||
@@ -0,0 +1,68 @@
|
||||
# ComfyUI RetroArch Player
|
||||
|
||||

|
||||
|
||||
Embeds the RetroArch Web Player inside a ComfyUI node.
|
||||
|
||||
This project uses the hosted RetroArch web player:
|
||||
- https://web.libretro.com/
|
||||
|
||||
## Install
|
||||
|
||||
- **Option A (git clone)**
|
||||
- Clone this repo into:
|
||||
- `ComfyUI/custom_nodes/ComfyUI_RetroArch_Player`
|
||||
|
||||
- **Option B (Manager)**
|
||||
- Open Manager and install **ComfyUI_RetroArch_Player**
|
||||
|
||||
- **Option C (zip)**
|
||||
- Download the repo as a zip.
|
||||
- Extract it to:
|
||||
- `ComfyUI/custom_nodes/ComfyUI_RetroArch_Player`
|
||||
|
||||
Restart ComfyUI after installing.
|
||||
|
||||
## Usage
|
||||
|
||||
- **Add the node**
|
||||
- In ComfyUI, add the node:
|
||||
- `ComfyRetroArchPlayer` (RetroArch Player)
|
||||
|
||||
- **Use RetroArch**
|
||||
- The player UI is embedded inside the node.
|
||||
- Use the RetroArch menu to select a core and load content.
|
||||
- Supports many systems including NES, SNES, Genesis, PlayStation, and more.
|
||||
|
||||
## Notes
|
||||
|
||||
- **Hosted web app**
|
||||
- This embed loads `https://web.libretro.com/` in an iframe, so it requires internet access.
|
||||
|
||||
- **Fullscreen limitations**
|
||||
- Due to cross-origin restrictions, the fullscreen button in RetroArch may not work within the iframe.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **The player UI doesn't update after changes**
|
||||
- Do a hard refresh in your browser:
|
||||
- `Ctrl+F5`
|
||||
- Or clear browser cache for the ComfyUI site.
|
||||
|
||||
- **Node appears but iframe is missing/blank**
|
||||
- Open DevTools Console and look for errors.
|
||||
- Confirm that `https://web.libretro.com/` is reachable from your browser.
|
||||
|
||||
- **Iframe seems misaligned during zoom/pan**
|
||||
- Make sure you're running the latest `web/comfy_retroarch_player.js` from this repo.
|
||||
- If you have multiple copies installed, remove old versions from other `custom_nodes` folders.
|
||||
|
||||
- **I uploaded a rom with "Add Content" but it doesn't show up**
|
||||
- Now that you've uploaded a ROM, click "Load Content" and and click "Start Directory" to see uploaded ROMs to select.
|
||||
- Select the ROM file and it will list compatible cores to select.
|
||||
- Select the core and the game should launch.
|
||||
|
||||
## Credits / License
|
||||
|
||||
- RetroArch Web Player: [web.libretro.com](https://web.libretro.com/) by the libretro team
|
||||
- This extension embeds the hosted RetroArch web player via iframe.
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
|
||||
class ComfyRetroArchPlayer:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {}}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "fun"
|
||||
|
||||
def run(self):
|
||||
return ()
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ComfyRetroArchPlayer": ComfyRetroArchPlayer,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ComfyRetroArchPlayer": "RetroArch Player",
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
[project]
|
||||
name = "comfyui-retroarch-player"
|
||||
version = "0.0.1"
|
||||
description = "Simple ComfyUI RetroArch Web Player embed"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
license = { text = "MIT License" }
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent"
|
||||
]
|
||||
dynamic = ["dependencies"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = { file = ["requirements.txt"] }
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/WASasquatch/ComfyUI_RetroArch_Player"
|
||||
"Bug Tracker" = "https://github.com/WASasquatch/ComfyUI_RetroArch_Player/issues"
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "was"
|
||||
DisplayName = "ComfyUI RetroArch Player"
|
||||
requires-comfyui = ">=1.0.0"
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
/* WAS RetroArch Player v2024.12.28.1155 */
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
console.log("[WAS RetroArch] ===== SCRIPT LOADED v2024.12.28.1155 =====");
|
||||
|
||||
const EXT_NAME = "WAS.RetroArchPlayer";
|
||||
const NODE_NAME = "ComfyRetroArchPlayer";
|
||||
|
||||
const DEFAULT_NODE_SIZE = [980, 600];
|
||||
|
||||
const STATE = {
|
||||
container: null,
|
||||
nodeIdToIframe: new Map(),
|
||||
cleanupIntervalId: null,
|
||||
cleanupListenersAttached: false,
|
||||
};
|
||||
|
||||
function getActiveGraphNodes() {
|
||||
const g = app?.graph || app?.canvas?.graph;
|
||||
const nodes = g?._nodes || g?.nodes;
|
||||
return Array.isArray(nodes) ? nodes : [];
|
||||
}
|
||||
|
||||
function isRetroArchPlayerNode(node) {
|
||||
try {
|
||||
return (
|
||||
node?.comfyClass === NODE_NAME ||
|
||||
node?.type === NODE_NAME ||
|
||||
node?.constructor?.comfyClass === NODE_NAME ||
|
||||
node?.constructor?.type === NODE_NAME
|
||||
);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function removeIframeByKey(key) {
|
||||
try {
|
||||
const iframe = STATE.nodeIdToIframe.get(key);
|
||||
if (!iframe) return;
|
||||
iframe.remove();
|
||||
} catch (e) {
|
||||
} finally {
|
||||
STATE.nodeIdToIframe.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupOrphanIframes() {
|
||||
try {
|
||||
const nodes = getActiveGraphNodes();
|
||||
const activeIds = new Set(
|
||||
nodes
|
||||
.filter((n) => isRetroArchPlayerNode(n))
|
||||
.map((n) => String(n.id))
|
||||
);
|
||||
|
||||
for (const key of Array.from(STATE.nodeIdToIframe.keys())) {
|
||||
if (!activeIds.has(key)) {
|
||||
removeIframeByKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (STATE.nodeIdToIframe.size === 0 && STATE.container) {
|
||||
try {
|
||||
STATE.container.remove();
|
||||
} catch (e) {
|
||||
}
|
||||
STATE.container = null;
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
function ensureCleanupRunning() {
|
||||
if (STATE.cleanupIntervalId != null) return;
|
||||
STATE.cleanupIntervalId = window.setInterval(() => cleanupOrphanIframes(), 1000);
|
||||
if (!STATE.cleanupListenersAttached) {
|
||||
STATE.cleanupListenersAttached = true;
|
||||
const onVis = () => cleanupOrphanIframes();
|
||||
const onFocus = () => cleanupOrphanIframes();
|
||||
window.addEventListener("visibilitychange", onVis);
|
||||
window.addEventListener("focus", onFocus);
|
||||
}
|
||||
}
|
||||
|
||||
function getContainer() {
|
||||
if (STATE.container) return STATE.container;
|
||||
const el = document.createElement("div");
|
||||
el.id = "was-retroarch-overlay";
|
||||
el.style.position = "absolute";
|
||||
el.style.left = "0";
|
||||
el.style.top = "0";
|
||||
el.style.width = "0";
|
||||
el.style.height = "0";
|
||||
el.style.pointerEvents = "none";
|
||||
el.style.zIndex = "1000";
|
||||
document.body.appendChild(el);
|
||||
STATE.container = el;
|
||||
return el;
|
||||
}
|
||||
|
||||
function ensureIframeForNode(node) {
|
||||
cleanupOrphanIframes();
|
||||
ensureCleanupRunning();
|
||||
|
||||
const key = String(node.id);
|
||||
const existing = STATE.nodeIdToIframe.get(key);
|
||||
if (existing) return existing;
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.setAttribute(
|
||||
"sandbox",
|
||||
"allow-scripts allow-same-origin allow-forms allow-popups allow-downloads allow-pointer-lock"
|
||||
);
|
||||
iframe.style.position = "absolute";
|
||||
iframe.style.border = "0";
|
||||
iframe.style.pointerEvents = "auto";
|
||||
iframe.style.background = "#000";
|
||||
iframe.style.left = "0px";
|
||||
iframe.style.top = "0px";
|
||||
iframe.style.width = "0px";
|
||||
iframe.style.height = "0px";
|
||||
iframe.style.display = "none";
|
||||
iframe.style.borderRadius = "8px";
|
||||
|
||||
|
||||
iframe.src = "https://web.libretro.com/";
|
||||
iframe.allowFullscreen = true;
|
||||
iframe.allow = "fullscreen";
|
||||
|
||||
getContainer().appendChild(iframe);
|
||||
STATE.nodeIdToIframe.set(key, iframe);
|
||||
|
||||
const oldOnRemoved = node.onRemoved;
|
||||
node.onRemoved = function () {
|
||||
try {
|
||||
removeIframeByKey(String(this.id));
|
||||
cleanupOrphanIframes();
|
||||
} catch (e) {
|
||||
}
|
||||
return oldOnRemoved ? oldOnRemoved.apply(this, arguments) : undefined;
|
||||
};
|
||||
|
||||
return iframe;
|
||||
}
|
||||
|
||||
function updateIframeRect(node, iframe) {
|
||||
const canvas = app.canvas;
|
||||
const canvasEl = canvas?.canvas;
|
||||
if (!canvasEl) return;
|
||||
|
||||
const rect = canvasEl.getBoundingClientRect();
|
||||
const ds = canvas.ds;
|
||||
const scale = ds?.scale ?? 1;
|
||||
const offset = ds?.offset ?? [0, 0];
|
||||
|
||||
const x = rect.left + (node.pos[0] + offset[0]) * scale;
|
||||
const y = rect.top + (node.pos[1] + offset[1]) * scale;
|
||||
const w = node.size[0] * scale;
|
||||
const h = node.size[1] * scale;
|
||||
|
||||
const titleHRaw =
|
||||
(typeof node?.title_height === "number" && Number.isFinite(node.title_height) && node.title_height) ||
|
||||
(typeof node?.constructor?.title_height === "number" &&
|
||||
Number.isFinite(node.constructor.title_height) &&
|
||||
node.constructor.title_height) ||
|
||||
globalThis?.LiteGraph?.NODE_TITLE_HEIGHT ||
|
||||
30;
|
||||
const titleH = titleHRaw * scale;
|
||||
const insetX = 8 * scale;
|
||||
const insetTop = 0;
|
||||
const insetBottom = 8 * scale;
|
||||
const headerAdjust = 20 * scale;
|
||||
const innerX = x + insetX;
|
||||
const innerY = y + titleH + insetTop - headerAdjust;
|
||||
const innerW = w - insetX * 2;
|
||||
const innerH = h - titleH - insetTop - insetBottom + headerAdjust;
|
||||
|
||||
const nx = Number.isFinite(innerX) ? innerX : 0;
|
||||
const ny = Number.isFinite(innerY) ? innerY : 0;
|
||||
const nw = Number.isFinite(innerW) ? innerW : 0;
|
||||
const nh = Number.isFinite(innerH) ? innerH : 0;
|
||||
|
||||
iframe.style.left = `${Math.round(nx)}px`;
|
||||
iframe.style.top = `${Math.round(ny)}px`;
|
||||
iframe.style.width = `${Math.max(0, Math.round(nw))}px`;
|
||||
iframe.style.height = `${Math.max(0, Math.round(nh))}px`;
|
||||
|
||||
const isCollapsed = !!node.flags?.collapsed;
|
||||
const hasArea = nw >= 2 && nh >= 2;
|
||||
iframe.style.display = !isCollapsed && hasArea ? "block" : "none";
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: EXT_NAME,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData?.name !== NODE_NAME) return;
|
||||
|
||||
ensureCleanupRunning();
|
||||
|
||||
const oldOnNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const r = oldOnNodeCreated ? oldOnNodeCreated.apply(this, arguments) : undefined;
|
||||
try {
|
||||
if (!Array.isArray(this.size) || this.size.length < 2) {
|
||||
this.size = [...DEFAULT_NODE_SIZE];
|
||||
} else {
|
||||
this.size[0] = Math.max(this.size[0] ?? 0, DEFAULT_NODE_SIZE[0]);
|
||||
this.size[1] = Math.max(this.size[1] ?? 0, DEFAULT_NODE_SIZE[1]);
|
||||
}
|
||||
this.setDirtyCanvas?.(true, true);
|
||||
} catch (e) {
|
||||
}
|
||||
return r;
|
||||
};
|
||||
|
||||
const oldOnDrawForeground = nodeType.prototype.onDrawForeground;
|
||||
nodeType.prototype.onDrawForeground = function (ctx) {
|
||||
const r = oldOnDrawForeground ? oldOnDrawForeground.apply(this, arguments) : undefined;
|
||||
try {
|
||||
const iframe = ensureIframeForNode(this);
|
||||
updateIframeRect(this, iframe);
|
||||
} catch (e) {
|
||||
}
|
||||
return r;
|
||||
};
|
||||
|
||||
const oldOnResize = nodeType.prototype.onResize;
|
||||
nodeType.prototype.onResize = function (size) {
|
||||
const r = oldOnResize ? oldOnResize.apply(this, arguments) : undefined;
|
||||
try {
|
||||
const iframe = ensureIframeForNode(this);
|
||||
updateIframeRect(this, iframe);
|
||||
} catch (e) {
|
||||
}
|
||||
return r;
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user